class DialogOpenArchive(Toplevel): def __init__(self, parent, openType, filesource, filenames, title, colHeader, showAltViewButton=False): if isinstance(parent, Cntlr): cntlr = parent parent = parent.parent # parent is cntlrWinMain else: # parent is a Toplevel dialog cntlr = parent.cntlr super(DialogOpenArchive, self).__init__(parent) self.parent = parent self.showAltViewButton = showAltViewButton parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.accepted = False self.transient(self.parent) frame = Frame(self) treeFrame = Frame(frame, width=500) vScrollbar = Scrollbar(treeFrame, orient=VERTICAL) hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL) self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set) self.treeView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.treeView.xview hScrollbar.grid(row=1, column=0, sticky=(E, W)) vScrollbar["command"] = self.treeView.yview vScrollbar.grid(row=0, column=1, sticky=(N, S)) treeFrame.columnconfigure(0, weight=1) treeFrame.rowconfigure(0, weight=1) treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.treeView.focus_set() if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(_("loading archive {0}").format(filesource.url)) self.filesource = filesource self.filenames = filenames self.selection = filesource.selection self.hasToolTip = False selectedNode = None if openType == ENTRY_POINTS: try: metadataFiles = filesource.taxonomyPackageMetadataFiles ''' take first for now if len(metadataFiles) != 1: raise IOError(_("Taxonomy package contained more than one metadata file: {0}.") .format(', '.join(metadataFiles))) ''' metadataFile = metadataFiles[0] metadata = filesource.url + os.sep + metadataFile self.metadataFilePrefix = os.sep.join( os.path.split(metadataFile)[:-1]) if self.metadataFilePrefix: self.metadataFilePrefix += "/" # zip contents have /, never \ file seps self.taxonomyPkgMetaInf = '{}/META-INF/'.format( os.path.splitext(os.path.basename(filesource.url))[0]) self.taxonomyPackage = parsePackage( cntlr, filesource, metadata, os.sep.join(os.path.split(metadata)[:-1]) + os.sep) if self.taxonomyPackage["entryPoints"]: # may have instance documents too self.packageContainedInstances = [] packageContentTypeCounts = {} for suffix in (".xhtml", ".htm", ".html"): for potentialInstance in filesource.dir: if potentialInstance.endswith(".xhtml"): _type = "Inline Instance" self.packageContainedInstances.append( [potentialInstance, _type]) packageContentTypeCounts[ potentialInstance] = packageContentTypeCounts.get( potentialInstance, 0) + 1 if self.packageContainedInstances: break if self.packageContainedInstances: # add sequences to any duplicated entry types for _type, count in packageContentTypeCounts.items(): if count > 1: _dupNo = 0 for i in range( len(self.packageContainedInstances)): if self.packageContainedInstances[i][ 0] == _type: _dupNo += 1 self.packageContainedInstances[i][ 0] = "{} {}".format(_type, _dupNo) else: # may be a catalog file with no entry oint names openType = ARCHIVE # no entry points to show, just archive self.showAltViewButton = False except Exception as e: self.close() err = _( "Failed to parse metadata; the underlying error was: {0}" ).format(e) messagebox.showerror(_("Malformed taxonomy package"), err) cntlr.addToLog(err) return if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(None) if openType in (DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): y = 3 else: y = 1 okButton = Button(frame, text=_("OK"), command=self.ok) cancelButton = Button(frame, text=_("Cancel"), command=self.close) okButton.grid(row=y, column=2, sticky=(S, E, W), pady=3) cancelButton.grid(row=y, column=3, sticky=(S, E, W), pady=3, padx=3) if self.showAltViewButton: self.altViewButton = Button(frame, command=self.showAltView) self.altViewButton.grid(row=y, column=0, sticky=(S, W), pady=3, padx=3) self.loadTreeView(openType, colHeader, title) self.geometry("+{0}+{1}".format(dialogX + 50, dialogY + 100)) frame.grid(row=0, column=0, sticky=(N, S, E, W)) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.toolTipText = StringVar() if self.hasToolTip: self.treeView.bind("<Motion>", self.motion, '+') self.treeView.bind("<Leave>", self.leave, '+') self.toolTipText = StringVar() self.toolTip = ToolTip(self.treeView, textvariable=self.toolTipText, wraplength=640, follow_mouse=True, state="disabled") self.toolTipRowId = None self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeView(self, openType, title, colHeader): self.title(title) self.openType = openType selectedNode = None # clear previous treeview entries for previousNode in self.treeView.get_children(""): self.treeView.delete(previousNode) # set up treeView widget and tabbed pane if openType in (ARCHIVE, DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): if openType in (PLUGIN, PACKAGE): width = 770 else: width = 500 self.treeView.column("#0", width=width, anchor="w") self.treeView.heading("#0", text=colHeader) self.isRss = getattr(self.filesource, "isRss", False) if self.isRss: self.treeView.column("#0", width=350, anchor="w") self.treeView["columns"] = ("descr", "date", "instDoc") self.treeView.column("descr", width=50, anchor="center", stretch=False) self.treeView.heading("descr", text="Form") self.treeView.column("date", width=170, anchor="w", stretch=False) self.treeView.heading("date", text="Pub Date") self.treeView.column("instDoc", width=200, anchor="w", stretch=False) self.treeView.heading("instDoc", text="Instance Document") elif openType == PLUGIN: self.treeView.column("#0", width=150, anchor="w") self.treeView["columns"] = ("name", "vers", "descr", "license") self.treeView.column("name", width=150, anchor="w", stretch=False) self.treeView.heading("name", text="Name") self.treeView.column("vers", width=60, anchor="w", stretch=False) self.treeView.heading("vers", text="Version") self.treeView.column("descr", width=300, anchor="w", stretch=False) self.treeView.heading("descr", text="Description") self.treeView.column("license", width=60, anchor="w", stretch=False) self.treeView.heading("license", text="License") elif openType == PACKAGE: self.treeView.column("#0", width=200, anchor="w") self.treeView["columns"] = ("vers", "descr", "license") self.treeView.column("vers", width=100, anchor="w", stretch=False) self.treeView.heading("vers", text="Version") self.treeView.column("descr", width=400, anchor="w", stretch=False) self.treeView.heading("descr", text="Description") self.treeView.column("license", width=70, anchor="w", stretch=False) self.treeView.heading("license", text="License") else: self.treeView["columns"] = tuple() loadedPaths = [] for i, filename in enumerate(self.filenames): if isinstance(filename, tuple): if self.isRss: form, date, instDoc = filename[2:5] elif openType == PLUGIN: name, vers, descr, license = filename[3:7] elif openType == PACKAGE: vers, descr, license = filename[3:6] filename = filename[0] # ignore tooltip self.hasToolTip = True if filename.endswith("/"): filename = filename[:-1] path = filename.split("/") if not self.isRss and len( path) > 1 and path[:-1] in loadedPaths: parent = "file{0}".format(loadedPaths.index(path[:-1])) else: parent = "" node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1]) if self.isRss: self.treeView.set(node, "descr", form) self.treeView.set(node, "date", date) self.treeView.set(node, "instDoc", os.path.basename(instDoc)) elif openType == PLUGIN: self.treeView.set(node, "name", name) self.treeView.set(node, "vers", vers) self.treeView.set(node, "descr", descr) self.treeView.set(node, "license", license) elif openType == PACKAGE: self.treeView.set(node, "vers", vers) self.treeView.set(node, "descr", descr) self.treeView.set(node, "license", license) if self.selection == filename: selectedNode = node loadedPaths.append(path) elif openType == ENTRY_POINTS: self.treeView.column("#0", width=200, anchor="w") self.treeView.heading("#0", text="Name") self.treeView["columns"] = ("url", ) self.treeView.column("url", width=300, anchor="w") self.treeView.heading("url", text="URL") for fileType, fileUrl in getattr(self, "packageContainedInstances", ()): self.treeView.insert("", "end", fileUrl, values=fileType, text=fileUrl or urls[0][2]) for name, urls in sorted( self.taxonomyPackage["entryPoints"].items(), key=lambda i: i[0][2]): self.treeView.insert("", "end", name, values="\n".join(url[1] for url in urls), text=name or urls[0][2]) self.hasToolTip = True else: # unknown openType return None if selectedNode: self.treeView.see(selectedNode) self.treeView.selection_set(selectedNode) if self.showAltViewButton: self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries")) def ok(self, event=None): selection = self.treeView.selection() if len(selection) > 0: if hasattr(self, "taxonomyPackage"): # load file source remappings self.filesource.mappedPaths = self.taxonomyPackage[ "remappings"] filename = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): filename = self.filenames[int(selection[0][4:])] if isinstance(filename, tuple): if self.isRss: filename = filename[4] else: filename = filename[0] elif self.openType == ENTRY_POINTS: epName = selection[0] #index 0 is the remapped Url, as opposed to the canonical one used for display # Greg Acsone reports [0] does not work for Corep 1.6 pkgs, need [1], old style packages filenames = [] for _url, _type in self.packageContainedInstances: # check if selection was an inline instance if _type == epName: filenames.append(_url) if not filenames: # else if it's a named taxonomy entry point for url in self.taxonomyPackage["entryPoints"][epName]: filename = url[0] if not filename.endswith("/"): # check if it's an absolute URL rather than a path into the archive if not isHttpUrl( filename ) and self.metadataFilePrefix != self.taxonomyPkgMetaInf: # assume it's a path inside the archive: filename = self.metadataFilePrefix + filename filenames.append(filename) if filenames: self.filesource.select(filenames) self.accepted = True self.close() return elif self.openType in (PLUGIN, PACKAGE): filename = self.filenames[int(selection[0][4:])][2] if filename is not None and not filename.endswith("/"): if hasattr(self, "taxonomyPackage"): # attempt to unmap the filename to original file # will be mapped again in loading, but this allows schemaLocation to be unmapped for prefix, remapping in self.taxonomyPackage[ "remappings"].items(): if isHttpUrl(remapping): remapStart = remapping else: remapStart = self.metadataFilePrefix + remapping if filename.startswith(remapStart): # set unmmapped file filename = prefix + filename[len(remapStart):] break if self.openType in (PLUGIN, PACKAGE): self.filesource.selection = filename else: self.filesource.select(filename) self.accepted = True self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def showAltView(self, event=None): if self.openType == ENTRY_POINTS: self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File")) else: self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File")) def leave(self, *args): self.toolTipRowId = None def motion(self, *args): tvRowId = self.treeView.identify_row(args[0].y) if tvRowId != self.toolTipRowId: text = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): self.toolTipRowId = tvRowId if tvRowId and len(tvRowId) > 4: try: text = self.filenames[int(tvRowId[4:])] if isinstance(text, tuple): text = (text[1] or "").replace("\\n", "\n") except (KeyError, ValueError): pass elif self.openType == ENTRY_POINTS: try: text = "{0}\n{1}".format( tvRowId, "\n".join( url[1] for url in self.taxonomyPackage["entryPoints"][tvRowId])) except KeyError: pass self.setToolTip(text) def setToolTip(self, text): self.toolTip._hide() if text: self.toolTipText.set(text) self.toolTip.configure(state="normal") self.toolTip._schedule() else: self.toolTipText.set("") self.toolTip.configure(state="disabled")
class StructEditor(tk.Frame, Subscriber, Observable): """Displays and allow editing of the coordinates and points of one superstructure Args: parent (tk.Frame): widget that is the parent of the editor structure (model.structure.Structure): the ship superstructure that will be edited """ def __init__(self, parent, structure, command_stack): Subscriber.__init__(self, structure) Observable.__init__(self) tk.Frame.__init__(self, parent, borderwidth=4, relief="raised") self._structure = structure self._command_stack = command_stack self.bind("<Button-1>", self._on_click) self.bind("<FocusIn>", self._on_get_focus) self.bind("<FocusOut>", self._on_lost_focus) self._tree = Treeview(self, columns=["#", "X", "Y"], selectmode="browse") #kill the icon column self._tree.column("#0", minwidth=0, width=0) style = Style() style.configure("Treeview.Heading", font=(None, 16)) self._tree.column("#", minwidth=20, width=40, anchor=tk.CENTER) self._tree.column("X", minwidth=20, width=40, anchor=tk.CENTER) self._tree.column("Y", minwidth=20, width=40, anchor=tk.CENTER) self._tree.heading("#", text="#") self._tree.heading("X", text="\u21d5") self._tree.heading("Y", text="\u21d4") self._tree.grid(row=0, column=POINTS_TABLE_COL, sticky=tk.N + tk.S) self._tree.bind("<<TreeviewSelect>>", self._on_point_selected) self._tree.bind("<FocusIn>", self._on_get_focus) self._tree.bind("<FocusOut>", self._on_lost_focus) scroll = Scrollbar(self, command=self._tree.yview) scroll.grid(row=0, column=SCROLL_COL, sticky=tk.N + tk.S) scroll.bind("<FocusIn>", self._on_get_focus) self._tree.configure(yscrollcommand=scroll.set) self._index_of_sel_point = -1 self._fill_tree() self._edit_zone = EditZone(self, self._structure, command_stack, self._on_get_focus) self._edit_zone.grid(column=EDIT_ZONE_COL, row=0, sticky=tk.N) def _set_selection(self, new_sel_index): """Set the selected point to the new_sel_index Gives correct focus, update, etc to the editor's widgets if the index is outside of the self.points, does nothing """ if new_sel_index >= 0 and new_sel_index <= len(self.points) - 1: iid = self._tree.get_children()[new_sel_index] self._tree.selection_set(iid) def _on_click(self, *_args): self._tree.focus_set() def _on_get_focus(self, *_args): if self._index_of_sel_point == -1: self._set_selection(0) self.configure(relief="sunken") self._notify("focus", {}) def _on_lost_focus(self, event): if event.widget not in self.winfo_children(): self.configure(relief="raised") def _on_point_selected(self, _event): """called back when a point is selected in the table/treeview Updates the editable fields """ selected_iid = self._tree.selection() self._index_of_sel_point = self._tree.index(selected_iid) self._edit_zone.set_editable_point( self._tree.item(selected_iid)["values"][0]) self._notify("focus", {}) def _fill_tree(self): """fills the treeview with data from the structure """ self._tree.delete(*self._tree.get_children()) for point_index, point in enumerate(self._structure.points): self._tree.insert( '', 'end', values=[point_index, round(point[0]), round(point[1])]) if point_index == self._index_of_sel_point: self._set_selection(point_index) def _on_notification(self, observable, event_type, event_info): """Rebuild the treeview on structure update Depending on the structure state and the operation, change the selcted point """ if event_type == "add_point": self._index_of_sel_point = event_info["index"] self._fill_tree() else: if self._index_of_sel_point >= len(self._structure.points): self._index_of_sel_point = len(self._structure.points) self._edit_zone.unset_point() self._fill_tree() self._notify("focus", {}) def update_to_coord(self, point): """Move the selected point to the position of the given point Intended to be called from click on the top view Args: point (x, y): new position in funnel coordinates """ if self._index_of_sel_point != -1 and self._index_of_sel_point <= len( self.points) - 1: self._command_stack.do( model.structure.UpdatePoint(self._structure, self._index_of_sel_point, round(point[0]), round(point[1]))) elif self._index_of_sel_point == len(self.points) or not self.points: self._command_stack.do( model.structure.AddPoint(self._structure, self._index_of_sel_point + 1, round(point[0]), round(point[1]))) if self._index_of_sel_point + 1 >= len(self.points): self.winfo_toplevel().update() self._index_of_sel_point = len(self.points) else: self._set_selection(self._index_of_sel_point + 1) self.winfo_toplevel().update() @property def points(self): """Pipe throught the struct's properties""" return self._structure.points @property def fill(self): """Pipe throught the struct's properties""" return self._structure.fill @property def selected_index(self): """the index in the struct's point list of the currently selected point Should be -1 if none selected """ return self._index_of_sel_point
class DialogPackageManager(Toplevel): def __init__(self, mainWin, packageNamesWithNewerFileDates): super(DialogPackageManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.packagesConfig = PackageManager.packagesConfig self.packagesConfigChanged = False self.packageNamesWithNewerFileDates = packageNamesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Taxonomy Packages Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find taxonomy packages:"), wraplength=64, justify="center") addLocalButton = Button(buttonFrame, text=_("Locally"), command=self.findLocally) ToolTip(addLocalButton, text=_("File chooser allows selecting taxonomy packages to add (or reload), from the local file system. " "Select either a taxonomy package zip file, or a taxonomy manifest (.taxonomyPackage.xml) within an unzipped taxonomy package. "), wraplength=240) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip(addWebButton, text=_("Dialog to enter URL full path to load (or reload) package, from the web or local file system. " "URL may be either a taxonomy package zip file, or a taxonomy manifest (.taxonomyPackage.xml) within an unzipped taxonomy package. "), wraplength=240) manifestNameButton = Button(buttonFrame, text=_("Manifest"), command=self.manifestName) ToolTip(manifestNameButton, text=_("Provide non-standard archive manifest file name pattern (e.g., *taxonomyPackage.xml). " "Uses unix file name pattern matching. " "Multiple manifest files are supported in archive (such as oasis catalogs). " "(Replaces search for either .taxonomyPackage.xml or catalog.xml). "), wraplength=240) self.manifestNamePattern = "" addLabel.grid(row=0, column=0, pady=4) addLocalButton.grid(row=1, column=0, pady=4) addWebButton.grid(row=2, column=0, pady=4) manifestNameButton.grid(row=3, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=3, sticky=(N, S, W), padx=3, pady=3) # right tree frame (packages already known to arelle) packagesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(packagesFrame, orient=VERTICAL) hScrollbar = Scrollbar(packagesFrame, orient=HORIZONTAL) self.packagesView = Treeview(packagesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.packagesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.packagesView.bind('<<TreeviewSelect>>', self.packageSelect) hScrollbar["command"] = self.packagesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.packagesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) packagesFrame.columnconfigure(0, weight=1) packagesFrame.rowconfigure(0, weight=1) packagesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.packagesView.focus_set() self.packagesView.column("#0", width=120, anchor="w") self.packagesView.heading("#0", text=_("Name")) self.packagesView["columns"] = ("ver", "status", "date", "update", "descr") self.packagesView.column("ver", width=150, anchor="w", stretch=False) self.packagesView.heading("ver", text=_("Version")) self.packagesView.column("status", width=50, anchor="w", stretch=False) self.packagesView.heading("status", text=_("Status")) self.packagesView.column("date", width=170, anchor="w", stretch=False) self.packagesView.heading("date", text=_("File Date")) self.packagesView.column("update", width=50, anchor="w", stretch=False) self.packagesView.heading("update", text=_("Update")) self.packagesView.column("descr", width=200, anchor="w", stretch=False) self.packagesView.heading("descr", text=_("Description")) remappingsFrame = Frame(frame) vScrollbar = Scrollbar(remappingsFrame, orient=VERTICAL) hScrollbar = Scrollbar(remappingsFrame, orient=HORIZONTAL) self.remappingsView = Treeview(remappingsFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.remappingsView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.remappingsView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.remappingsView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) remappingsFrame.columnconfigure(0, weight=1) remappingsFrame.rowconfigure(0, weight=1) remappingsFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.remappingsView.focus_set() self.remappingsView.column("#0", width=200, anchor="w") self.remappingsView.heading("#0", text=_("Prefix")) self.remappingsView["columns"] = ("remapping") self.remappingsView.column("remapping", width=500, anchor="w", stretch=False) self.remappingsView.heading("remapping", text=_("Remapping")) # bottom frame package info details packageInfoFrame = Frame(frame, width=700) packageInfoFrame.columnconfigure(1, weight=1) self.packageNameLabel = Label(packageInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.packageNameLabel.grid(row=0, column=0, columnspan=6, sticky=W) self.packageVersionHdr = Label(packageInfoFrame, text=_("version:"), state=DISABLED) self.packageVersionHdr.grid(row=1, column=0, sticky=W) self.packageVersionLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageVersionLabel.grid(row=1, column=1, columnspan=5, sticky=W) self.packageDescrHdr = Label(packageInfoFrame, text=_("description:"), state=DISABLED) self.packageDescrHdr.grid(row=2, column=0, sticky=W) self.packageDescrLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageDescrLabel.grid(row=2, column=1, columnspan=5, sticky=W) self.packagePrefixesHdr = Label(packageInfoFrame, text=_("prefixes:"), state=DISABLED) self.packagePrefixesHdr.grid(row=3, column=0, sticky=W) self.packagePrefixesLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packagePrefixesLabel.grid(row=3, column=1, columnspan=5, sticky=W) ToolTip(self.packagePrefixesLabel, text=_("List of prefixes that this package remaps."), wraplength=240) self.packageUrlHdr = Label(packageInfoFrame, text=_("URL:"), state=DISABLED) self.packageUrlHdr.grid(row=4, column=0, sticky=W) self.packageUrlLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageUrlLabel.grid(row=4, column=1, columnspan=5, sticky=W) ToolTip(self.packageUrlLabel, text=_("URL of taxonomy package (local file path or web loaded file)."), wraplength=240) self.packageDateHdr = Label(packageInfoFrame, text=_("date:"), state=DISABLED) self.packageDateHdr.grid(row=5, column=0, sticky=W) self.packageDateLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageDateLabel.grid(row=5, column=1, columnspan=5, sticky=W) ToolTip(self.packageDateLabel, text=_("Date of currently loaded package file (with parenthetical node when an update is available)."), wraplength=240) self.packageEnableButton = Button(packageInfoFrame, text=self.ENABLE, state=DISABLED, command=self.packageEnable) ToolTip(self.packageEnableButton, text=_("Enable/disable package."), wraplength=240) self.packageEnableButton.grid(row=6, column=1, sticky=E) self.packageMoveUpButton = Button(packageInfoFrame, text=_("Move Up"), state=DISABLED, command=self.packageMoveUp) ToolTip(self.packageMoveUpButton, text=_("Move package up (above other remappings)."), wraplength=240) self.packageMoveUpButton.grid(row=6, column=2, sticky=E) self.packageMoveDownButton = Button(packageInfoFrame, text=_("Move Down"), state=DISABLED, command=self.packageMoveDown) ToolTip(self.packageMoveDownButton, text=_("Move package down (below other remappings)."), wraplength=240) self.packageMoveDownButton.grid(row=6, column=3, sticky=E) self.packageReloadButton = Button(packageInfoFrame, text=_("Reload"), state=DISABLED, command=self.packageReload) ToolTip(self.packageReloadButton, text=_("Reload/update package."), wraplength=240) self.packageReloadButton.grid(row=6, column=4, sticky=E) self.packageRemoveButton = Button(packageInfoFrame, text=_("Remove"), state=DISABLED, command=self.packageRemove) ToolTip(self.packageRemoveButton, text=_("Remove package from packages table (does not erase the package file)."), wraplength=240) self.packageRemoveButton.grid(row=6, column=5, sticky=E) packageInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) packageInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=0) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.packagesView.get_children(""): self.packagesView.delete(previousNode) for i, packageInfo in enumerate(self.packagesConfig.get("packages", [])): name = packageInfo.get("name", "package{}".format(i)) node = self.packagesView.insert("", "end", "_{}".format(i), text=name) self.packagesView.set(node, "ver", packageInfo.get("version")) self.packagesView.set(node, "status", packageInfo.get("status")) self.packagesView.set(node, "date", packageInfo.get("fileDate")) if name in self.packageNamesWithNewerFileDates: self.packagesView.set(node, "update", _("available")) self.packagesView.set(node, "descr", packageInfo.get("description")) # clear previous treeview entries for previousNode in self.remappingsView.get_children(""): self.remappingsView.delete(previousNode) for i, remappingItem in enumerate(sorted(self.packagesConfig.get("remappings", {}).items())): prefix, remapping = remappingItem node = self.remappingsView.insert("", "end", prefix, text=prefix) self.remappingsView.set(node, "remapping", remapping) self.packageSelect() # clear out prior selection def ok(self, event=None): if self.packagesConfigChanged: PackageManager.packagesConfig = self.packagesConfig PackageManager.packagesConfigChanged = True self.cntlr.onPackageEnablementChanged() self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def packageSelect(self, *args): node = (self.packagesView.selection() or (None,))[0] try: nodeIndex = int(node[1:]) except (ValueError, TypeError): nodeIndex = -1 if 0 <= nodeIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][nodeIndex] self.selectedPackageIndex = nodeIndex name = packageInfo["name"] self.packageNameLabel.config(text=name) self.packageVersionHdr.config(state=ACTIVE) self.packageVersionLabel.config(text=packageInfo["version"]) self.packageDescrHdr.config(state=ACTIVE) self.packageDescrLabel.config(text=packageInfo["description"]) self.packagePrefixesHdr.config(state=ACTIVE) self.packagePrefixesLabel.config(text=', '.join(packageInfo["remappings"].keys())) self.packageUrlHdr.config(state=ACTIVE) self.packageUrlLabel.config(text=packageInfo["URL"]) self.packageDateHdr.config(state=ACTIVE) self.packageDateLabel.config(text=packageInfo["fileDate"] + " " + (_("(an update is available)") if name in self.packageNamesWithNewerFileDates else "")) self.packageEnableButton.config(state=ACTIVE, text={"enabled":self.DISABLE, "disabled":self.ENABLE}[packageInfo["status"]]) self.packageMoveUpButton.config(state=ACTIVE if 0 < nodeIndex else DISABLED) self.packageMoveDownButton.config(state=ACTIVE if nodeIndex < (len(self.packagesConfig["packages"]) - 1) else DISABLED) self.packageReloadButton.config(state=ACTIVE) self.packageRemoveButton.config(state=ACTIVE) else: self.selectedPackageIndex = -1 self.packageNameLabel.config(text="") self.packageVersionHdr.config(state=DISABLED) self.packageVersionLabel.config(text="") self.packageDescrHdr.config(state=DISABLED) self.packageDescrLabel.config(text="") self.packagePrefixesHdr.config(state=DISABLED) self.packagePrefixesLabel.config(text="") self.packageUrlHdr.config(state=DISABLED) self.packageUrlLabel.config(text="") self.packageDateHdr.config(state=DISABLED) self.packageDateLabel.config(text="") self.packageEnableButton.config(state=DISABLED, text=self.ENABLE) self.packageMoveUpButton.config(state=DISABLED) self.packageMoveDownButton.config(state=DISABLED) self.packageReloadButton.config(state=DISABLED) self.packageRemoveButton.config(state=DISABLED) def findLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory initialdir = self.cntlr.config.setdefault("packageOpenDir", initialdir) filename = self.cntlr.uiFileDialog("open", parent=self, title=_("Choose taxonomy package file"), initialdir=initialdir, filetypes=[(_("Taxonomy package files (*.zip)"), "*.zip"), (_("Manifest (*.taxonomyPackage.xml)"), "*.taxonomyPackage.xml"), (_("Oasis Catalog (*catalog.xml)"), "*catalog.xml")], defaultextension=".zip") if filename: # check if a package is selected (any file in a directory containing an __init__.py self.cntlr.config["packageOpenDir"] = os.path.dirname(filename) packageInfo = PackageManager.packageInfo(filename, packageManifestName=self.manifestNamePattern) self.loadFoundPackageInfo(packageInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file packageInfo = PackageManager.packageInfo(url, packageManifestName=self.manifestNamePattern) self.cntlr.showStatus("") # clear web loading status self.loadFoundPackageInfo(packageInfo, url) def manifestName(self): self.manifestNamePattern = simpledialog.askstring(_("Archive manifest file name pattern"), _("Provide non-standard archive manifest file name pattern (e.g., *taxonomyPackage.xml). \n" "Uses unix file name pattern matching. \n" "Multiple manifest files are supported in archive (such as oasis catalogs). \n" "(If blank, search for either .taxonomyPackage.xml or catalog.xml). "), initialvalue=self.manifestNamePattern, parent=self) def loadFoundPackageInfo(self, packageInfo, url): if packageInfo and packageInfo.get("name"): self.addPackageInfo(packageInfo) self.loadTreeViews() else: messagebox.showwarning(_("Package is not itself a taxonomy package. "), _("File does not itself contain a manifest file: \n\n{0}\n\n " "If opening an archive file, the manifest file search pattern currently is \"\", please press \"Manifest\" to change manifest file name pattern, e.g.,, \"*.taxonomyPackage.xml\", if needed. ") .format(url), parent=self) def removePackageInfo(self, name, version): # find package entry packagesList = self.packagesConfig["packages"] j = -1 for i, packageInfo in enumerate(packagesList): if packageInfo['name'] == name and packageInfo['version'] == version: j = i break if 0 <= j < len(packagesList): del self.packagesConfig["packages"][i] self.packagesConfigChanged = True def addPackageInfo(self, packageInfo): name = packageInfo["name"] version = packageInfo["version"] self.removePackageInfo(name, version) # remove any prior entry for this package self.packageNamesWithNewerFileDates.discard(name) # no longer has an update available self.packagesConfig["packages"].append(packageInfo) PackageManager.rebuildRemappings() self.packagesConfigChanged = True def packageEnable(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] if self.packageEnableButton['text'] == self.ENABLE: packageInfo["status"] = "enabled" self.packageEnableButton['text'] = self.DISABLE elif self.packageEnableButton['text'] == self.DISABLE: packageInfo["status"] = "disabled" self.packageEnableButton['text'] = self.ENABLE self.packagesConfigChanged = True PackageManager.rebuildRemappings() self.loadTreeViews() def packageMoveUp(self): if 1 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packages = self.packagesConfig["packages"] packageInfo = packages[self.selectedPackageIndex] del packages[self.selectedPackageIndex] packages.insert(self.selectedPackageIndex -1, packageInfo) self.packagesConfigChanged = True PackageManager.rebuildRemappings() self.loadTreeViews() def packageMoveDown(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]) - 1: packages = self.packagesConfig["packages"] packageInfo = packages[self.selectedPackageIndex] del packages[self.selectedPackageIndex] packages.insert(self.selectedPackageIndex + 1, packageInfo) self.packagesConfigChanged = True PackageManager.rebuildRemappings() self.loadTreeViews() def packageReload(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] url = packageInfo.get("URL") if url: packageInfo = PackageManager.packageInfo(url, reload=True, packageManifestName=packageInfo.get("manifestName")) if packageInfo: self.addPackageInfo(packageInfo) PackageManager.rebuildRemappings() self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format(packageInfo.get("name")), clearAfter=5000) else: messagebox.showwarning(_("Package error"), _("File or package cannot be reloaded: \n\n{0}") .format(url), parent=self) def packageRemove(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] self.removePackageInfo(packageInfo["name"], packageInfo["version"]) self.packagesConfigChanged = True PackageManager.rebuildRemappings() self.loadTreeViews()
class DialogPluginManager(Toplevel): def __init__(self, mainWin, modulesWithNewerFileDates): super(DialogPluginManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.pluginConfig = PluginManager.pluginConfig self.pluginConfigChanged = False self.uiClassMethodsChanged = False self.modelClassesChanged = False self.disclosureSystemTypesChanged = False self.modulesWithNewerFileDates = modulesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Plug-in Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find plug-in modules:"), wraplength=60, justify="center") addLocalButton = Button(buttonFrame, text=_("Locally"), command=self.findLocally) ToolTip(addLocalButton, text=_("File chooser allows selecting python module files to add (or reload) plug-ins, from the local file system."), wraplength=240) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip(addWebButton, text=_("Dialog to enter URL full path to load (or reload) plug-ins, from the web or local file system."), wraplength=240) addLabel.grid(row=0, column=0, pady=4) addLocalButton.grid(row=1, column=0, pady=4) addWebButton.grid(row=2, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=2, sticky=(N, S, W), padx=3, pady=3) # right tree frame (plugins already known to arelle) modulesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(modulesFrame, orient=VERTICAL) hScrollbar = Scrollbar(modulesFrame, orient=HORIZONTAL) self.modulesView = Treeview(modulesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.modulesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.modulesView.bind('<<TreeviewSelect>>', self.moduleSelect) hScrollbar["command"] = self.modulesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.modulesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) modulesFrame.columnconfigure(0, weight=1) modulesFrame.rowconfigure(0, weight=1) modulesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.modulesView.focus_set() self.modulesView.column("#0", width=120, anchor="w") self.modulesView.heading("#0", text=_("Name")) self.modulesView["columns"] = ("author", "ver", "status", "date", "update", "descr", "license") self.modulesView.column("author", width=100, anchor="w", stretch=False) self.modulesView.heading("author", text=_("Author")) self.modulesView.column("ver", width=50, anchor="w", stretch=False) self.modulesView.heading("ver", text=_("Version")) self.modulesView.column("status", width=50, anchor="w", stretch=False) self.modulesView.heading("status", text=_("Status")) self.modulesView.column("date", width=70, anchor="w", stretch=False) self.modulesView.heading("date", text=_("File Date")) self.modulesView.column("update", width=50, anchor="w", stretch=False) self.modulesView.heading("update", text=_("Update")) self.modulesView.column("descr", width=200, anchor="w", stretch=False) self.modulesView.heading("descr", text=_("Description")) self.modulesView.column("license", width=70, anchor="w", stretch=False) self.modulesView.heading("license", text=_("License")) classesFrame = Frame(frame) vScrollbar = Scrollbar(classesFrame, orient=VERTICAL) hScrollbar = Scrollbar(classesFrame, orient=HORIZONTAL) self.classesView = Treeview(classesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.classesView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.classesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.classesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) classesFrame.columnconfigure(0, weight=1) classesFrame.rowconfigure(0, weight=1) classesFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.classesView.focus_set() self.classesView.column("#0", width=200, anchor="w") self.classesView.heading("#0", text=_("Class")) self.classesView["columns"] = ("modules",) self.classesView.column("modules", width=500, anchor="w", stretch=False) self.classesView.heading("modules", text=_("Modules")) # bottom frame module info details moduleInfoFrame = Frame(frame, width=700) moduleInfoFrame.columnconfigure(1, weight=1) self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.moduleNameLabel.grid(row=0, column=0, columnspan=4, sticky=W) self.moduleAuthorHdr = Label(moduleInfoFrame, text=_("author:"), state=DISABLED) self.moduleAuthorHdr.grid(row=1, column=0, sticky=W) self.moduleAuthorLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleAuthorLabel.grid(row=1, column=1, columnspan=3, sticky=W) self.moduleDescrHdr = Label(moduleInfoFrame, text=_("description:"), state=DISABLED) self.moduleDescrHdr.grid(row=2, column=0, sticky=W) self.moduleDescrLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDescrLabel.grid(row=2, column=1, columnspan=3, sticky=W) self.moduleClassesHdr = Label(moduleInfoFrame, text=_("classes:"), state=DISABLED) self.moduleClassesHdr.grid(row=3, column=0, sticky=W) self.moduleClassesLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleClassesLabel.grid(row=3, column=1, columnspan=3, sticky=W) ToolTip(self.moduleClassesLabel, text=_("List of classes that this plug-in handles."), wraplength=240) self.moduleUrlHdr = Label(moduleInfoFrame, text=_("URL:"), state=DISABLED) self.moduleUrlHdr.grid(row=4, column=0, sticky=W) self.moduleUrlLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleUrlLabel.grid(row=4, column=1, columnspan=3, sticky=W) ToolTip(self.moduleUrlLabel, text=_("URL of plug-in module (local file path or web loaded file)."), wraplength=240) self.moduleDateHdr = Label(moduleInfoFrame, text=_("date:"), state=DISABLED) self.moduleDateHdr.grid(row=5, column=0, sticky=W) self.moduleDateLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDateLabel.grid(row=5, column=1, columnspan=3, sticky=W) ToolTip(self.moduleDateLabel, text=_("Date of currently loaded module file (with parenthetical node when an update is available)."), wraplength=240) self.moduleLicenseHdr = Label(moduleInfoFrame, text=_("license:"), state=DISABLED) self.moduleLicenseHdr.grid(row=6, column=0, sticky=W) self.moduleLicenseLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleLicenseLabel.grid(row=6, column=1, columnspan=3, sticky=W) self.moduleEnableButton = Button(moduleInfoFrame, text=self.ENABLE, state=DISABLED, command=self.moduleEnable) ToolTip(self.moduleEnableButton, text=_("Enable/disable plug in."), wraplength=240) self.moduleEnableButton.grid(row=7, column=1, sticky=E) self.moduleReloadButton = Button(moduleInfoFrame, text=_("Reload"), state=DISABLED, command=self.moduleReload) ToolTip(self.moduleReloadButton, text=_("Reload/update plug in."), wraplength=240) self.moduleReloadButton.grid(row=7, column=2, sticky=E) self.moduleRemoveButton = Button(moduleInfoFrame, text=_("Remove"), state=DISABLED, command=self.moduleRemove) ToolTip(self.moduleRemoveButton, text=_("Remove plug in from plug in table (does not erase the plug in's file)."), wraplength=240) self.moduleRemoveButton.grid(row=7, column=3, sticky=E) moduleInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) moduleInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S,W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) ToolTip(enableAllButton, text=_("Enable all plug ins."), wraplength=240) disableAllButton = Button(enableDisableFrame, text=_("Disable All"), command=self.disableAll) ToolTip(disableAllButton, text=_("Disable all plug ins."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=0) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.modulesView.get_children(""): self.modulesView.delete(previousNode) for i, moduleItem in enumerate(sorted(self.pluginConfig.get("modules", {}).items())): moduleInfo = moduleItem[1] name = moduleInfo.get("name", moduleItem[0]) node = self.modulesView.insert("", "end", name, text=name) self.modulesView.set(node, "author", moduleInfo.get("author")) self.modulesView.set(node, "ver", moduleInfo.get("version")) self.modulesView.set(node, "status", moduleInfo.get("status")) self.modulesView.set(node, "date", moduleInfo.get("fileDate")) if name in self.modulesWithNewerFileDates: self.modulesView.set(node, "update", _("available")) self.modulesView.set(node, "descr", moduleInfo.get("description")) self.modulesView.set(node, "license", moduleInfo.get("license")) # clear previous treeview entries for previousNode in self.classesView.get_children(""): self.classesView.delete(previousNode) for i, classItem in enumerate(sorted(self.pluginConfig.get("classes", {}).items())): className, moduleList = classItem node = self.classesView.insert("", "end", className, text=className) self.classesView.set(node, "modules", ', '.join(moduleList)) self.moduleSelect() # clear out prior selection def ok(self, event=None): if self.pluginConfigChanged: PluginManager.pluginConfig = self.pluginConfig PluginManager.pluginConfigChanged = True PluginManager.reset() # force reloading of modules if self.uiClassMethodsChanged or self.modelClassesChanged or self.disclosureSystemTypesChanged: # may require reloading UI affectedItems = "" if self.uiClassMethodsChanged: affectedItems += _("menus of the user interface") if self.modelClassesChanged: if self.uiClassMethodsChanged: affectedItems += _(" and ") affectedItems += _("model objects of the processor") if (self.uiClassMethodsChanged or self.modelClassesChanged): affectedItems += _(" and ") if self.disclosureSystemTypesChanged: if (self.uiClassMethodsChanged or self.modelClassesChanged): affectedItems += _(" and ") affectedItems += _("disclosure system types") if messagebox.askyesno(_("User interface plug-in change"), _("A change in plug-in class methods may have affected {0}. " "Please restart Arelle to due to these changes. \n\n" "Should Arelle restart itself now " "(if there are any unsaved changes they would be lost!)?" ).format(affectedItems), parent=self): self.cntlr.uiThreadQueue.put((self.cntlr.quit, [None, True])) self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def moduleSelect(self, *args): node = (self.modulesView.selection() or (None,))[0] moduleInfo = self.pluginConfig.get("modules", {}).get(node) if moduleInfo: self.selectedModule = node name = moduleInfo["name"] self.moduleNameLabel.config(text=name) self.moduleAuthorHdr.config(state=ACTIVE) self.moduleAuthorLabel.config(text=moduleInfo["author"]) self.moduleDescrHdr.config(state=ACTIVE) self.moduleDescrLabel.config(text=moduleInfo["description"]) self.moduleClassesHdr.config(state=ACTIVE) self.moduleClassesLabel.config(text=', '.join(moduleInfo["classMethods"])) self.moduleUrlHdr.config(state=ACTIVE) self.moduleUrlLabel.config(text=moduleInfo["moduleURL"]) self.moduleDateHdr.config(state=ACTIVE) self.moduleDateLabel.config(text=moduleInfo["fileDate"] + " " + (_("(an update is available)") if name in self.modulesWithNewerFileDates else "")) self.moduleLicenseHdr.config(state=ACTIVE) self.moduleLicenseLabel.config(text=moduleInfo["license"]) self.moduleEnableButton.config(state=ACTIVE, text={"enabled":self.DISABLE, "disabled":self.ENABLE}[moduleInfo["status"]]) self.moduleReloadButton.config(state=ACTIVE) self.moduleRemoveButton.config(state=ACTIVE) else: self.selectedModule = None self.moduleNameLabel.config(text="") self.moduleAuthorHdr.config(state=DISABLED) self.moduleAuthorLabel.config(text="") self.moduleDescrHdr.config(state=DISABLED) self.moduleDescrLabel.config(text="") self.moduleClassesHdr.config(state=DISABLED) self.moduleClassesLabel.config(text="") self.moduleUrlHdr.config(state=DISABLED) self.moduleUrlLabel.config(text="") self.moduleDateHdr.config(state=DISABLED) self.moduleDateLabel.config(text="") self.moduleLicenseHdr.config(state=DISABLED) self.moduleLicenseLabel.config(text="") self.moduleEnableButton.config(state=DISABLED, text=self.ENABLE) self.moduleReloadButton.config(state=DISABLED) self.moduleRemoveButton.config(state=DISABLED) def findLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory initialdir = self.cntlr.config.setdefault("pluginOpenDir", initialdir) filename = self.cntlr.uiFileDialog("open", parent=self, title=_("Choose plug-in module file"), initialdir=initialdir, filetypes=[(_("Python files"), "*.py")], defaultextension=".py") if filename: # check if a package is selected (any file in a directory containing an __init__.py if (os.path.isdir(os.path.dirname(filename)) and os.path.isfile(os.path.join(os.path.dirname(filename), "__init__.py"))): filename = os.path.dirname(filename) # refer to the package instead self.cntlr.config["pluginOpenDir"] = os.path.dirname(filename) moduleInfo = PluginManager.moduleModuleInfo(filename) self.loadFoundModuleInfo(moduleInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file moduleInfo = PluginManager.moduleModuleInfo(url) self.cntlr.showStatus("") # clear web loading status self.loadFoundModuleInfo(moduleInfo, url) def loadFoundModuleInfo(self, moduleInfo, url): if moduleInfo and moduleInfo.get("name"): self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() else: messagebox.showwarning(_("Module is not itself a plug-in or in a directory with package __init__.py plug-in. "), _("File does not itself contain a python program with an appropriate __pluginInfo__ declaration: \n\n{0}") .format(url), parent=self) def removePluginConfigModuleInfo(self, name): moduleInfo = self.pluginConfig["modules"].get(name) if moduleInfo: for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].get(classMethod) if classMethods and name in classMethods: classMethods.remove(name) if not classMethods: # list has become unused del self.pluginConfig["classes"][classMethod] # remove class if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI elif classMethod == "ModelObjectFactory.ElementSubstitutionClasses": self.modelClassesChanged = True # model object factor classes changed elif classMethod == "DisclosureSystem.Types": self.disclosureSystemTypesChanged = True # disclosure system types changed del self.pluginConfig["modules"][name] self.pluginConfigChanged = True def addPluginConfigModuleInfo(self, moduleInfo): name = moduleInfo["name"] self.removePluginConfigModuleInfo(name) # remove any prior entry for this module self.modulesWithNewerFileDates.discard(name) # no longer has an update available self.pluginConfig["modules"][name] = moduleInfo # add classes for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].setdefault(classMethod, []) if name not in classMethods: classMethods.append(name) if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI elif classMethod == "ModelObjectFactory.ElementSubstitutionClasses": self.modelClassesChanged = True # model object factor classes changed elif classMethod == "DisclosureSystem.Types": self.disclosureSystemTypesChanged = True # disclosure system types changed self.pluginConfigChanged = True def moduleEnable(self): if self.selectedModule in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][self.selectedModule] if self.moduleEnableButton['text'] == self.ENABLE: moduleInfo["status"] = "enabled" self.moduleEnableButton['text'] = self.DISABLE elif self.moduleEnableButton['text'] == self.DISABLE: moduleInfo["status"] = "disabled" self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() def moduleReload(self): if self.selectedModule in self.pluginConfig["modules"]: url = self.pluginConfig["modules"][self.selectedModule].get("moduleURL") if url: moduleInfo = PluginManager.moduleModuleInfo(url, reload=True) if moduleInfo: self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format(moduleInfo.get("name")), clearAfter=5000) else: messagebox.showwarning(_("Module error"), _("File or module cannot be reloaded: \n\n{0}") .format(url), parent=self) def moduleRemove(self): if self.selectedModule in self.pluginConfig["modules"]: self.removePluginConfigModuleInfo(self.selectedModule) self.pluginConfigChanged = True self.loadTreeViews() def enableAll(self): self.enableDisableAll(True) def disableAll(self): self.enableDisableAll(False) def enableDisableAll(self, doEnable): for module in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][module] if doEnable: moduleInfo["status"] = "enabled" self.moduleEnableButton['text'] = self.DISABLE else: moduleInfo["status"] = "disabled" self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews()
class DialogPluginManager(Toplevel): def __init__(self, mainWin, modulesWithNewerFileDates): super(DialogPluginManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.pluginConfig = PluginManager.pluginConfig self.pluginConfigChanged = False self.uiClassMethodsChanged = False self.modulesWithNewerFileDates = modulesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Plug-in Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find plug-in modules:"), wraplength=60, justify="center") addLocalButton = Button(buttonFrame, text=_("Locally"), command=self.findLocally) ToolTip(addLocalButton, text=_("File chooser allows selecting python module files to add (or reload) plug-ins, from the local file system."), wraplength=240) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip(addWebButton, text=_("Dialog to enter URL full path to load (or reload) plug-ins, from the web or local file system."), wraplength=240) addLabel.grid(row=0, column=0, pady=4) addLocalButton.grid(row=1, column=0, pady=4) addWebButton.grid(row=2, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=2, sticky=(N, S, W), padx=3, pady=3) # right tree frame (plugins already known to arelle) modulesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(modulesFrame, orient=VERTICAL) hScrollbar = Scrollbar(modulesFrame, orient=HORIZONTAL) self.modulesView = Treeview(modulesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.modulesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.modulesView.bind('<<TreeviewSelect>>', self.moduleSelect) hScrollbar["command"] = self.modulesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.modulesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) modulesFrame.columnconfigure(0, weight=1) modulesFrame.rowconfigure(0, weight=1) modulesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.modulesView.focus_set() self.modulesView.column("#0", width=120, anchor="w") self.modulesView.heading("#0", text=_("Name")) self.modulesView["columns"] = ("author", "ver", "status", "date", "update", "descr", "license") self.modulesView.column("author", width=100, anchor="w", stretch=False) self.modulesView.heading("author", text=_("Author")) self.modulesView.column("ver", width=50, anchor="w", stretch=False) self.modulesView.heading("ver", text=_("Version")) self.modulesView.column("status", width=50, anchor="w", stretch=False) self.modulesView.heading("status", text=_("Status")) self.modulesView.column("date", width=70, anchor="w", stretch=False) self.modulesView.heading("date", text=_("File Date")) self.modulesView.column("update", width=50, anchor="w", stretch=False) self.modulesView.heading("update", text=_("Update")) self.modulesView.column("descr", width=200, anchor="w", stretch=False) self.modulesView.heading("descr", text=_("Description")) self.modulesView.column("license", width=70, anchor="w", stretch=False) self.modulesView.heading("license", text=_("License")) classesFrame = Frame(frame) vScrollbar = Scrollbar(classesFrame, orient=VERTICAL) hScrollbar = Scrollbar(classesFrame, orient=HORIZONTAL) self.classesView = Treeview(classesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.classesView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.classesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.classesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) classesFrame.columnconfigure(0, weight=1) classesFrame.rowconfigure(0, weight=1) classesFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.classesView.focus_set() self.classesView.column("#0", width=200, anchor="w") self.classesView.heading("#0", text=_("Class")) self.classesView["columns"] = ("modules") self.classesView.column("modules", width=500, anchor="w", stretch=False) self.classesView.heading("modules", text=_("Modules")) # bottom frame module info details moduleInfoFrame = Frame(frame, width=700) moduleInfoFrame.columnconfigure(1, weight=1) self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.moduleNameLabel.grid(row=0, column=0, columnspan=4, sticky=W) self.moduleAuthorHdr = Label(moduleInfoFrame, text=_("author:"), state=DISABLED) self.moduleAuthorHdr.grid(row=1, column=0, sticky=W) self.moduleAuthorLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleAuthorLabel.grid(row=1, column=1, columnspan=3, sticky=W) self.moduleDescrHdr = Label(moduleInfoFrame, text=_("description:"), state=DISABLED) self.moduleDescrHdr.grid(row=2, column=0, sticky=W) self.moduleDescrLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDescrLabel.grid(row=2, column=1, columnspan=3, sticky=W) self.moduleClassesHdr = Label(moduleInfoFrame, text=_("classes:"), state=DISABLED) self.moduleClassesHdr.grid(row=3, column=0, sticky=W) self.moduleClassesLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleClassesLabel.grid(row=3, column=1, columnspan=3, sticky=W) ToolTip(self.moduleClassesLabel, text=_("List of classes that this plug-in handles."), wraplength=240) self.moduleUrlHdr = Label(moduleInfoFrame, text=_("URL:"), state=DISABLED) self.moduleUrlHdr.grid(row=4, column=0, sticky=W) self.moduleUrlLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleUrlLabel.grid(row=4, column=1, columnspan=3, sticky=W) ToolTip(self.moduleUrlLabel, text=_("URL of plug-in module (local file path or web loaded file)."), wraplength=240) self.moduleDateHdr = Label(moduleInfoFrame, text=_("date:"), state=DISABLED) self.moduleDateHdr.grid(row=5, column=0, sticky=W) self.moduleDateLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDateLabel.grid(row=5, column=1, columnspan=3, sticky=W) ToolTip(self.moduleDateLabel, text=_("Date of currently loaded module file (with parenthetical node when an update is available)."), wraplength=240) self.moduleLicenseHdr = Label(moduleInfoFrame, text=_("license:"), state=DISABLED) self.moduleLicenseHdr.grid(row=6, column=0, sticky=W) self.moduleLicenseLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleLicenseLabel.grid(row=6, column=1, columnspan=3, sticky=W) self.moduleEnableButton = Button(moduleInfoFrame, text=self.ENABLE, state=DISABLED, command=self.moduleEnable) ToolTip(self.moduleEnableButton, text=_("Enable/disable plug in."), wraplength=240) self.moduleEnableButton.grid(row=7, column=1, sticky=E) self.moduleReloadButton = Button(moduleInfoFrame, text=_("Reload"), state=DISABLED, command=self.moduleReload) ToolTip(self.moduleReloadButton, text=_("Reload/update plug in."), wraplength=240) self.moduleReloadButton.grid(row=7, column=2, sticky=E) self.moduleRemoveButton = Button(moduleInfoFrame, text=_("Remove"), state=DISABLED, command=self.moduleRemove) ToolTip(self.moduleRemoveButton, text=_("Remove plug in from plug in table (does not erase the plug in's file)."), wraplength=240) self.moduleRemoveButton.grid(row=7, column=3, sticky=E) moduleInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) moduleInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) self.loadTreeViews() frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.modulesView.get_children(""): self.modulesView.delete(previousNode) for i, moduleItem in enumerate(sorted(self.pluginConfig.get("modules", {}).items())): moduleInfo = moduleItem[1] name = moduleInfo.get("name", moduleItem[0]) node = self.modulesView.insert("", "end", name, text=name) self.modulesView.set(node, "author", moduleInfo.get("author")) self.modulesView.set(node, "ver", moduleInfo.get("version")) self.modulesView.set(node, "status", moduleInfo.get("status")) self.modulesView.set(node, "date", moduleInfo.get("fileDate")) if name in self.modulesWithNewerFileDates: self.modulesView.set(node, "update", _("available")) self.modulesView.set(node, "descr", moduleInfo.get("description")) self.modulesView.set(node, "license", moduleInfo.get("license")) # clear previous treeview entries for previousNode in self.classesView.get_children(""): self.classesView.delete(previousNode) for i, classItem in enumerate(sorted(self.pluginConfig.get("classes", {}).items())): className, moduleList = classItem node = self.classesView.insert("", "end", className, text=className) self.classesView.set(node, "modules", ', '.join(moduleList)) self.moduleSelect() # clear out prior selection def ok(self, event=None): if self.pluginConfigChanged: PluginManager.pluginConfig = self.pluginConfig PluginManager.pluginConfigChanged = True PluginManager.reset() # force reloading of modules if self.uiClassMethodsChanged: # may require reloading UI if messagebox.askyesno(_("User interface plug-in change"), _("A change in plug-in class methods may have affected the menus " "of the user interface. It may be necessary to restart Arelle to " "access the menu entries or the changes to their plug-in methods. \n\n" "Should Arelle restart with changed user interface language, " "(if there are any unsaved changes they would be lost!)?"), parent=self): self.cntlr.uiThreadQueue.put((self.cntlr.quit, [None, True])) self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def moduleSelect(self, *args): node = (self.modulesView.selection() or (None,))[0] moduleInfo = self.pluginConfig.get("modules", {}).get(node) if moduleInfo: self.selectedModule = node name = moduleInfo["name"] self.moduleNameLabel.config(text=name) self.moduleAuthorHdr.config(state=ACTIVE) self.moduleAuthorLabel.config(text=moduleInfo["author"]) self.moduleDescrHdr.config(state=ACTIVE) self.moduleDescrLabel.config(text=moduleInfo["description"]) self.moduleClassesHdr.config(state=ACTIVE) self.moduleClassesLabel.config(text=', '.join(moduleInfo["classMethods"])) self.moduleUrlHdr.config(state=ACTIVE) self.moduleUrlLabel.config(text=moduleInfo["moduleURL"]) self.moduleDateHdr.config(state=ACTIVE) self.moduleDateLabel.config(text=moduleInfo["fileDate"] + " " + (_("(an update is available)") if name in self.modulesWithNewerFileDates else "")) self.moduleLicenseHdr.config(state=ACTIVE) self.moduleLicenseLabel.config(text=moduleInfo["license"]) self.moduleEnableButton.config(state=ACTIVE, text={"enabled":self.DISABLE, "disabled":self.ENABLE}[moduleInfo["status"]]) self.moduleReloadButton.config(state=ACTIVE) self.moduleRemoveButton.config(state=ACTIVE) else: self.selectedModule = None self.moduleNameLabel.config(text="") self.moduleAuthorHdr.config(state=DISABLED) self.moduleAuthorLabel.config(text="") self.moduleDescrHdr.config(state=DISABLED) self.moduleDescrLabel.config(text="") self.moduleClassesHdr.config(state=DISABLED) self.moduleClassesLabel.config(text="") self.moduleUrlHdr.config(state=DISABLED) self.moduleUrlLabel.config(text="") self.moduleDateHdr.config(state=DISABLED) self.moduleDateLabel.config(text="") self.moduleLicenseHdr.config(state=DISABLED) self.moduleLicenseLabel.config(text="") self.moduleEnableButton.config(state=DISABLED, text=self.ENABLE) self.moduleReloadButton.config(state=DISABLED) self.moduleRemoveButton.config(state=DISABLED) def findLocally(self): filename = self.cntlr.uiFileDialog("open", owner=self, title=_("Choose plug-in module file"), initialdir=self.cntlr.config.setdefault("pluginOpenDir","."), filetypes=[(_("Python files"), "*.py")], defaultextension=".py") if filename: self.cntlr.config["pluginOpenDir"] = os.path.dirname(filename) moduleInfo = PluginManager.moduleModuleInfo(filename) self.loadFoundModuleInfo(moduleInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file moduleInfo = PluginManager.moduleModuleInfo(url) self.cntlr.showStatus("") # clear web loading status self.loadFoundModuleInfo(moduleInfo, url) def loadFoundModuleInfo(self, moduleInfo, url): if moduleInfo and moduleInfo.get("name"): self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() else: messagebox.showwarning(_("Module is not a plug-in"), _("File does not contain a python program with an appropriate __pluginInfo__ declaration: \n\n{0}") .format(url), parent=self) def removePluginConfigModuleInfo(self, name): moduleInfo = self.pluginConfig["modules"].get(name) if moduleInfo: for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].get(classMethod) if classMethods and name in classMethods: classMethods.remove(name) if not classMethods: # list has become unused del self.pluginConfig["classes"][classMethod] # remove class if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI del self.pluginConfig["modules"][name] self.pluginConfigChanged = True def addPluginConfigModuleInfo(self, moduleInfo): name = moduleInfo["name"] self.removePluginConfigModuleInfo(name) # remove any prior entry for this module self.modulesWithNewerFileDates.discard(name) # no longer has an update available self.pluginConfig["modules"][name] = moduleInfo # add classes for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].setdefault(classMethod, []) if name not in classMethods: classMethods.append(name) if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI self.pluginConfigChanged = True def moduleEnable(self): if self.selectedModule in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][self.selectedModule] if self.moduleEnableButton['text'] == self.ENABLE: moduleInfo["status"] = "enabled" self.moduleEnableButton['text'] = self.DISABLE elif self.moduleEnableButton['text'] == self.DISABLE: moduleInfo["status"] = "disabled" self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() def moduleReload(self): if self.selectedModule in self.pluginConfig["modules"]: url = self.pluginConfig["modules"][self.selectedModule].get("moduleURL") if url: moduleInfo = PluginManager.moduleModuleInfo(url, reload=True) if moduleInfo: self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format(moduleInfo.get("name")), clearAfter=5000) else: messagebox.showwarning(_("Module error"), _("File or module cannot be reloaded: \n\n{0}") .format(url), parent=self) def moduleRemove(self): if self.selectedModule in self.pluginConfig["modules"]: self.removePluginConfigModuleInfo(self.selectedModule) self.pluginConfigChanged = True self.loadTreeViews()
class DialogPluginManager(Toplevel): def __init__(self, mainWin, modulesWithNewerFileDates): super(DialogPluginManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.pluginConfig = PluginManager.pluginConfig self.pluginConfigChanged = False self.uiClassMethodsChanged = False self.modelClassesChanged = False self.customTransformsChanged = False self.disclosureSystemTypesChanged = False self.hostSystemFeaturesChanged = False self.modulesWithNewerFileDates = modulesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Plug-in Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find plug-in modules:"), wraplength=60, justify="center") addSelectLocalButton = Button(buttonFrame, text=_("Select"), command=self.selectLocally) ToolTip( addSelectLocalButton, text=_( "Select python module files from the local plugin directory."), wraplength=240) addBrowseLocalButton = Button(buttonFrame, text=_("Browse"), command=self.browseLocally) ToolTip( addBrowseLocalButton, text= _("File chooser allows browsing and selecting python module files to add (or reload) plug-ins, from the local file system." ), wraplength=240) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip( addWebButton, text= _("Dialog to enter URL full path to load (or reload) plug-ins, from the web or local file system." ), wraplength=240) addLabel.grid(row=0, column=0, pady=4) addSelectLocalButton.grid(row=1, column=0, pady=4) addBrowseLocalButton.grid(row=2, column=0, pady=4) addWebButton.grid(row=3, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=3, sticky=(N, S, W), padx=3, pady=3) # right tree frame (plugins already known to arelle) modulesFrame = Frame(frame, width=720) vScrollbar = Scrollbar(modulesFrame, orient=VERTICAL) hScrollbar = Scrollbar(modulesFrame, orient=HORIZONTAL) self.modulesView = Treeview(modulesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.modulesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.modulesView.bind('<<TreeviewSelect>>', self.moduleSelect) hScrollbar["command"] = self.modulesView.xview hScrollbar.grid(row=1, column=0, sticky=(E, W)) vScrollbar["command"] = self.modulesView.yview vScrollbar.grid(row=0, column=1, sticky=(N, S)) modulesFrame.columnconfigure(0, weight=1) modulesFrame.rowconfigure(0, weight=1) modulesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.modulesView.focus_set() self.modulesView.column("#0", width=120, anchor="w") self.modulesView.heading("#0", text=_("Name")) self.modulesView["columns"] = ("author", "ver", "status", "date", "update", "descr", "license") self.modulesView.column("author", width=100, anchor="w", stretch=False) self.modulesView.heading("author", text=_("Author")) self.modulesView.column("ver", width=60, anchor="w", stretch=False) self.modulesView.heading("ver", text=_("Version")) self.modulesView.column("status", width=50, anchor="w", stretch=False) self.modulesView.heading("status", text=_("Status")) self.modulesView.column("date", width=70, anchor="w", stretch=False) self.modulesView.heading("date", text=_("File Date")) self.modulesView.column("update", width=50, anchor="w", stretch=False) self.modulesView.heading("update", text=_("Update")) self.modulesView.column("descr", width=200, anchor="w", stretch=False) self.modulesView.heading("descr", text=_("Description")) self.modulesView.column("license", width=70, anchor="w", stretch=False) self.modulesView.heading("license", text=_("License")) classesFrame = Frame(frame) vScrollbar = Scrollbar(classesFrame, orient=VERTICAL) hScrollbar = Scrollbar(classesFrame, orient=HORIZONTAL) self.classesView = Treeview(classesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.classesView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.classesView.xview hScrollbar.grid(row=1, column=0, sticky=(E, W)) vScrollbar["command"] = self.classesView.yview vScrollbar.grid(row=0, column=1, sticky=(N, S)) classesFrame.columnconfigure(0, weight=1) classesFrame.rowconfigure(0, weight=1) classesFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.classesView.focus_set() self.classesView.column("#0", width=200, anchor="w") self.classesView.heading("#0", text=_("Class")) self.classesView["columns"] = ("modules", ) self.classesView.column("modules", width=500, anchor="w", stretch=False) self.classesView.heading("modules", text=_("Modules")) # bottom frame module info details moduleInfoFrame = Frame(frame, width=700) moduleInfoFrame.columnconfigure(1, weight=1) self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.moduleNameLabel.grid(row=0, column=0, columnspan=4, sticky=W) self.moduleAuthorHdr = Label(moduleInfoFrame, text=_("author:"), state=DISABLED) self.moduleAuthorHdr.grid(row=1, column=0, sticky=W) self.moduleAuthorLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleAuthorLabel.grid(row=1, column=1, columnspan=3, sticky=W) self.moduleDescrHdr = Label(moduleInfoFrame, text=_("description:"), state=DISABLED) self.moduleDescrHdr.grid(row=2, column=0, sticky=W) self.moduleDescrLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDescrLabel.grid(row=2, column=1, columnspan=3, sticky=W) self.moduleClassesHdr = Label(moduleInfoFrame, text=_("classes:"), state=DISABLED) self.moduleClassesHdr.grid(row=3, column=0, sticky=W) self.moduleClassesLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleClassesLabel.grid(row=3, column=1, columnspan=3, sticky=W) ToolTip(self.moduleClassesLabel, text=_("List of classes that this plug-in handles."), wraplength=240) self.moduleVersionHdr = Label(moduleInfoFrame, text=_("version:"), state=DISABLED) self.moduleVersionHdr.grid(row=4, column=0, sticky=W) self.moduleVersionLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleVersionLabel.grid(row=4, column=1, columnspan=3, sticky=W) ToolTip(self.moduleVersionLabel, text=_("Version of plug-in module."), wraplength=240) self.moduleUrlHdr = Label(moduleInfoFrame, text=_("URL:"), state=DISABLED) self.moduleUrlHdr.grid(row=5, column=0, sticky=W) self.moduleUrlLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleUrlLabel.grid(row=5, column=1, columnspan=3, sticky=W) ToolTip( self.moduleUrlLabel, text=_( "URL of plug-in module (local file path or web loaded file)."), wraplength=240) self.moduleDateHdr = Label(moduleInfoFrame, text=_("date:"), state=DISABLED) self.moduleDateHdr.grid(row=6, column=0, sticky=W) self.moduleDateLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDateLabel.grid(row=6, column=1, columnspan=3, sticky=W) ToolTip( self.moduleDateLabel, text= _("Date of currently loaded module file (with parenthetical node when an update is available)." ), wraplength=240) self.moduleLicenseHdr = Label(moduleInfoFrame, text=_("license:"), state=DISABLED) self.moduleLicenseHdr.grid(row=7, column=0, sticky=W) self.moduleLicenseLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleLicenseLabel.grid(row=7, column=1, columnspan=3, sticky=W) self.moduleImportsHdr = Label(moduleInfoFrame, text=_("imports:"), state=DISABLED) self.moduleImportsHdr.grid(row=8, column=0, sticky=W) self.moduleImportsLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleImportsLabel.grid(row=8, column=1, columnspan=3, sticky=W) self.moduleEnableButton = Button(moduleInfoFrame, text=self.ENABLE, state=DISABLED, command=self.moduleEnable) ToolTip(self.moduleEnableButton, text=_("Enable/disable plug in."), wraplength=240) self.moduleEnableButton.grid(row=9, column=1, sticky=E) self.moduleReloadButton = Button(moduleInfoFrame, text=_("Reload"), state=DISABLED, command=self.moduleReload) ToolTip(self.moduleReloadButton, text=_("Reload/update plug in."), wraplength=240) self.moduleReloadButton.grid(row=9, column=2, sticky=E) self.moduleRemoveButton = Button(moduleInfoFrame, text=_("Remove"), state=DISABLED, command=self.moduleRemove) ToolTip( self.moduleRemoveButton, text= _("Remove plug in from plug in table (does not erase the plug in's file)." ), wraplength=240) self.moduleRemoveButton.grid(row=9, column=3, sticky=E) moduleInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) moduleInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S, E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S, E), pady=3, padx=3) enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S, W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) ToolTip(enableAllButton, text=_("Enable all plug ins."), wraplength=240) disableAllButton = Button(enableDisableFrame, text=_("Disable All"), command=self.disableAll) ToolTip(disableAllButton, text=_("Disable all plug ins."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX + 50, dialogY + 100)) frame.grid(row=0, column=0, sticky=(N, S, E, W)) frame.columnconfigure(0, weight=0) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.modulesView.get_children(""): self.modulesView.delete(previousNode) def loadSubtree(parentNode, moduleItems): for moduleItem in sorted(moduleItems, key=lambda item: item[0]): moduleInfo = moduleItem[1] if parentNode or not moduleInfo.get("isImported"): nodeName = moduleItem[0] if parentNode: nodeName = parentNode + GROUPSEP + nodeName name = moduleInfo.get("name", nodeName) node = self.modulesView.insert(parentNode, "end", nodeName, text=name) self.modulesView.set(node, "author", moduleInfo.get("author")) self.modulesView.set(node, "ver", moduleInfo.get("version")) self.modulesView.set(node, "status", moduleInfo.get("status")) self.modulesView.set(node, "date", moduleInfo.get("fileDate")) if name in self.modulesWithNewerFileDates: self.modulesView.set(node, "update", _("available")) self.modulesView.set(node, "descr", moduleInfo.get("description")) self.modulesView.set(node, "license", moduleInfo.get("license")) if moduleInfo.get("imports"): loadSubtree( node, [(importModuleInfo["name"], importModuleInfo) for importModuleInfo in moduleInfo["imports"]]) loadSubtree("", self.pluginConfig.get("modules", {}).items()) # clear previous treeview entries for previousNode in self.classesView.get_children(""): self.classesView.delete(previousNode) for i, classItem in enumerate( sorted(self.pluginConfig.get("classes", {}).items())): className, moduleList = classItem node = self.classesView.insert("", "end", className, text=className) self.classesView.set(node, "modules", ', '.join(moduleList)) self.moduleSelect() # clear out prior selection def ok(self, event=None): # check for orphaned classes (for which there is no longer a corresponding module) _moduleNames = self.pluginConfig.get("modules", {}).keys() _orphanedClassNames = set() for className, moduleList in self.pluginConfig.get("classes", {}).items(): for _moduleName in moduleList.copy(): if _moduleName not in _moduleNames: # it's orphaned moduleList.remove(_moduleName) self.pluginConfigChanged = True if not moduleList: # now orphaned _orphanedClassNames.add(className) self.pluginConfigChanged = True for _orphanedClassName in _orphanedClassNames: del self.pluginConfig["classes"][_orphanedClassName] if self.pluginConfigChanged: PluginManager.pluginConfig = self.pluginConfig PluginManager.pluginConfigChanged = True PluginManager.reset() # force reloading of modules if self.uiClassMethodsChanged or self.modelClassesChanged or self.customTransformsChanged or self.disclosureSystemTypesChanged or self.hostSystemFeaturesChanged: # may require reloading UI affectedItems = "" if self.uiClassMethodsChanged: affectedItems += _("menus of the user interface") if self.modelClassesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("model objects of the processor") if self.customTransformsChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("custom transforms") if self.disclosureSystemTypesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("disclosure system types") if self.hostSystemFeaturesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("host system features") if messagebox.askyesno( _("User interface plug-in change"), _("A change in plug-in class methods may have affected {0}. " "Please restart Arelle to due to these changes. \n\n" "Should Arelle restart itself now " "(if there are any unsaved changes they would be lost!)?" ).format(affectedItems), parent=self): self.cntlr.uiThreadQueue.put((self.cntlr.quit, [None, True])) self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def moduleSelect(self, *args): node = (self.modulesView.selection() or (None, ))[0] if node: node = node.rpartition(GROUPSEP)[ 2] # drop leading path names for module name moduleInfo = self.pluginConfig.get("modules", {}).get(node) if moduleInfo: self.selectedModule = node name = moduleInfo["name"] self.moduleNameLabel.config(text=name) self.moduleAuthorHdr.config(state=ACTIVE) self.moduleAuthorLabel.config(text=moduleInfo.get("author")) self.moduleDescrHdr.config(state=ACTIVE) self.moduleDescrLabel.config(text=moduleInfo.get("description")) self.moduleClassesHdr.config(state=ACTIVE) self.moduleClassesLabel.config( text=', '.join(moduleInfo["classMethods"])) self.moduleVersionHdr.config(state=ACTIVE) self.moduleVersionLabel.config(text=moduleInfo.get("version")) self.moduleUrlHdr.config(state=ACTIVE) self.moduleUrlLabel.config(text=moduleInfo["moduleURL"]) self.moduleDateHdr.config(state=ACTIVE) self.moduleDateLabel.config( text=moduleInfo["fileDate"] + " " + (_("(an update is available)") if name in self.modulesWithNewerFileDates else "")) self.moduleLicenseHdr.config(state=ACTIVE) self.moduleLicenseLabel.config(text=moduleInfo.get("license")) if moduleInfo.get("imports"): self.moduleImportsHdr.config(state=ACTIVE) _text = ", ".join(mi["name"] for mi in moduleInfo["imports"][:3]) if len(moduleInfo["imports"]) >= 3: _text += ", ..." self.moduleImportsLabel.config(text=_text) _buttonState = DISABLED if moduleInfo.get("isImported") else ACTIVE self.moduleEnableButton.config(state=_buttonState, text={ "enabled": self.DISABLE, "disabled": self.ENABLE }[moduleInfo["status"]]) self.moduleReloadButton.config(state=_buttonState) self.moduleRemoveButton.config(state=_buttonState) else: self.selectedModule = None self.moduleNameLabel.config(text="") self.moduleAuthorHdr.config(state=DISABLED) self.moduleAuthorLabel.config(text="") self.moduleDescrHdr.config(state=DISABLED) self.moduleDescrLabel.config(text="") self.moduleClassesHdr.config(state=DISABLED) self.moduleClassesLabel.config(text="") self.moduleVersionHdr.config(state=DISABLED) self.moduleVersionLabel.config(text="") self.moduleUrlHdr.config(state=DISABLED) self.moduleUrlLabel.config(text="") self.moduleDateHdr.config(state=DISABLED) self.moduleDateLabel.config(text="") self.moduleLicenseHdr.config(state=DISABLED) self.moduleLicenseLabel.config(text="") self.moduleImportsHdr.config(state=DISABLED) self.moduleImportsLabel.config(text="") self.moduleEnableButton.config(state=DISABLED, text=self.ENABLE) self.moduleReloadButton.config(state=DISABLED) self.moduleRemoveButton.config(state=DISABLED) def selectLocally(self): choices = [] # list of tuple of (file name, description) def sortOrder(key): return { "EdgarRenderer": "1", "validate": "2", "xbrlDB": "3" }.get(key, "4") + key.lower() def selectChoices(dir, indent=""): dirHasEntries = False for f in sorted(os.listdir(dir), key=sortOrder): if f not in (".", "..", "__pycache__", "__init__.py"): fPath = os.path.join(dir, f) fPkgInit = os.path.join(fPath, "__init__.py") dirInsertPoint = len(choices) moduleInfo = None if ((os.path.isdir(fPath) and os.path.exists(fPkgInit)) or ((os.path.isfile(fPath) and f.endswith(".py")))): moduleInfo = PluginManager.moduleModuleInfo(fPath) if moduleInfo: choices.append(( indent + f, "name: {}\ndescription: {}\nversion: {}\nlicense: {}" .format(moduleInfo["name"], moduleInfo.get("description"), moduleInfo.get("version"), moduleInfo.get("license")), fPath, moduleInfo["name"], moduleInfo.get("version"), moduleInfo.get("description"), moduleInfo.get("license"))) dirHasEntries = True if os.path.isdir(fPath) and f not in ("DQC_US_Rules", ): if selectChoices(fPath, indent=indent + " ") and not moduleInfo: choices.insert(dirInsertPoint, (indent + f, None, None, None, None, None, None)) return dirHasEntries selectChoices(self.cntlr.pluginDir) selectedPath = DialogOpenArchive.selectPlugin(self, choices) if selectedPath: moduleInfo = PluginManager.moduleModuleInfo( selectedPath[len(self.cntlr.pluginDir) + 1:]) self.loadFoundModuleInfo(moduleInfo, selectedPath) def browseLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory initialdir = self.cntlr.config.setdefault("pluginOpenDir", initialdir) filename = self.cntlr.uiFileDialog( "open", parent=self, title=_("Choose plug-in module file"), initialdir=initialdir, filetypes=[(_("Python files"), "*.py")], defaultextension=".py") if filename: # check if a package is selected (any file in a directory containing an __init__.py #if (os.path.basename(filename) == "__init__.py" and os.path.isdir(os.path.dirname(filename)) and # os.path.isfile(filename)): # filename = os.path.dirname(filename) # refer to the package instead self.cntlr.config["pluginOpenDir"] = os.path.dirname(filename) moduleInfo = PluginManager.moduleModuleInfo(filename) self.loadFoundModuleInfo(moduleInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file moduleInfo = PluginManager.moduleModuleInfo(url) self.cntlr.showStatus("") # clear web loading status self.loadFoundModuleInfo(moduleInfo, url) def loadFoundModuleInfo(self, moduleInfo, url): if moduleInfo and moduleInfo.get("name"): self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() else: messagebox.showwarning( _("Module is not itself a plug-in or in a directory with package __init__.py plug-in. " ), _("File does not itself contain a python program with an appropriate __pluginInfo__ declaration: \n\n{0}" ).format(url), parent=self) def checkIfImported(self, moduleInfo): if moduleInfo.get("isImported"): messagebox.showwarning( _("Plug-in is imported by a parent plug-in. "), _("Plug-in has a parent, please request operation on the parent: \n\n{0}" ).format(moduleInfo.get("name")), parent=self) return True return False def checkClassMethodsChanged(self, moduleInfo): for classMethod in moduleInfo["classMethods"]: if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI elif classMethod == "ModelObjectFactory.ElementSubstitutionClasses": self.modelClassesChanged = True # model object factor classes changed elif classMethod == "ModelManager.LoadCustomTransforms": self.customTransformsChanged = True elif classMethod == "DisclosureSystem.Types": self.disclosureSystemTypesChanged = True # disclosure system types changed elif classMethod.startswith("Proxy."): self.hostSystemFeaturesChanged = True # system features (e.g., proxy) changed def removePluginConfigModuleInfo(self, name): moduleInfo = self.pluginConfig["modules"].get(name) if moduleInfo: if self.checkIfImported(moduleInfo): return def _removePluginConfigModuleInfo(moduleInfo): _name = moduleInfo.get("name") if _name: self.checkClassMethodsChanged(moduleInfo) for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].get( classMethod) if classMethods and _name in classMethods: classMethods.remove(_name) if not classMethods: # list has become unused del self.pluginConfig["classes"][ classMethod] # remove class for importModuleInfo in moduleInfo.get( "imports", EMPTYLIST): _removePluginConfigModuleInfo(importModuleInfo) self.pluginConfig["modules"].pop(_name, None) _removePluginConfigModuleInfo(moduleInfo) if not self.pluginConfig["modules"] and self.pluginConfig[ "classes"]: self.pluginConfig["classes"].clear() # clean orphan classes self.pluginConfigChanged = True def addPluginConfigModuleInfo(self, moduleInfo): if self.checkIfImported(moduleInfo): return name = moduleInfo.get("name") self.removePluginConfigModuleInfo( name) # remove any prior entry for this module def _addPlugin(moduleInfo): _name = moduleInfo.get("name") if _name: self.modulesWithNewerFileDates.discard( _name) # no longer has an update available self.pluginConfig["modules"][_name] = moduleInfo # add classes for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].setdefault( classMethod, []) if name not in classMethods: classMethods.append(_name) self.checkClassMethodsChanged(moduleInfo) for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _addPlugin(importModuleInfo) _addPlugin(moduleInfo) self.pluginConfigChanged = True def moduleEnable(self): if self.selectedModule in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][self.selectedModule] if self.checkIfImported(moduleInfo): return def _moduleEnable(moduleInfo): if self.moduleEnableButton['text'] == self.ENABLE: moduleInfo["status"] = "enabled" elif self.moduleEnableButton['text'] == self.DISABLE: moduleInfo["status"] = "disabled" self.checkClassMethodsChanged(moduleInfo) for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _moduleEnable( importModuleInfo) # set status on nested moduleInfo if importModuleInfo['name'] in self.pluginConfig[ "modules"]: # set status on top level moduleInfo _moduleEnable(self.pluginConfig["modules"][ importModuleInfo['name']]) _moduleEnable(moduleInfo) if self.moduleEnableButton['text'] == self.ENABLE: self.moduleEnableButton['text'] = self.DISABLE elif self.moduleEnableButton['text'] == self.DISABLE: self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() def moduleReload(self): if self.selectedModule in self.pluginConfig["modules"]: url = self.pluginConfig["modules"][self.selectedModule].get( "moduleURL") if url: moduleInfo = PluginManager.moduleModuleInfo(url, reload=True) if moduleInfo: if self.checkIfImported(moduleInfo): return self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format( moduleInfo["name"]), clearAfter=5000) else: messagebox.showwarning( _("Module error"), _("File or module cannot be reloaded: \n\n{0}").format( url), parent=self) def moduleRemove(self): if self.selectedModule in self.pluginConfig["modules"]: self.removePluginConfigModuleInfo(self.selectedModule) self.pluginConfigChanged = True self.loadTreeViews() def enableAll(self): self.enableDisableAll(True) def disableAll(self): self.enableDisableAll(False) def enableDisableAll(self, doEnable): for module in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][module] if not moduleInfo.get("isImported"): def _enableDisableAll(moduleInfo): if doEnable: moduleInfo["status"] = "enabled" else: moduleInfo["status"] = "disabled" for importModuleInfo in moduleInfo.get( "imports", EMPTYLIST): _enableDisableAll(importModuleInfo) _enableDisableAll(moduleInfo) if doEnable: self.moduleEnableButton['text'] = self.DISABLE else: self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews()
class DialogOpenArchive(Toplevel): def __init__(self, mainWin, openType, filesource, filenames, title, colHeader, showAltViewButton=False): parent = mainWin.parent super(DialogOpenArchive, self).__init__(parent) self.parent = parent self.showAltViewButton = showAltViewButton parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.accepted = False self.transient(self.parent) frame = Frame(self) treeFrame = Frame(frame, width=500) vScrollbar = Scrollbar(treeFrame, orient=VERTICAL) hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL) self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set) self.treeView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.treeView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.treeView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) treeFrame.columnconfigure(0, weight=1) treeFrame.rowconfigure(0, weight=1) treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.treeView.focus_set() mainWin.showStatus(_("loading archive {0}").format(filesource.url)) self.filesource = filesource self.filenames = filenames self.selection = filesource.selection self.hasToolTip = False selectedNode = None if openType == ENTRY_POINTS: try: metadataFiles = filesource.taxonomyPackageMetadataFiles if len(metadataFiles) > 1: raise IOError(_("Taxonomy package contained more than one metadata file: {0}.") .format(', '.join(metadataFiles))) metadataFile = metadataFiles[0] metadata = filesource.file(filesource.url + os.sep + metadataFile)[0] self.metadataFilePrefix = os.sep.join(os.path.split(metadataFile)[:-1]) if self.metadataFilePrefix: self.metadataFilePrefix += os.sep self.nameToUrls, self.remappings = parseTxmyPkg(mainWin, metadata) except Exception as e: self.close() err = _("Failed to parse metadata; the underlying error was: {0}").format(e) messagebox.showerror(_("Malformed taxonomy package"), err) mainWin.addToLog(err) return mainWin.showStatus(None) if openType == DISCLOSURE_SYSTEM: y = 3 else: y = 1 okButton = Button(frame, text=_("OK"), command=self.ok) cancelButton = Button(frame, text=_("Cancel"), command=self.close) okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3) cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3) if showAltViewButton: self.altViewButton = Button(frame, command=self.showAltView) self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3) self.loadTreeView(openType, colHeader, title) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.toolTipText = StringVar() if self.hasToolTip: self.treeView.bind("<Motion>", self.motion, '+') self.treeView.bind("<Leave>", self.leave, '+') self.toolTipText = StringVar() self.toolTip = ToolTip(self.treeView, textvariable=self.toolTipText, wraplength=640, follow_mouse=True, state="disabled") self.toolTipRowId = None self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeView(self, openType, title, colHeader): self.title(title) self.openType = openType selectedNode = None # clear previous treeview entries for previousNode in self.treeView.get_children(""): self.treeView.delete(previousNode) # set up treeView widget and tabbed pane if openType in (ARCHIVE, DISCLOSURE_SYSTEM): self.treeView.column("#0", width=500, anchor="w") self.treeView.heading("#0", text=colHeader) try: self.isRss = self.filesource.isRss if self.isRss: self.treeView.column("#0", width=350, anchor="w") self.treeView["columns"] = ("descr", "date", "instDoc") self.treeView.column("descr", width=50, anchor="center", stretch=False) self.treeView.heading("descr", text="Form") self.treeView.column("date", width=170, anchor="w", stretch=False) self.treeView.heading("date", text="Pub Date") self.treeView.column("instDoc", width=200, anchor="w", stretch=False) self.treeView.heading("instDoc", text="Instance Document") except AttributeError: self.isRss = False self.treeView["columns"] = tuple() loadedPaths = [] for i, filename in enumerate(self.filenames): if isinstance(filename,tuple): if self.isRss: form, date, instDoc = filename[2:5] filename = filename[0] # ignore tooltip self.hasToolTip = True if filename.endswith("/"): filename = filename[:-1] path = filename.split("/") if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths: parent = "file{0}".format(loadedPaths.index(path[:-1])) else: parent = "" node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1]) if self.isRss: self.treeView.set(node, "descr", form) self.treeView.set(node, "date", date) self.treeView.set(node, "instDoc", os.path.basename(instDoc)) if self.selection == filename: selectedNode = node loadedPaths.append(path) elif openType == ENTRY_POINTS: self.treeView.column("#0", width=150, anchor="w") self.treeView.heading("#0", text="Name") self.treeView["columns"] = ("url",) self.treeView.column("url", width=350, anchor="w") self.treeView.heading("url", text="URL") for name, urls in self.nameToUrls.items(): displayUrl = urls[1] # display the canonical URL self.treeView.insert("", "end", name, values=[displayUrl], text=name) self.hasToolTip = True else: # unknown openType return None if selectedNode: self.treeView.see(selectedNode) self.treeView.selection_set(selectedNode) if self.showAltViewButton: self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries")) def ok(self, event=None): selection = self.treeView.selection() if len(selection) > 0: if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): filename = self.filenames[int(selection[0][4:])] if isinstance(filename,tuple): if self.isRss: filename = filename[4] else: filename = filename[0] if not filename.endswith("/"): self.filesource.select(filename) self.accepted = True self.close() elif self.openType == ENTRY_POINTS: epName = selection[0] #index 0 is the remapped Url, as opposed to the canonical one used for display urlOrFile = self.nameToUrls[epName][0] # load file source remappings self.filesource.mappedPaths = \ dict((prefix, remapping if isHttpUrl(remapping) else (self.filesource.baseurl + os.sep + self.metadataFilePrefix +remapping.replace("/", os.sep))) for prefix, remapping in self.remappings.items()) if not urlOrFile.endswith("/"): # check if it's an absolute URL rather than a path into the archive if isHttpUrl(urlOrFile): self.filesource.select(urlOrFile) # absolute path selection else: # assume it's a path inside the archive: self.filesource.select(self.metadataFilePrefix + urlOrFile) self.accepted = True self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def showAltView(self, event=None): if self.openType == ENTRY_POINTS: self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File")) else: self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File")) def leave(self, *args): self.toolTipRowId = None def motion(self, *args): tvRowId = self.treeView.identify_row(args[0].y) if tvRowId != self.toolTipRowId: text = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): self.toolTipRowId = tvRowId if tvRowId and len(tvRowId) > 4: try: text = self.filenames[ int(tvRowId[4:]) ] if isinstance(text, tuple): text = text[1].replace("\\n","\n") except (KeyError, ValueError): pass elif self.openType == ENTRY_POINTS: try: epUrl = self.nameToUrls[tvRowId][1] text = "{0}\n{1}".format(tvRowId, epUrl) except KeyError: pass self.setToolTip(text) def setToolTip(self, text): self.toolTip._hide() if text: self.toolTipText.set(text) self.toolTip.configure(state="normal") self.toolTip._schedule() else: self.toolTipText.set("") self.toolTip.configure(state="disabled")
class DialogOpenArchive(Toplevel): def __init__(self, mainWin, openType, filesource, filenames, title, colHeader, showAltViewButton=False): parent = mainWin.parent super(DialogOpenArchive, self).__init__(parent) self.parent = parent self.showAltViewButton = showAltViewButton parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.accepted = False self.transient(self.parent) frame = Frame(self) treeFrame = Frame(frame, width=500) vScrollbar = Scrollbar(treeFrame, orient=VERTICAL) hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL) self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set) self.treeView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.treeView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.treeView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) treeFrame.columnconfigure(0, weight=1) treeFrame.rowconfigure(0, weight=1) treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.treeView.focus_set() mainWin.showStatus(_("loading archive {0}").format(filesource.url)) self.filesource = filesource self.filenames = filenames self.selection = filesource.selection self.hasToolTip = False selectedNode = None if openType == ENTRY_POINTS: try: metadataFiles = filesource.taxonomyPackageMetadataFiles ''' take first for now if len(metadataFiles) != 1: raise IOError(_("Taxonomy package contained more than one metadata file: {0}.") .format(', '.join(metadataFiles))) ''' metadataFile = metadataFiles[0] metadata = filesource.url + os.sep + metadataFile self.metadataFilePrefix = os.sep.join(os.path.split(metadataFile)[:-1]) if self.metadataFilePrefix: self.metadataFilePrefix += "/" # zip contents have /, never \ file seps self.taxonomyPkgMetaInf = '{}/META-INF/'.format( os.path.splitext(os.path.basename(filesource.url))[0]) self.taxonomyPackage = parsePackage(mainWin, filesource, metadata, os.sep.join(os.path.split(metadata)[:-1]) + os.sep) # may be a catalog file with no entry oint names if not self.taxonomyPackage["nameToUrls"]: openType = ARCHIVE # no entry points to show, just archive self.showAltViewButton = False except Exception as e: self.close() err = _("Failed to parse metadata; the underlying error was: {0}").format(e) messagebox.showerror(_("Malformed taxonomy package"), err) mainWin.addToLog(err) return mainWin.showStatus(None) if openType == DISCLOSURE_SYSTEM: y = 3 else: y = 1 okButton = Button(frame, text=_("OK"), command=self.ok) cancelButton = Button(frame, text=_("Cancel"), command=self.close) okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3) cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3) if self.showAltViewButton: self.altViewButton = Button(frame, command=self.showAltView) self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3) self.loadTreeView(openType, colHeader, title) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.toolTipText = StringVar() if self.hasToolTip: self.treeView.bind("<Motion>", self.motion, '+') self.treeView.bind("<Leave>", self.leave, '+') self.toolTipText = StringVar() self.toolTip = ToolTip(self.treeView, textvariable=self.toolTipText, wraplength=640, follow_mouse=True, state="disabled") self.toolTipRowId = None self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeView(self, openType, title, colHeader): self.title(title) self.openType = openType selectedNode = None # clear previous treeview entries for previousNode in self.treeView.get_children(""): self.treeView.delete(previousNode) # set up treeView widget and tabbed pane if openType in (ARCHIVE, DISCLOSURE_SYSTEM): self.treeView.column("#0", width=500, anchor="w") self.treeView.heading("#0", text=colHeader) try: self.isRss = self.filesource.isRss if self.isRss: self.treeView.column("#0", width=350, anchor="w") self.treeView["columns"] = ("descr", "date", "instDoc") self.treeView.column("descr", width=50, anchor="center", stretch=False) self.treeView.heading("descr", text="Form") self.treeView.column("date", width=170, anchor="w", stretch=False) self.treeView.heading("date", text="Pub Date") self.treeView.column("instDoc", width=200, anchor="w", stretch=False) self.treeView.heading("instDoc", text="Instance Document") except AttributeError: self.isRss = False self.treeView["columns"] = tuple() loadedPaths = [] for i, filename in enumerate(self.filenames): if isinstance(filename,tuple): if self.isRss: form, date, instDoc = filename[2:5] filename = filename[0] # ignore tooltip self.hasToolTip = True if filename.endswith("/"): filename = filename[:-1] path = filename.split("/") if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths: parent = "file{0}".format(loadedPaths.index(path[:-1])) else: parent = "" node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1]) if self.isRss: self.treeView.set(node, "descr", form) self.treeView.set(node, "date", date) self.treeView.set(node, "instDoc", os.path.basename(instDoc)) if self.selection == filename: selectedNode = node loadedPaths.append(path) elif openType == ENTRY_POINTS: self.treeView.column("#0", width=150, anchor="w") self.treeView.heading("#0", text="Name") self.treeView["columns"] = ("url",) self.treeView.column("url", width=350, anchor="w") self.treeView.heading("url", text="URL") for name, urls in self.taxonomyPackage["nameToUrls"].items(): displayUrl = urls[1] # display the canonical URL self.treeView.insert("", "end", name, values=[displayUrl], text=name) self.hasToolTip = True else: # unknown openType return None if selectedNode: self.treeView.see(selectedNode) self.treeView.selection_set(selectedNode) if self.showAltViewButton: self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries")) def ok(self, event=None): selection = self.treeView.selection() if len(selection) > 0: if hasattr(self, "taxonomyPackage"): # load file source remappings self.filesource.mappedPaths = self.taxonomyPackage["remappings"] filename = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): filename = self.filenames[int(selection[0][4:])] if isinstance(filename,tuple): if self.isRss: filename = filename[4] else: filename = filename[0] elif self.openType == ENTRY_POINTS: epName = selection[0] #index 0 is the remapped Url, as opposed to the canonical one used for display filename = self.taxonomyPackage["nameToUrls"][epName][0] if not filename.endswith("/"): # check if it's an absolute URL rather than a path into the archive if not isHttpUrl(filename) and self.metadataFilePrefix != self.taxonomyPkgMetaInf: # assume it's a path inside the archive: filename = self.metadataFilePrefix + filename if filename is not None and not filename.endswith("/"): if hasattr(self, "taxonomyPackage"): # attempt to unmap the filename to original file # will be mapped again in loading, but this allows schemaLocation to be unmapped for prefix, remapping in self.taxonomyPackage["remappings"].items(): if isHttpUrl(remapping): remapStart = remapping else: remapStart = self.metadataFilePrefix + remapping if filename.startswith(remapStart): # set unmmapped file filename = prefix + filename[len(remapStart):] break self.filesource.select(filename) self.accepted = True self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def showAltView(self, event=None): if self.openType == ENTRY_POINTS: self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File")) else: self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File")) def leave(self, *args): self.toolTipRowId = None def motion(self, *args): tvRowId = self.treeView.identify_row(args[0].y) if tvRowId != self.toolTipRowId: text = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): self.toolTipRowId = tvRowId if tvRowId and len(tvRowId) > 4: try: text = self.filenames[ int(tvRowId[4:]) ] if isinstance(text, tuple): text = text[1].replace("\\n","\n") except (KeyError, ValueError): pass elif self.openType == ENTRY_POINTS: try: epUrl = self.taxonomyPackage["nameToUrls"][tvRowId][1] text = "{0}\n{1}".format(tvRowId, epUrl) except KeyError: pass self.setToolTip(text) def setToolTip(self, text): self.toolTip._hide() if text: self.toolTipText.set(text) self.toolTip.configure(state="normal") self.toolTip._schedule() else: self.toolTipText.set("") self.toolTip.configure(state="disabled")
class DialogPackageManager(Toplevel): def __init__(self, mainWin, packageNamesWithNewerFileDates): super(DialogPackageManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.packagesConfig = PackageManager.packagesConfig self.packagesConfigChanged = False self.packageNamesWithNewerFileDates = packageNamesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Taxonomy Packages Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find taxonomy packages:"), wraplength=64, justify="center") addLocalButton = Button(buttonFrame, text=_("Locally"), command=self.findLocally) ToolTip(addLocalButton, text=_("File chooser allows selecting taxonomy packages to add (or reload), from the local file system. " "Select either a PWD or prior taxonomy package zip file, or a taxonomy manifest (.taxonomyPackage.xml) within an unzipped taxonomy package. "), wraplength=360) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip(addWebButton, text=_("Dialog to enter URL full path to load (or reload) package, from the web or local file system. " "URL may be either a PWD or prior taxonomy package zip file, or a taxonomy manifest (.taxonomyPackage.xml) within an unzipped taxonomy package. "), wraplength=360) manifestNameButton = Button(buttonFrame, text=_("Manifest"), command=self.manifestName) ToolTip(manifestNameButton, text=_("Provide pre-PWD non-standard archive manifest file name pattern (e.g., *taxonomyPackage.xml). " "Uses unix file name pattern matching. " "Multiple manifest files are supported in pre-PWD archives (such as oasis catalogs). " "(Replaces pre-PWD search for either .taxonomyPackage.xml or catalog.xml). "), wraplength=480) self.manifestNamePattern = "" addLabel.grid(row=0, column=0, pady=4) addLocalButton.grid(row=1, column=0, pady=4) addWebButton.grid(row=2, column=0, pady=4) manifestNameButton.grid(row=3, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=3, sticky=(N, S, W), padx=3, pady=3) # right tree frame (packages already known to arelle) packagesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(packagesFrame, orient=VERTICAL) hScrollbar = Scrollbar(packagesFrame, orient=HORIZONTAL) self.packagesView = Treeview(packagesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.packagesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.packagesView.bind('<<TreeviewSelect>>', self.packageSelect) hScrollbar["command"] = self.packagesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.packagesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) packagesFrame.columnconfigure(0, weight=1) packagesFrame.rowconfigure(0, weight=1) packagesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.packagesView.focus_set() self.packagesView.column("#0", width=120, anchor="w") self.packagesView.heading("#0", text=_("Name")) self.packagesView["columns"] = ("ver", "status", "date", "update", "descr") self.packagesView.column("ver", width=150, anchor="w", stretch=False) self.packagesView.heading("ver", text=_("Version")) self.packagesView.column("status", width=50, anchor="w", stretch=False) self.packagesView.heading("status", text=_("Status")) self.packagesView.column("date", width=170, anchor="w", stretch=False) self.packagesView.heading("date", text=_("File Date")) self.packagesView.column("update", width=50, anchor="w", stretch=False) self.packagesView.heading("update", text=_("Update")) self.packagesView.column("descr", width=200, anchor="w", stretch=False) self.packagesView.heading("descr", text=_("Description")) remappingsFrame = Frame(frame) vScrollbar = Scrollbar(remappingsFrame, orient=VERTICAL) hScrollbar = Scrollbar(remappingsFrame, orient=HORIZONTAL) self.remappingsView = Treeview(remappingsFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.remappingsView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.remappingsView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.remappingsView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) remappingsFrame.columnconfigure(0, weight=1) remappingsFrame.rowconfigure(0, weight=1) remappingsFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.remappingsView.focus_set() self.remappingsView.column("#0", width=200, anchor="w") self.remappingsView.heading("#0", text=_("Prefix")) self.remappingsView["columns"] = ("remapping") self.remappingsView.column("remapping", width=500, anchor="w", stretch=False) self.remappingsView.heading("remapping", text=_("Remapping")) # bottom frame package info details packageInfoFrame = Frame(frame, width=700) packageInfoFrame.columnconfigure(1, weight=1) self.packageNameLabel = Label(packageInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.packageNameLabel.grid(row=0, column=0, columnspan=6, sticky=W) self.packageVersionHdr = Label(packageInfoFrame, text=_("version:"), state=DISABLED) self.packageVersionHdr.grid(row=1, column=0, sticky=W) self.packageVersionLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageVersionLabel.grid(row=1, column=1, columnspan=5, sticky=W) self.packageDescrHdr = Label(packageInfoFrame, text=_("description:"), state=DISABLED) self.packageDescrHdr.grid(row=2, column=0, sticky=W) self.packageDescrLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageDescrLabel.grid(row=2, column=1, columnspan=5, sticky=W) self.packagePrefixesHdr = Label(packageInfoFrame, text=_("prefixes:"), state=DISABLED) self.packagePrefixesHdr.grid(row=3, column=0, sticky=W) self.packagePrefixesLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packagePrefixesLabel.grid(row=3, column=1, columnspan=5, sticky=W) ToolTip(self.packagePrefixesLabel, text=_("List of prefixes that this package remaps."), wraplength=240) self.packageUrlHdr = Label(packageInfoFrame, text=_("URL:"), state=DISABLED) self.packageUrlHdr.grid(row=4, column=0, sticky=W) self.packageUrlLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageUrlLabel.grid(row=4, column=1, columnspan=5, sticky=W) ToolTip(self.packageUrlLabel, text=_("URL of taxonomy package (local file path or web loaded file)."), wraplength=240) self.packageDateHdr = Label(packageInfoFrame, text=_("date:"), state=DISABLED) self.packageDateHdr.grid(row=5, column=0, sticky=W) self.packageDateLabel = Label(packageInfoFrame, wraplength=600, justify="left") self.packageDateLabel.grid(row=5, column=1, columnspan=5, sticky=W) ToolTip(self.packageDateLabel, text=_("Date of currently loaded package file (with parenthetical node when an update is available)."), wraplength=240) self.packageEnableButton = Button(packageInfoFrame, text=self.ENABLE, state=DISABLED, command=self.packageEnable) ToolTip(self.packageEnableButton, text=_("Enable/disable package."), wraplength=240) self.packageEnableButton.grid(row=6, column=1, sticky=E) self.packageMoveUpButton = Button(packageInfoFrame, text=_("Move Up"), state=DISABLED, command=self.packageMoveUp) ToolTip(self.packageMoveUpButton, text=_("Move package up (above other remappings)."), wraplength=240) self.packageMoveUpButton.grid(row=6, column=2, sticky=E) self.packageMoveDownButton = Button(packageInfoFrame, text=_("Move Down"), state=DISABLED, command=self.packageMoveDown) ToolTip(self.packageMoveDownButton, text=_("Move package down (below other remappings)."), wraplength=240) self.packageMoveDownButton.grid(row=6, column=3, sticky=E) self.packageReloadButton = Button(packageInfoFrame, text=_("Reload"), state=DISABLED, command=self.packageReload) ToolTip(self.packageReloadButton, text=_("Reload/update package."), wraplength=240) self.packageReloadButton.grid(row=6, column=4, sticky=E) self.packageRemoveButton = Button(packageInfoFrame, text=_("Remove"), state=DISABLED, command=self.packageRemove) ToolTip(self.packageRemoveButton, text=_("Remove package from packages table (does not erase the package file)."), wraplength=240) self.packageRemoveButton.grid(row=6, column=5, sticky=E) packageInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) packageInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S,W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) ToolTip(enableAllButton, text=_("Enable all packages."), wraplength=240) disableAllButton = Button(enableDisableFrame, text=_("Disable All"), command=self.disableAll) ToolTip(disableAllButton, text=_("Disable all packages."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=0) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.packagesView.get_children(""): self.packagesView.delete(previousNode) for i, packageInfo in enumerate(self.packagesConfig.get("packages", [])): name = packageInfo.get("name", "package{}".format(i)) node = self.packagesView.insert("", "end", "_{}".format(i), text=name) self.packagesView.set(node, "ver", packageInfo.get("version")) self.packagesView.set(node, "status", packageInfo.get("status")) self.packagesView.set(node, "date", packageInfo.get("fileDate")) if name in self.packageNamesWithNewerFileDates: self.packagesView.set(node, "update", _("available")) self.packagesView.set(node, "descr", packageInfo.get("description")) # clear previous treeview entries for previousNode in self.remappingsView.get_children(""): self.remappingsView.delete(previousNode) for i, remappingItem in enumerate(sorted(self.packagesConfig.get("remappings", {}).items())): prefix, remapping = remappingItem node = self.remappingsView.insert("", "end", prefix, text=prefix) self.remappingsView.set(node, "remapping", remapping) self.packageSelect() # clear out prior selection def ok(self, event=None): if self.packagesConfigChanged: PackageManager.packagesConfig = self.packagesConfig PackageManager.packagesConfigChanged = True self.cntlr.onPackageEnablementChanged() self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def packageSelect(self, *args): node = (self.packagesView.selection() or (None,))[0] try: nodeIndex = int(node[1:]) except (ValueError, TypeError): nodeIndex = -1 if 0 <= nodeIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][nodeIndex] self.selectedPackageIndex = nodeIndex name = packageInfo["name"] self.packageNameLabel.config(text=name) self.packageVersionHdr.config(state=ACTIVE) self.packageVersionLabel.config(text=packageInfo["version"]) self.packageDescrHdr.config(state=ACTIVE) self.packageDescrLabel.config(text=packageInfo["description"]) self.packagePrefixesHdr.config(state=ACTIVE) self.packagePrefixesLabel.config(text=', '.join(packageInfo["remappings"].keys())) self.packageUrlHdr.config(state=ACTIVE) self.packageUrlLabel.config(text=packageInfo["URL"]) self.packageDateHdr.config(state=ACTIVE) self.packageDateLabel.config(text=packageInfo["fileDate"] + " " + (_("(an update is available)") if name in self.packageNamesWithNewerFileDates else "")) self.packageEnableButton.config(state=ACTIVE, text={"enabled":self.DISABLE, "disabled":self.ENABLE}[packageInfo["status"]]) self.packageMoveUpButton.config(state=ACTIVE if 0 < nodeIndex else DISABLED) self.packageMoveDownButton.config(state=ACTIVE if nodeIndex < (len(self.packagesConfig["packages"]) - 1) else DISABLED) self.packageReloadButton.config(state=ACTIVE) self.packageRemoveButton.config(state=ACTIVE) else: self.selectedPackageIndex = -1 self.packageNameLabel.config(text="") self.packageVersionHdr.config(state=DISABLED) self.packageVersionLabel.config(text="") self.packageDescrHdr.config(state=DISABLED) self.packageDescrLabel.config(text="") self.packagePrefixesHdr.config(state=DISABLED) self.packagePrefixesLabel.config(text="") self.packageUrlHdr.config(state=DISABLED) self.packageUrlLabel.config(text="") self.packageDateHdr.config(state=DISABLED) self.packageDateLabel.config(text="") self.packageEnableButton.config(state=DISABLED, text=self.ENABLE) self.packageMoveUpButton.config(state=DISABLED) self.packageMoveDownButton.config(state=DISABLED) self.packageReloadButton.config(state=DISABLED) self.packageRemoveButton.config(state=DISABLED) def findLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory initialdir = self.cntlr.config.setdefault("packageOpenDir", initialdir) filename = self.cntlr.uiFileDialog("open", parent=self, title=_("Choose taxonomy package file"), initialdir=initialdir, filetypes=[(_("Taxonomy package files (*.zip)"), "*.zip"), (_("PWD Manifest (taxonomyPackage.xml)"), "taxonomyPackage.xml"), (_("pre-PWD Manifest (*.taxonomyPackage.xml)"), "*.taxonomyPackage.xml"), (_("pre-PWD Oasis Catalog (*catalog.xml)"), "*catalog.xml")], defaultextension=".zip") if filename: # check if a package is selected (any file in a directory containing an __init__.py self.cntlr.config["packageOpenDir"] = os.path.dirname(filename) packageInfo = PackageManager.packageInfo(self.cntlr, filename, packageManifestName=self.manifestNamePattern) self.loadFoundPackageInfo(packageInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file packageInfo = PackageManager.packageInfo(self.cntlr, url, packageManifestName=self.manifestNamePattern) self.cntlr.showStatus("") # clear web loading status self.loadFoundPackageInfo(packageInfo, url) def manifestName(self): self.manifestNamePattern = simpledialog.askstring(_("Archive manifest file name pattern"), _("Provide non-standard archive manifest file name pattern (e.g., *taxonomyPackage.xml). \n" "Uses unix file name pattern matching. \n" "Multiple manifest files are supported in archive (such as oasis catalogs). \n" "(If blank, search for either .taxonomyPackage.xml or catalog.xml). "), initialvalue=self.manifestNamePattern, parent=self) def loadFoundPackageInfo(self, packageInfo, url): if packageInfo and packageInfo.get("name"): self.addPackageInfo(packageInfo) self.loadTreeViews() else: messagebox.showwarning(_("Package is not itself a taxonomy package. "), _("File does not itself contain a manifest file: \n\n{0}\n\n " "If opening an archive file, the manifest file search pattern currently is \"\", please press \"Manifest\" to change manifest file name pattern, e.g.,, \"*.taxonomyPackage.xml\", if needed. ") .format(url), parent=self) def removePackageInfo(self, name, version): # find package entry packagesList = self.packagesConfig["packages"] j = -1 for i, packageInfo in enumerate(packagesList): if packageInfo['name'] == name and packageInfo['version'] == version: j = i break if 0 <= j < len(packagesList): del self.packagesConfig["packages"][i] self.packagesConfigChanged = True def addPackageInfo(self, packageInfo): name = packageInfo["name"] version = packageInfo["version"] self.removePackageInfo(name, version) # remove any prior entry for this package self.packageNamesWithNewerFileDates.discard(name) # no longer has an update available self.packagesConfig["packages"].append(packageInfo) PackageManager.rebuildRemappings(self.cntlr) self.packagesConfigChanged = True def packageEnable(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] if self.packageEnableButton['text'] == self.ENABLE: packageInfo["status"] = "enabled" self.packageEnableButton['text'] = self.DISABLE elif self.packageEnableButton['text'] == self.DISABLE: packageInfo["status"] = "disabled" self.packageEnableButton['text'] = self.ENABLE self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() def packageMoveUp(self): if 1 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packages = self.packagesConfig["packages"] packageInfo = packages[self.selectedPackageIndex] del packages[self.selectedPackageIndex] packages.insert(self.selectedPackageIndex -1, packageInfo) self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() def packageMoveDown(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]) - 1: packages = self.packagesConfig["packages"] packageInfo = packages[self.selectedPackageIndex] del packages[self.selectedPackageIndex] packages.insert(self.selectedPackageIndex + 1, packageInfo) self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() def packageReload(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] url = packageInfo.get("URL") if url: packageInfo = PackageManager.packageInfo(self.cntlr, url, reload=True, packageManifestName=packageInfo.get("manifestName")) if packageInfo: self.addPackageInfo(packageInfo) PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format(packageInfo.get("name")), clearAfter=5000) else: messagebox.showwarning(_("Package error"), _("File or package cannot be reloaded: \n\n{0}") .format(url), parent=self) def packageRemove(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] self.removePackageInfo(packageInfo["name"], packageInfo["version"]) self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() def enableAll(self): self.enableDisableAll(True) def disableAll(self): self.enableDisableAll(False) def enableDisableAll(self, doEnable): for iPkg in range(len(self.packagesConfig["packages"])): packageInfo = self.packagesConfig["packages"][iPkg] if doEnable: packageInfo["status"] = "enabled" self.packageEnableButton['text'] = self.DISABLE else: packageInfo["status"] = "disabled" self.packageEnableButton['text'] = self.ENABLE self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews()
class DialogOpenArchive(Toplevel): def __init__(self, parent, openType, filesource, filenames, title, colHeader, showAltViewButton=False): if isinstance(parent, Cntlr): cntlr = parent parent = parent.parent # parent is cntlrWinMain else: # parent is a Toplevel dialog cntlr = parent.cntlr super(DialogOpenArchive, self).__init__(parent) self.parent = parent self.showAltViewButton = showAltViewButton parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.accepted = False self.transient(self.parent) frame = Frame(self) treeFrame = Frame(frame, width=500) vScrollbar = Scrollbar(treeFrame, orient=VERTICAL) hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL) self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set) self.treeView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.treeView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.treeView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) treeFrame.columnconfigure(0, weight=1) treeFrame.rowconfigure(0, weight=1) treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.treeView.focus_set() if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(_("loading archive {0}").format(filesource.url)) self.filesource = filesource self.filenames = filenames self.selection = filesource.selection self.hasToolTip = False selectedNode = None if openType == ENTRY_POINTS: try: metadataFiles = filesource.taxonomyPackageMetadataFiles ''' take first for now if len(metadataFiles) != 1: raise IOError(_("Taxonomy package contained more than one metadata file: {0}.") .format(', '.join(metadataFiles))) ''' metadataFile = metadataFiles[0] metadata = filesource.url + os.sep + metadataFile self.metadataFilePrefix = os.sep.join(os.path.split(metadataFile)[:-1]) if self.metadataFilePrefix: self.metadataFilePrefix += "/" # zip contents have /, never \ file seps self.taxonomyPkgMetaInf = '{}/META-INF/'.format( os.path.splitext(os.path.basename(filesource.url))[0]) self.taxonomyPackage = parsePackage(cntlr, filesource, metadata, os.sep.join(os.path.split(metadata)[:-1]) + os.sep) if self.taxonomyPackage["entryPoints"]: # may have instance documents too self.packageContainedInstances = [] packageContentTypeCounts = {} for suffix in (".xhtml", ".htm", ".html"): for potentialInstance in filesource.dir: if potentialInstance.endswith(".xhtml"): _type = "Inline Instance" self.packageContainedInstances.append([potentialInstance, _type]) packageContentTypeCounts[potentialInstance] = packageContentTypeCounts.get(potentialInstance, 0) + 1 if self.packageContainedInstances: break if self.packageContainedInstances: # add sequences to any duplicated entry types for _type, count in packageContentTypeCounts.items(): if count > 1: _dupNo = 0 for i in range(len(self.packageContainedInstances)): if self.packageContainedInstances[i][0] == _type: _dupNo += 1 self.packageContainedInstances[i][0] = "{} {}".format(_type, _dupNo) else: # may be a catalog file with no entry oint names openType = ARCHIVE # no entry points to show, just archive self.showAltViewButton = False except Exception as e: self.close() err = _("Failed to parse metadata; the underlying error was: {0}").format(e) messagebox.showerror(_("Malformed taxonomy package"), err) cntlr.addToLog(err) return if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(None) if openType in (DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): y = 3 else: y = 1 okButton = Button(frame, text=_("OK"), command=self.ok) cancelButton = Button(frame, text=_("Cancel"), command=self.close) okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3) cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3) if self.showAltViewButton: self.altViewButton = Button(frame, command=self.showAltView) self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3) self.loadTreeView(openType, colHeader, title) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.toolTipText = StringVar() if self.hasToolTip: self.treeView.bind("<Motion>", self.motion, '+') self.treeView.bind("<Leave>", self.leave, '+') self.toolTipText = StringVar() self.toolTip = ToolTip(self.treeView, textvariable=self.toolTipText, wraplength=640, follow_mouse=True, state="disabled") self.toolTipRowId = None self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeView(self, openType, title, colHeader): self.title(title) self.openType = openType selectedNode = None # clear previous treeview entries for previousNode in self.treeView.get_children(""): self.treeView.delete(previousNode) # set up treeView widget and tabbed pane if openType in (ARCHIVE, DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): if openType in (PLUGIN, PACKAGE): width = 770 else: width = 500 self.treeView.column("#0", width=width, anchor="w") self.treeView.heading("#0", text=colHeader) self.isRss = getattr(self.filesource, "isRss", False) if self.isRss: self.treeView.column("#0", width=350, anchor="w") self.treeView["columns"] = ("descr", "date", "instDoc") self.treeView.column("descr", width=50, anchor="center", stretch=False) self.treeView.heading("descr", text="Form") self.treeView.column("date", width=170, anchor="w", stretch=False) self.treeView.heading("date", text="Pub Date") self.treeView.column("instDoc", width=200, anchor="w", stretch=False) self.treeView.heading("instDoc", text="Instance Document") elif openType == PLUGIN: self.treeView.column("#0", width=150, anchor="w") self.treeView["columns"] = ("name", "vers", "descr", "license") self.treeView.column("name", width=150, anchor="w", stretch=False) self.treeView.heading("name", text="Name") self.treeView.column("vers", width=60, anchor="w", stretch=False) self.treeView.heading("vers", text="Version") self.treeView.column("descr", width=300, anchor="w", stretch=False) self.treeView.heading("descr", text="Description") self.treeView.column("license", width=60, anchor="w", stretch=False) self.treeView.heading("license", text="License") elif openType == PACKAGE: self.treeView.column("#0", width=200, anchor="w") self.treeView["columns"] = ("vers", "descr", "license") self.treeView.column("vers", width=100, anchor="w", stretch=False) self.treeView.heading("vers", text="Version") self.treeView.column("descr", width=400, anchor="w", stretch=False) self.treeView.heading("descr", text="Description") self.treeView.column("license", width=70, anchor="w", stretch=False) self.treeView.heading("license", text="License") else: self.treeView["columns"] = tuple() loadedPaths = [] for i, filename in enumerate(self.filenames): if isinstance(filename,tuple): if self.isRss: form, date, instDoc = filename[2:5] elif openType == PLUGIN: name, vers, descr, license = filename[3:7] elif openType == PACKAGE: vers, descr, license = filename[3:6] filename = filename[0] # ignore tooltip self.hasToolTip = True if filename.endswith("/"): filename = filename[:-1] path = filename.split("/") if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths: parent = "file{0}".format(loadedPaths.index(path[:-1])) else: parent = "" node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1]) if self.isRss: self.treeView.set(node, "descr", form) self.treeView.set(node, "date", date) self.treeView.set(node, "instDoc", os.path.basename(instDoc)) elif openType == PLUGIN: self.treeView.set(node, "name", name) self.treeView.set(node, "vers", vers) self.treeView.set(node, "descr", descr) self.treeView.set(node, "license", license) elif openType == PACKAGE: self.treeView.set(node, "vers", vers) self.treeView.set(node, "descr", descr) self.treeView.set(node, "license", license) if self.selection == filename: selectedNode = node loadedPaths.append(path) elif openType == ENTRY_POINTS: self.treeView.column("#0", width=200, anchor="w") self.treeView.heading("#0", text="Name") self.treeView["columns"] = ("url",) self.treeView.column("url", width=300, anchor="w") self.treeView.heading("url", text="URL") for fileType, fileUrl in getattr(self, "packageContainedInstances", ()): self.treeView.insert("", "end", fileUrl, values=fileType, text=fileUrl or urls[0][2]) for name, urls in sorted(self.taxonomyPackage["entryPoints"].items(), key=lambda i:i[0][2]): self.treeView.insert("", "end", name, values="\n".join(url[1] for url in urls), text=name or urls[0][2]) self.hasToolTip = True else: # unknown openType return None if selectedNode: self.treeView.see(selectedNode) self.treeView.selection_set(selectedNode) if self.showAltViewButton: self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries")) def ok(self, event=None): selection = self.treeView.selection() if len(selection) > 0: if hasattr(self, "taxonomyPackage"): # load file source remappings self.filesource.mappedPaths = self.taxonomyPackage["remappings"] filename = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM): filename = self.filenames[int(selection[0][4:])] if isinstance(filename,tuple): if self.isRss: filename = filename[4] else: filename = filename[0] elif self.openType == ENTRY_POINTS: epName = selection[0] #index 0 is the remapped Url, as opposed to the canonical one used for display # Greg Acsone reports [0] does not work for Corep 1.6 pkgs, need [1], old style packages filenames = [] for _url, _type in self.packageContainedInstances: # check if selection was an inline instance if _type == epName: filenames.append(_url) if not filenames: # else if it's a named taxonomy entry point for url in self.taxonomyPackage["entryPoints"][epName]: filename = url[0] if not filename.endswith("/"): # check if it's an absolute URL rather than a path into the archive if not isHttpUrl(filename) and self.metadataFilePrefix != self.taxonomyPkgMetaInf: # assume it's a path inside the archive: filename = self.metadataFilePrefix + filename filenames.append(filename) if filenames: self.filesource.select(filenames) self.accepted = True self.close() return elif self.openType in (PLUGIN, PACKAGE): filename = self.filenames[int(selection[0][4:])][2] if filename is not None and not filename.endswith("/"): if hasattr(self, "taxonomyPackage"): # attempt to unmap the filename to original file # will be mapped again in loading, but this allows schemaLocation to be unmapped for prefix, remapping in self.taxonomyPackage["remappings"].items(): if isHttpUrl(remapping): remapStart = remapping else: remapStart = self.metadataFilePrefix + remapping if filename.startswith(remapStart): # set unmmapped file filename = prefix + filename[len(remapStart):] break if self.openType in (PLUGIN, PACKAGE): self.filesource.selection = filename else: self.filesource.select(filename) self.accepted = True self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def showAltView(self, event=None): if self.openType == ENTRY_POINTS: self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File")) else: self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File")) def leave(self, *args): self.toolTipRowId = None def motion(self, *args): tvRowId = self.treeView.identify_row(args[0].y) if tvRowId != self.toolTipRowId: text = None if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): self.toolTipRowId = tvRowId if tvRowId and len(tvRowId) > 4: try: text = self.filenames[ int(tvRowId[4:]) ] if isinstance(text, tuple): text = (text[1] or "").replace("\\n","\n") except (KeyError, ValueError): pass elif self.openType == ENTRY_POINTS: try: text = "{0}\n{1}".format(tvRowId, "\n".join(url[1] for url in self.taxonomyPackage["entryPoints"][tvRowId])) except KeyError: pass self.setToolTip(text) def setToolTip(self, text): self.toolTip._hide() if text: self.toolTipText.set(text) self.toolTip.configure(state="normal") self.toolTip._schedule() else: self.toolTipText.set("") self.toolTip.configure(state="disabled")
class Decryptor(object): def __init__(self): self.fileBrowserShadowInformation = dict() self.startUI() def startUI(self): self.root = Tk() self.root.title('Decryptor') self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) mainFrame = Frame(self.root) mainFrame.grid(row=0, column=0, padx=5, pady=5, sticky='nswe') mainFrame.rowconfigure(1, weight=1) mainFrame.columnconfigure(0, weight=1) optionsFrame = Frame(mainFrame) optionsFrame.grid(row=0, column=0, pady=(5,0), sticky='nswe') optionsFrame.columnconfigure(0, pad=5) optionsFrame.columnconfigure(1, weight=1) optionsFrame.columnconfigure(2, pad=5) browserFrame = Frame(mainFrame) browserFrame.grid(row=1, column=0, pady=(5,0), sticky='nswe') browserFrame.rowconfigure(0, weight=1) browserFrame.columnconfigure(0, weight=1) actionFrame = Frame(mainFrame) actionFrame.grid(row=2, column=0, pady=(5,0), sticky='e') actionFrame.columnconfigure(0, weight=1) Label(optionsFrame, text="Secret").grid(row=0, column=0, sticky='w') self.secret = Entry(optionsFrame) self.secret.grid(row=0, column=1, sticky='nswe') self.secret.focus_set() self.secretToggleButton = Button(optionsFrame, text="Set", command=self.toggleSecret, width=8) self.secretToggleButton.grid(row=0, column=2, sticky='e') self.secretToggleButton.setvar('set', False) Label(optionsFrame, text="Encrypted Folder").grid(row=1, column=0, sticky='w') self.folderPath = Entry(optionsFrame, state='readonly') self.browseButton = Button(optionsFrame, text="Browse", command=self.chooseFolder, width=8) self.browseButton.grid(row=1, column=2, sticky='e') self.browseButton.configure(state='disabled') self.folderPath.grid(row=1, column=1, sticky='nswe') fileBrowserColumns = ('size', 'modified', 'encrypted name') self.fileBrowser = Treeview(browserFrame, columns=fileBrowserColumns) self.fileBrowser.heading('#0', text='File') for i in range(0, len(fileBrowserColumns)): self.fileBrowser.heading('#{0}'.format(i+1), text=fileBrowserColumns[i].title()) self.fileBrowser.grid(row=0, column=0, sticky='nswe') scrollBarY = Scrollbar(orient=VERTICAL, command=self.fileBrowser.yview) scrollBarX = Scrollbar(orient=HORIZONTAL, command=self.fileBrowser.xview) self.fileBrowser['yscroll'] = scrollBarY.set self.fileBrowser['xscroll'] = scrollBarX.set scrollBarY.grid(in_=browserFrame, row=0, column=1, sticky='ns') scrollBarX.grid(in_=browserFrame, row=1, column=0, sticky='ew') Button(actionFrame, text="Decrypt Selected", command=self.decryptSelected).grid(row=0, column=0) Button(actionFrame, text="Decrypt All", command=self.decryptAll).grid(row=0, column=1) self.root.mainloop() def chooseFolder(self): dirPath = askdirectory(parent=self.root, mustexist=True) if os.name == 'nt': dirPath = '\\\\?\\' + os.path.normpath(dirPath) self.folderPath.configure(state='normal') self.folderPath.delete(0, len(self.folderPath.get())) self.folderPath.insert(0, dirPath) self.folderPath.configure(state='readonly') self.initializeFileBrowser() def toggleSecret(self): if self.secretToggleButton.getvar('set'): self.secret.configure(state='normal') self.secretToggleButton.setvar('set', False) self.secretToggleButton.configure(text='Set') self.browseButton.configure(state='disabled') self.__clearFileBrowser() else: if self.secret.get() == '': showerror('Invalid Secret', 'The provided secret must not be empty.') return self.secret.configure(state='readonly') self.secretToggleButton.setvar('set', True) self.secretToggleButton.configure(text='Change') self.browseButton.configure(state='normal') self.decryptor = Encryption(self.secret.get(), None, None) def initializeFileBrowser(self): self.__clearFileBrowser() self.__initializeFileBrowser(self.decryptor, self.folderPath.get()) self.fileBrowser.focus_set() def decryptSelected(self): selectedItems = self.fileBrowser.selection() if len(selectedItems) < 1: return destinationFolder = self.__askForDestinationFolder() if destinationFolder is None: return transitiveSelectedItems = set() for selectedItem in selectedItems: transitiveSelectedItems.update(self.__getAllFileBrowserChildren(selectedItem)) filteredSelectedItems = filter(lambda x: not x in transitiveSelectedItems, selectedItems) for selectedItem in filteredSelectedItems: self.__decryptFileBrowserItemToDestination(selectedItem, destinationFolder) def decryptAll(self): destinationFolder = self.__askForDestinationFolder() if destinationFolder is None: return children = self.fileBrowser.get_children('') for child in children: self.__decryptFileBrowserItemToDestination(child, destinationFolder) def __getAllFileBrowserChildren(self, identifier): children = set() queue = list() queue.append(identifier) while len(queue) > 0: for item in self.fileBrowser.get_children(queue.pop()): children.add(item) queue.append(item) return children def __askForDestinationFolder(self): return askdirectory(parent=self.root, mustexist=True, title='Destination Folder') def __decryptFileBrowserItemToDestination(self, selectedItem, destinationFolder): decryptedFileName = self.fileBrowser.item(selectedItem, option='text') destinationFullPath = os.path.join(destinationFolder, decryptedFileName) encryptedFullPath = self.fileBrowserShadowInformation.get(selectedItem) if os.path.isdir(encryptedFullPath): self.__decryptDirToDestination(encryptedFullPath, destinationFullPath) else: self.__decryptFileToDestination(encryptedFullPath, destinationFullPath) def __decryptFileToDestination(self, encryptedFullPath, destinationPath): chunkSize = 4096 encryptedVirtualFile = None filename, segmentNumber = FilenameUtils.splitSegmentedFileName(encryptedFullPath) if segmentNumber is None: encryptedVirtualFile = VirtualFile(encryptedFullPath) else: encryptedVirtualFile = SegmentedVirtualFile(filename) try: decryptedFileSize = self.decryptor.decryptedFileSize(encryptedVirtualFile) with open(destinationPath, 'wb') as destinationFile: for offset in range(0, decryptedFileSize, chunkSize): destinationFile.write(self.decryptor.decryptedContent(encryptedVirtualFile, offset, chunkSize)) finally: encryptedVirtualFile.closeFileHandle() def __decryptDirToDestination(self, encryptedFullPath, destinationFullPath): if not os.path.isdir(destinationFullPath): os.mkdir(destinationFullPath) for entry in os.listdir(encryptedFullPath): entryEncryptedFullPath = os.path.join(encryptedFullPath, entry) decryptedEntryName = self.decryptor.decryptFileName(entry) entryDestinationFullPath = os.path.join(destinationFullPath, decryptedEntryName) if os.path.isdir(entryEncryptedFullPath): self.__decryptDirToDestination(entryEncryptedFullPath, entryDestinationFullPath) else: self.__decryptFileToDestination(entryEncryptedFullPath, entryDestinationFullPath) def __clearFileBrowser(self): for child in self.fileBrowser.get_children(): self.fileBrowser.delete(child) self.fileBrowserShadowInformation.clear() def __initializeFileBrowser(self, decryption, encryptedDir, parent=''): for entry in os.listdir(encryptedDir): # Check if file is segmented filename, segment = FilenameUtils.splitSegmentedFileName(entry) if segment is not None and segment is not 0: continue encryptedFullPathForEntry = os.path.join(encryptedDir, entry) decryptedName = decryption.decryptFileName(filename) identifier = None printableModificationDate = datetime.fromtimestamp(os.path.getmtime(encryptedFullPathForEntry)).strftime('%Y-%m-%d %H:%M:%S') fileSize = None if os.path.isfile(encryptedFullPathForEntry): encryptedVirtualFile = None if segment is not None: encryptedVirtualFile = SegmentedVirtualFile(os.path.join(encryptedDir, filename)) else: encryptedVirtualFile = VirtualFile(encryptedFullPathForEntry) fileSize = decryption.decryptedFileSize(encryptedVirtualFile) encryptedVirtualFile.closeFileHandle() identifier = self.fileBrowser.insert(parent, 'end', text=decryptedName) else: identifier = self.fileBrowser.insert(parent, 'end', text=decryptedName) self.__initializeFileBrowser(decryption, encryptedFullPathForEntry, identifier) fileSize = os.path.getsize(encryptedFullPathForEntry) self.fileBrowser.set(identifier, 'size', hurry.filesize.size(fileSize)) self.fileBrowser.set(identifier, 'modified', printableModificationDate) self.fileBrowser.set(identifier, 'encrypted name', entry) self.fileBrowserShadowInformation[identifier] = encryptedFullPathForEntry
class DialogPluginManager(Toplevel): def __init__(self, mainWin, modulesWithNewerFileDates): super(DialogPluginManager, self).__init__(mainWin.parent) self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin # copy plugins for temporary display self.pluginConfig = PluginManager.pluginConfig self.pluginConfigChanged = False self.uiClassMethodsChanged = False self.modelClassesChanged = False self.disclosureSystemTypesChanged = False self.hostSystemFeaturesChanged = False self.modulesWithNewerFileDates = modulesWithNewerFileDates parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Plug-in Manager")) frame = Frame(self) # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) addLabel = Label(buttonFrame, text=_("Find plug-in modules:"), wraplength=60, justify="center") addLocalButton = Button(buttonFrame, text=_("Locally"), command=self.findLocally) ToolTip(addLocalButton, text=_("File chooser allows selecting python module files to add (or reload) plug-ins, from the local file system."), wraplength=240) addWebButton = Button(buttonFrame, text=_("On Web"), command=self.findOnWeb) ToolTip(addWebButton, text=_("Dialog to enter URL full path to load (or reload) plug-ins, from the web or local file system."), wraplength=240) addLabel.grid(row=0, column=0, pady=4) addLocalButton.grid(row=1, column=0, pady=4) addWebButton.grid(row=2, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=2, sticky=(N, S, W), padx=3, pady=3) # right tree frame (plugins already known to arelle) modulesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(modulesFrame, orient=VERTICAL) hScrollbar = Scrollbar(modulesFrame, orient=HORIZONTAL) self.modulesView = Treeview(modulesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=7) self.modulesView.grid(row=0, column=0, sticky=(N, S, E, W)) self.modulesView.bind('<<TreeviewSelect>>', self.moduleSelect) hScrollbar["command"] = self.modulesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.modulesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) modulesFrame.columnconfigure(0, weight=1) modulesFrame.rowconfigure(0, weight=1) modulesFrame.grid(row=0, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.modulesView.focus_set() self.modulesView.column("#0", width=120, anchor="w") self.modulesView.heading("#0", text=_("Name")) self.modulesView["columns"] = ("author", "ver", "status", "date", "update", "descr", "license") self.modulesView.column("author", width=100, anchor="w", stretch=False) self.modulesView.heading("author", text=_("Author")) self.modulesView.column("ver", width=50, anchor="w", stretch=False) self.modulesView.heading("ver", text=_("Version")) self.modulesView.column("status", width=50, anchor="w", stretch=False) self.modulesView.heading("status", text=_("Status")) self.modulesView.column("date", width=70, anchor="w", stretch=False) self.modulesView.heading("date", text=_("File Date")) self.modulesView.column("update", width=50, anchor="w", stretch=False) self.modulesView.heading("update", text=_("Update")) self.modulesView.column("descr", width=200, anchor="w", stretch=False) self.modulesView.heading("descr", text=_("Description")) self.modulesView.column("license", width=70, anchor="w", stretch=False) self.modulesView.heading("license", text=_("License")) classesFrame = Frame(frame) vScrollbar = Scrollbar(classesFrame, orient=VERTICAL) hScrollbar = Scrollbar(classesFrame, orient=HORIZONTAL) self.classesView = Treeview(classesFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set, height=5) self.classesView.grid(row=0, column=0, sticky=(N, S, E, W)) hScrollbar["command"] = self.classesView.xview hScrollbar.grid(row=1, column=0, sticky=(E,W)) vScrollbar["command"] = self.classesView.yview vScrollbar.grid(row=0, column=1, sticky=(N,S)) classesFrame.columnconfigure(0, weight=1) classesFrame.rowconfigure(0, weight=1) classesFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.classesView.focus_set() self.classesView.column("#0", width=200, anchor="w") self.classesView.heading("#0", text=_("Class")) self.classesView["columns"] = ("modules",) self.classesView.column("modules", width=500, anchor="w", stretch=False) self.classesView.heading("modules", text=_("Modules")) # bottom frame module info details moduleInfoFrame = Frame(frame, width=700) moduleInfoFrame.columnconfigure(1, weight=1) self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.moduleNameLabel.grid(row=0, column=0, columnspan=4, sticky=W) self.moduleAuthorHdr = Label(moduleInfoFrame, text=_("author:"), state=DISABLED) self.moduleAuthorHdr.grid(row=1, column=0, sticky=W) self.moduleAuthorLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleAuthorLabel.grid(row=1, column=1, columnspan=3, sticky=W) self.moduleDescrHdr = Label(moduleInfoFrame, text=_("description:"), state=DISABLED) self.moduleDescrHdr.grid(row=2, column=0, sticky=W) self.moduleDescrLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDescrLabel.grid(row=2, column=1, columnspan=3, sticky=W) self.moduleClassesHdr = Label(moduleInfoFrame, text=_("classes:"), state=DISABLED) self.moduleClassesHdr.grid(row=3, column=0, sticky=W) self.moduleClassesLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleClassesLabel.grid(row=3, column=1, columnspan=3, sticky=W) ToolTip(self.moduleClassesLabel, text=_("List of classes that this plug-in handles."), wraplength=240) self.moduleUrlHdr = Label(moduleInfoFrame, text=_("URL:"), state=DISABLED) self.moduleUrlHdr.grid(row=4, column=0, sticky=W) self.moduleUrlLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleUrlLabel.grid(row=4, column=1, columnspan=3, sticky=W) ToolTip(self.moduleUrlLabel, text=_("URL of plug-in module (local file path or web loaded file)."), wraplength=240) self.moduleDateHdr = Label(moduleInfoFrame, text=_("date:"), state=DISABLED) self.moduleDateHdr.grid(row=5, column=0, sticky=W) self.moduleDateLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleDateLabel.grid(row=5, column=1, columnspan=3, sticky=W) ToolTip(self.moduleDateLabel, text=_("Date of currently loaded module file (with parenthetical node when an update is available)."), wraplength=240) self.moduleLicenseHdr = Label(moduleInfoFrame, text=_("license:"), state=DISABLED) self.moduleLicenseHdr.grid(row=6, column=0, sticky=W) self.moduleLicenseLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleLicenseLabel.grid(row=6, column=1, columnspan=3, sticky=W) self.moduleImportsHdr = Label(moduleInfoFrame, text=_("imports:"), state=DISABLED) self.moduleImportsHdr.grid(row=7, column=0, sticky=W) self.moduleImportsLabel = Label(moduleInfoFrame, wraplength=600, justify="left") self.moduleImportsLabel.grid(row=7, column=1, columnspan=3, sticky=W) self.moduleEnableButton = Button(moduleInfoFrame, text=self.ENABLE, state=DISABLED, command=self.moduleEnable) ToolTip(self.moduleEnableButton, text=_("Enable/disable plug in."), wraplength=240) self.moduleEnableButton.grid(row=8, column=1, sticky=E) self.moduleReloadButton = Button(moduleInfoFrame, text=_("Reload"), state=DISABLED, command=self.moduleReload) ToolTip(self.moduleReloadButton, text=_("Reload/update plug in."), wraplength=240) self.moduleReloadButton.grid(row=8, column=2, sticky=E) self.moduleRemoveButton = Button(moduleInfoFrame, text=_("Remove"), state=DISABLED, command=self.moduleRemove) ToolTip(self.moduleRemoveButton, text=_("Remove plug in from plug in table (does not erase the plug in's file)."), wraplength=240) self.moduleRemoveButton.grid(row=8, column=3, sticky=E) moduleInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) moduleInfoFrame.config(borderwidth=4, relief="groove") okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S,W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) ToolTip(enableAllButton, text=_("Enable all plug ins."), wraplength=240) disableAllButton = Button(enableDisableFrame, text=_("Disable All"), command=self.disableAll) ToolTip(disableAllButton, text=_("Disable all plug ins."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(0, weight=0) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) self.bind("<Return>", self.ok) self.bind("<Escape>", self.close) self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries for previousNode in self.modulesView.get_children(""): self.modulesView.delete(previousNode) def loadSubtree(parentNode, moduleItems): for moduleItem in sorted(moduleItems, key=lambda item: item[0]): moduleInfo = moduleItem[1] if parentNode or not moduleInfo.get("isImported"): nodeName = moduleItem[0] if parentNode: nodeName = parentNode + GROUPSEP + nodeName name = moduleInfo.get("name", nodeName) node = self.modulesView.insert(parentNode, "end", nodeName, text=name) self.modulesView.set(node, "author", moduleInfo.get("author")) self.modulesView.set(node, "ver", moduleInfo.get("version")) self.modulesView.set(node, "status", moduleInfo.get("status")) self.modulesView.set(node, "date", moduleInfo.get("fileDate")) if name in self.modulesWithNewerFileDates: self.modulesView.set(node, "update", _("available")) self.modulesView.set(node, "descr", moduleInfo.get("description")) self.modulesView.set(node, "license", moduleInfo.get("license")) if moduleInfo.get("imports"): loadSubtree(node, [(importModuleInfo["name"],importModuleInfo) for importModuleInfo in moduleInfo["imports"]]) loadSubtree("", self.pluginConfig.get("modules", {}).items()) # clear previous treeview entries for previousNode in self.classesView.get_children(""): self.classesView.delete(previousNode) for i, classItem in enumerate(sorted(self.pluginConfig.get("classes", {}).items())): className, moduleList = classItem node = self.classesView.insert("", "end", className, text=className) self.classesView.set(node, "modules", ', '.join(moduleList)) self.moduleSelect() # clear out prior selection def ok(self, event=None): if self.pluginConfigChanged: PluginManager.pluginConfig = self.pluginConfig PluginManager.pluginConfigChanged = True PluginManager.reset() # force reloading of modules if self.uiClassMethodsChanged or self.modelClassesChanged or self.disclosureSystemTypesChanged or self.hostSystemFeaturesChanged: # may require reloading UI affectedItems = "" if self.uiClassMethodsChanged: affectedItems += _("menus of the user interface") if self.modelClassesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("model objects of the processor") if self.disclosureSystemTypesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("disclosure system types") if self.hostSystemFeaturesChanged: if affectedItems: affectedItems += _(" and ") affectedItems += _("host system features") if messagebox.askyesno(_("User interface plug-in change"), _("A change in plug-in class methods may have affected {0}. " "Please restart Arelle to due to these changes. \n\n" "Should Arelle restart itself now " "(if there are any unsaved changes they would be lost!)?" ).format(affectedItems), parent=self): self.cntlr.uiThreadQueue.put((self.cntlr.quit, [None, True])) self.close() def close(self, event=None): self.parent.focus_set() self.destroy() def moduleSelect(self, *args): node = (self.modulesView.selection() or (None,))[0] if node: node = node.rpartition(GROUPSEP)[2] # drop leading path names for module name moduleInfo = self.pluginConfig.get("modules", {}).get(node) if moduleInfo: self.selectedModule = node name = moduleInfo["name"] self.moduleNameLabel.config(text=name) self.moduleAuthorHdr.config(state=ACTIVE) self.moduleAuthorLabel.config(text=moduleInfo["author"]) self.moduleDescrHdr.config(state=ACTIVE) self.moduleDescrLabel.config(text=moduleInfo["description"]) self.moduleClassesHdr.config(state=ACTIVE) self.moduleClassesLabel.config(text=', '.join(moduleInfo["classMethods"])) self.moduleUrlHdr.config(state=ACTIVE) self.moduleUrlLabel.config(text=moduleInfo["moduleURL"]) self.moduleDateHdr.config(state=ACTIVE) self.moduleDateLabel.config(text=moduleInfo["fileDate"] + " " + (_("(an update is available)") if name in self.modulesWithNewerFileDates else "")) self.moduleLicenseHdr.config(state=ACTIVE) self.moduleLicenseLabel.config(text=moduleInfo["license"]) if moduleInfo.get("imports"): self.moduleImportsHdr.config(state=ACTIVE) _text = ", ".join(mi["name"] for mi in moduleInfo["imports"][:3]) if len(moduleInfo["imports"]) >= 3: _text += ", ..." self.moduleImportsLabel.config(text=_text) _buttonState = DISABLED if moduleInfo.get("isImported") else ACTIVE self.moduleEnableButton.config(state=_buttonState, text={"enabled":self.DISABLE, "disabled":self.ENABLE}[moduleInfo["status"]]) self.moduleReloadButton.config(state=_buttonState) self.moduleRemoveButton.config(state=_buttonState) else: self.selectedModule = None self.moduleNameLabel.config(text="") self.moduleAuthorHdr.config(state=DISABLED) self.moduleAuthorLabel.config(text="") self.moduleDescrHdr.config(state=DISABLED) self.moduleDescrLabel.config(text="") self.moduleClassesHdr.config(state=DISABLED) self.moduleClassesLabel.config(text="") self.moduleUrlHdr.config(state=DISABLED) self.moduleUrlLabel.config(text="") self.moduleDateHdr.config(state=DISABLED) self.moduleDateLabel.config(text="") self.moduleLicenseHdr.config(state=DISABLED) self.moduleLicenseLabel.config(text="") self.moduleImportsHdr.config(state=DISABLED) self.moduleImportsLabel.config(text="") self.moduleEnableButton.config(state=DISABLED, text=self.ENABLE) self.moduleReloadButton.config(state=DISABLED) self.moduleRemoveButton.config(state=DISABLED) def findLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory initialdir = self.cntlr.config.setdefault("pluginOpenDir", initialdir) filename = self.cntlr.uiFileDialog("open", parent=self, title=_("Choose plug-in module file"), initialdir=initialdir, filetypes=[(_("Python files"), "*.py")], defaultextension=".py") if filename: # check if a package is selected (any file in a directory containing an __init__.py #if (os.path.basename(filename) == "__init__.py" and os.path.isdir(os.path.dirname(filename)) and # os.path.isfile(filename)): # filename = os.path.dirname(filename) # refer to the package instead self.cntlr.config["pluginOpenDir"] = os.path.dirname(filename) moduleInfo = PluginManager.moduleModuleInfo(filename) self.loadFoundModuleInfo(moduleInfo, filename) def findOnWeb(self): url = DialogURL.askURL(self) if url: # url is the in-cache or local file moduleInfo = PluginManager.moduleModuleInfo(url) self.cntlr.showStatus("") # clear web loading status self.loadFoundModuleInfo(moduleInfo, url) def loadFoundModuleInfo(self, moduleInfo, url): if moduleInfo and moduleInfo.get("name"): self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() else: messagebox.showwarning(_("Module is not itself a plug-in or in a directory with package __init__.py plug-in. "), _("File does not itself contain a python program with an appropriate __pluginInfo__ declaration: \n\n{0}") .format(url), parent=self) def checkIfImported(self, moduleInfo): if moduleInfo.get("isImported"): messagebox.showwarning(_("Plug-in is imported by a parent plug-in. "), _("Plug-in has a parent, please request operation on the parent: \n\n{0}") .format(moduleInfo.get("name")), parent=self) return True return False def removePluginConfigModuleInfo(self, name): moduleInfo = self.pluginConfig["modules"].get(name) if moduleInfo: if self.checkIfImported(moduleInfo): return; def _removePluginConfigModuleInfo(moduleInfo): _name = moduleInfo.get("name") if _name: for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].get(classMethod) if classMethods and _name in classMethods: classMethods.remove(_name) if not classMethods: # list has become unused del self.pluginConfig["classes"][classMethod] # remove class if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI elif classMethod == "ModelObjectFactory.ElementSubstitutionClasses": self.modelClassesChanged = True # model object factor classes changed elif classMethod == "DisclosureSystem.Types": self.disclosureSystemTypesChanged = True # disclosure system types changed elif classMethod.startswith("Proxy."): self.hostSystemFeaturesChanged = True # system features (e.g., proxy) changed for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _removePluginConfigModuleInfo(importModuleInfo) self.pluginConfig["modules"].pop(_name, None) _removePluginConfigModuleInfo(moduleInfo) self.pluginConfigChanged = True def addPluginConfigModuleInfo(self, moduleInfo): if self.checkIfImported(moduleInfo): return; name = moduleInfo.get("name") self.removePluginConfigModuleInfo(name) # remove any prior entry for this module def _addPlugin(moduleInfo): _name = moduleInfo.get("name") if _name: self.modulesWithNewerFileDates.discard(_name) # no longer has an update available self.pluginConfig["modules"][_name] = moduleInfo # add classes for classMethod in moduleInfo["classMethods"]: classMethods = self.pluginConfig["classes"].setdefault(classMethod, []) if name not in classMethods: classMethods.append(_name) if classMethod.startswith("CntlrWinMain.Menu"): self.uiClassMethodsChanged = True # may require reloading UI elif classMethod == "ModelObjectFactory.ElementSubstitutionClasses": self.modelClassesChanged = True # model object factor classes changed elif classMethod == "DisclosureSystem.Types": self.disclosureSystemTypesChanged = True # disclosure system types changed elif classMethod.startswith("Proxy."): self.hostSystemFeaturesChanged = True # system features (e.g., proxy) changed for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _addPlugin(importModuleInfo) _addPlugin(moduleInfo) self.pluginConfigChanged = True def moduleEnable(self): if self.selectedModule in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][self.selectedModule] if self.checkIfImported(moduleInfo): return; def _moduleEnable(moduleInfo): if self.moduleEnableButton['text'] == self.ENABLE: moduleInfo["status"] = "enabled" elif self.moduleEnableButton['text'] == self.DISABLE: moduleInfo["status"] = "disabled" for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _moduleEnable(importModuleInfo) _moduleEnable(moduleInfo) if self.moduleEnableButton['text'] == self.ENABLE: self.moduleEnableButton['text'] = self.DISABLE elif self.moduleEnableButton['text'] == self.DISABLE: self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() def moduleReload(self): if self.selectedModule in self.pluginConfig["modules"]: url = self.pluginConfig["modules"][self.selectedModule].get("moduleURL") if url: moduleInfo = PluginManager.moduleModuleInfo(url, reload=True) if moduleInfo: if self.checkIfImported(moduleInfo): return; self.addPluginConfigModuleInfo(moduleInfo) self.loadTreeViews() self.cntlr.showStatus(_("{0} reloaded").format(moduleInfo["name"]), clearAfter=5000) else: messagebox.showwarning(_("Module error"), _("File or module cannot be reloaded: \n\n{0}") .format(url), parent=self) def moduleRemove(self): if self.selectedModule in self.pluginConfig["modules"]: self.removePluginConfigModuleInfo(self.selectedModule) self.pluginConfigChanged = True self.loadTreeViews() def enableAll(self): self.enableDisableAll(True) def disableAll(self): self.enableDisableAll(False) def enableDisableAll(self, doEnable): for module in self.pluginConfig["modules"]: if not module.get("isImported"): moduleInfo = self.pluginConfig["modules"][module] def _enableDisableAll(moduleInfo): if doEnable: moduleInfo["status"] = "enabled" else: moduleInfo["status"] = "disabled" for importModuleInfo in moduleInfo.get("imports", EMPTYLIST): _enableDisableAll(importModuleInfo) _enableDisableAll(moduleInfo) if doEnable: self.moduleEnableButton['text'] = self.DISABLE else: self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews()