class _List(WidgetBase): def __init__(self, master, **options): super().__init__(master) from tkinter.ttk import Treeview self.widget = Treeview(self.master, show="tree", **options) self.widget.bind("<<TreeviewSelect>>", self.callback) self.value = () def set_header(self, column, text): self.widget.heading("#" + str(column), text=text) def add_item(self, parent="", index=0, id=None, label="", values=None, **options): if not id is None: options.update(iid=id) if not values is None: options.update(values=values) self.widget.insert(parent=parent, index=index, text=label, **options) def callback(self, event): self.value = self.widget.selection() def set_selection(self, items): self.value = items self.widget.selection_set(items) def exist_item(self, id): return self.widget.exists(id)
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 Configurator(tk.Tk): """ The main Tk window representing the main app. Attributes ---------- treeview : :py:class:`~tkinter.Treeview` The treeview widget. treeview_popup_target_id : `int` The pop target id relating to the id of the selected element. treeview_popup : :py:class:`~tkinter.Widget` The treeview popup widget. cfg_file_name : `str` The file name of the current configuration. element_dict : `dict` The dictionary of elements. Keys are the element ids. root_element : :py:class:`~enrich2.base.storemanager.StoreManager` An instance inheriting from storemanager that acts as a root object. force_recalculate :py:class:`tkinter.BooleanVar` The tkinter boolean variable for this option. component_outliers :py:class:`tkinter.BooleanVar` The tkinter boolean variable for this option. tsv_requested : :py:class:`tkinter.BooleanVar` The tkinter boolean variable for this option. treeview_buttons : `list` The ``new``, ``edit`` and ``delete`` buttons. go_button : :py:class`~tkinter.ttk.Button` The button that begins the analysis scorer_widget : :py:class:`~enrich2.gui.options_frame.ScorerScriptsDropDown` The ScorerScriptsDropDown instance associated with this app. scorer : :py:class:`~enrich2.plugins.scoring.BaseScorerPlugin` The scorer class loaded from a plugin scorer_attrs : `dict` The scoring attributes for the plugin. scorer_path : `str` The path to the currently selected scoring plugin. analysis_thread : :py:class:`~threading.Thread` The thread object that runs the computation method to prevent GUI blocking. Methods ------- create_main_frame create_menubar create_treeview_context_menu create_new_element menu_open menu_save menu_saveas menu_selectall refresh_treeview treeview_context_menu set_treeview_properties populate_tree go_button_press new_button_press edit_button_press delete_button_press delete_element apply_seqlib_fastq get_element get_focused_element get_selected_elements get_selected_scorer_class get_selected_scorer_attrs get_selected_scorer_path run_analysis set_gui_state configure_analysis refresh_plugins show_plugin_source_window See Also -------- :py:class:`~tkinter.Tk` """ def __init__(self): tk.Tk.__init__(self) self.title("Enrich 2") # Main app variables self.cfg_file_name = tk.StringVar() self.element_dict = dict() self.root_element = None self.analysis_thread = None self.plugin_source_window = None self.queue = queue.Queue() # Treeview variables self.treeview = None self.treeview_popup_target_id = None self.treeview_popup = None # analysis options self.force_recalculate = tk.BooleanVar() self.component_outliers = tk.BooleanVar() self.tsv_requested = tk.BooleanVar() # allow resizing self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) # create UI elements self.treeview_buttons = [] self.go_button = None self.scorer_widget = None self.scorer = None self.scorer_attrs = None self.scorer_path = None self.create_main_frame() self.create_menubar() self.create_treeview_context_menu() self.after(10, self.poll_logging_queue) self.plugin_source_window = SourceWindow(master=self) self.plugin_source_window.hide() self.refresh_plugins() # ---------------------------------------------------------------------- # # Creation Methods # ---------------------------------------------------------------------- # def create_treeview_context_menu(self): """ This creates the tree-like view rendering the experiment heirachy. """ self.treeview_popup = tk.Menu(self, tearoff=0) self.treeview_popup.add_command(label="Apply FASTQ...", command=self.apply_seqlib_fastq) def create_main_frame(self): """ Large function creating all the basic elements of the main app frame. Creates the treeview and associated buttons, the scoring plugin frame and the go button. """ # Frame for the Treeview and New/Edit/Delete buttons main = Frame(self, padding=(3, 3, 12, 12)) main.rowconfigure(0, weight=1) main.columnconfigure(0, weight=1) main.columnconfigure(1, weight=0) main.grid(row=0, column=0, sticky="nsew") # ------------------------------------------------------- # # Frame for the Treeview and its scrollbars tree_frame = Frame(main, padding=(3, 3, 12, 12)) tree_frame.rowconfigure(0, weight=1) tree_frame.rowconfigure(1, weight=0) tree_frame.columnconfigure(0, weight=1) tree_frame.columnconfigure(1, weight=0) tree_frame.grid(row=0, column=0, sticky="nsew") # ------------------------------------------------------- # # Treeview with column headings self.treeview = Treeview(tree_frame) self.treeview["columns"] = ("class", "barcodes", "variants") self.treeview.column("class", width=120) self.treeview.heading("class", text="Type") self.treeview.column("barcodes", width=25, stretch=tk.NO, anchor=tk.CENTER) self.treeview.heading("barcodes", text="BC") self.treeview.column("variants", width=25, stretch=tk.NO, anchor=tk.CENTER) self.treeview.heading("variants", text="V") self.treeview.grid(row=0, column=0, sticky="nsew") # Treeview context menu bindings self.treeview.bind("<Button-2>", self.treeview_context_menu) # Treeview scrollbars tree_ysb = tk.Scrollbar(tree_frame, orient="vertical", command=self.treeview.yview) tree_xsb = tk.Scrollbar(tree_frame, orient="horizontal", command=self.treeview.xview) tree_ysb.grid(row=0, column=1, sticky="nsw") tree_xsb.grid(row=1, column=0, sticky="ewn") self.treeview.config(yscroll=tree_ysb.set, xscroll=tree_xsb.set) # ------------------------------------------------------- # # Frame for New/Edit/Delete buttons button_frame = Frame(main, padding=(3, 3, 12, 12)) button_frame.grid(row=1, column=0) new_button = Button(button_frame, text="New...", command=self.new_button_press) new_button.grid(row=0, column=0) edit_button = Button(button_frame, text="Edit...", command=self.edit_button_press) edit_button.grid(row=0, column=1) delete_button = Button(button_frame, text="Delete", command=self.delete_button_press) delete_button.grid(row=0, column=2) self.treeview_buttons = [new_button, delete_button, edit_button] # ------------------------------------------------------- # # Frame for Plugin and Analysis Options right_frame = Frame(main, padding=(3, 3, 12, 12)) right_frame.rowconfigure(0, weight=1) right_frame.rowconfigure(1, weight=0) right_frame.columnconfigure(0, weight=1) right_frame.columnconfigure(1, weight=0) right_frame.grid(row=0, column=1, sticky="new") # ------------------------------------------------------- # # LabelFrame for plugin and options scoring_plugin = ScorerScriptsDropDown(right_frame, text="Scoring Options", padding=(3, 3, 12, 12)) scoring_plugin.grid(row=0, column=0, sticky="new") self.scorer_widget = scoring_plugin # ------------------------------------------------------- # # LabelFrame for Analysis Options row = 0 options_frame = LabelFrame(right_frame, text="Analysis Options", padding=(3, 3, 12, 12)) options_frame.grid(row=1, column=0, sticky="new", pady=4) # force recalculate force_recalculate = Checkbutton(options_frame, text="Force Recalculation", variable=self.force_recalculate) force_recalculate.grid(column=0, row=row, sticky="w") row += 1 # component outliers component_outliers = Checkbutton( options_frame, text="Component Outlier Statistics", variable=self.component_outliers, ) component_outliers.grid(column=0, row=row, sticky="w") row += 1 # write tsv tsv_requested = Checkbutton(options_frame, text="Write TSV Files", variable=self.tsv_requested) tsv_requested.grid(column=0, row=row, sticky="w") tsv_requested.invoke() row += 1 # ------------------------------------------------------- # # Run Analysis button frame go_button_frame = Frame(main, padding=(3, 3, 12, 12)) go_button_frame.grid(row=1, column=1) go_button = Button(go_button_frame, text="Run Analysis", command=self.go_button_press) go_button.grid(column=0, row=0) self.go_button = go_button def create_new_element(self): """ Create and return a new element based on the current selection. This element is not added to the treeview. """ element = None parent_element = self.get_focused_element() if isinstance(parent_element, Experiment): element = Condition() element.parent = parent_element elif isinstance(parent_element, Condition): element = Selection() element.parent = parent_element elif isinstance(parent_element, Selection): element = CreateSeqLibDialog(self).element_type() element.parent = parent_element elif isinstance(parent_element, SeqLib): # special case: creates a copy of the selected SeqLib as a sibling element = type(parent_element)() element.configure(parent_element.serialize()) element.parent = parent_element.parent # clear out the seqlib-specific values element.name = None element.timepoint = None element.counts_file = None element.reads = None else: raise ValueError("Unrecognized parent object " "type '{}'".format(type(parent_element))) return element def create_menubar(self): """ Creates the menubar for the main app, with associated drop down menus. """ # make platform-specific keybinds if platform.system() == "Darwin": accel_string = "Command+" accel_bind = "Command-" else: accel_string = "Ctrl+" accel_bind = "Control-" # create the menubar menubar = tk.Menu(self) # file menu filemenu = tk.Menu(menubar, tearoff=0) filemenu.add_command( label="Open...", accelerator="{}O".format(accel_string), command=self.menu_open, ) filemenu.add_command(label="Save", accelerator="{}S".format(accel_string), command=self.menu_save) filemenu.add_command( label="Save As...", accelerator="{}Shift+S".format(accel_string), command=self.menu_saveas, ) menubar.add_cascade(label="File", menu=filemenu) # edit menu filemenu = tk.Menu(menubar, tearoff=0) filemenu.add_command( label="Select All", accelerator="{}A".format(accel_string), command=self.menu_selectall, ) menubar.add_cascade(label="Edit", menu=filemenu) # tools menu filemenu = tk.Menu(menubar, tearoff=0) filemenu.add_command( label="Show Log", accelerator="{}L".format(accel_string), command=show_log_window, ) filemenu.add_command( label="Plugin Sources", accelerator="{}P".format(accel_string), command=self.show_plugin_source_window, ) filemenu.add_command( label="Refresh Plugins", accelerator="{}R".format(accel_string), command=self.refresh_plugins, ) menubar.add_cascade(label="Tools", menu=filemenu) # add the menubar self.config(menu=menubar) # add file menu keybinds self.bind("<{}o>".format(accel_bind), lambda event: self.menu_open()) self.bind("<{}s>".format(accel_bind), lambda event: self.menu_save()) self.bind("<{}Shift-s>".format(accel_bind), lambda event: self.menu_saveas()) # add edit menu keybinds self.bind("<{}a>".format(accel_bind), lambda event: self.menu_selectall()) # add show log menu keybinds # add edit menu keybinds self.bind("<{}l>".format(accel_bind), lambda event: show_log_window()) self.bind("<{}p>".format(accel_bind), lambda event: self.show_plugin_source_window()) self.bind("<{}r>".format(accel_bind), lambda event: self.refresh_plugins()) # ---------------------------------------------------------------------- # # Treeview Methods # ---------------------------------------------------------------------- # def treeview_context_menu(self, click): """ Sets the currently selected treeview object id in the variable ``treeview_popup_target_id``. Parameters ---------- click : tkinter click event """ target = self.treeview.identify_row(click.y) if target != "": self.treeview_popup_target_id = target self.treeview_popup.post(click.x_root, click.y_root) self.treeview_popup_target_id = None def apply_seqlib_fastq(self): """ Applies settings to the seqlib object by running the configuration method. """ SeqLibApplyDialog(self, self, self.treeview_popup_target_id) def new_button_press(self): """ Spawns a dialog box depending on the currently selected treeview item to create a new element. """ if self.treeview.focus() == "" and self.root_element is not None: tkinter.messagebox.showwarning(None, "No parent element selected.") else: if self.treeview.focus() == "" and self.root_element is None: element = CreateRootDialog(self).element if isinstance(element, SeqLib): EditDialog(self, self, element) self.root_element = element else: element = self.create_new_element() EditDialog(self, self, element) # refresh the treeview and re-assign treeview id's self.refresh_treeview() # select the newly added element if it was successfully added if element.treeview_id in list(self.element_dict.keys()): self.treeview.focus(element.treeview_id) self.treeview.selection_set(element.treeview_id) else: if element.parent is not None: self.treeview.focus(element.parent.treeview_id) self.treeview.selection_set(element.parent.treeview_id) del element def edit_button_press(self): """ Spawns a dialog box depending on the currently selected treeview item to edit the selected element. """ if self.treeview.focus() == "": tkinter.messagebox.showwarning(None, "No element selected.") else: EditDialog(self, self, self.get_focused_element()) def delete_button_press(self): """ Deletes the selected treeview element and it's children. """ if self.treeview.focus() == "": tkinter.messagebox.showwarning(None, "No element selected.") else: DeleteDialog(self, self) def delete_element(self, tree_id): """ Delete element with Treeview id *tree_id* from the tree, from the element dictionary, and from the associated data structure. Recursively deletes all children of *tree_id*. The tree should be refreshed using :py:meth:`refresh_tree` after each deletion. This is the responsibility of the caller. Parameters ---------- tree_id : `int` The id of the currently selected treeview element. """ if tree_id in self.element_dict: # recursively delete children if self.element_dict[tree_id].children is not None: for child in self.element_dict[tree_id].children: self.delete_element(child.treeview_id) # check if deleting the root element if self.root_element.treeview_id == tree_id: # clear the root element print("None {}".format(tree_id)) self.root_element = None else: try: # remove the element from its parent's list of children self.element_dict[tree_id].parent.remove_child_id(tree_id) except AttributeError: raise AttributeError( "Non-root element lacks proper parent") # delete the element from the dictionary del self.element_dict[tree_id] def refresh_treeview(self): """ Clears the Treeview and repopulates it with the current contents of the tree. """ # clear the entries in the Treeview for x in self.treeview.get_children(): self.treeview.delete(x) # clear the id-element dictionary # elements may be given new id's after repopulation self.element_dict.clear() # repopulate if self.root_element is not None: self.populate_tree(self.root_element) def set_treeview_properties(self, element): """ Set the information text for the Treeview *element*. Parameters ---------- element : :py:class:`~enrich2.base.storemanager.StoreManager` The storemanager object to configure. """ # set class property self.treeview.set(element.treeview_id, "class", element.treeview_class_name) # add the check marks for barcodes/variants if "variants" in element.labels: self.treeview.set(element.treeview_id, "variants", u"\u2713") else: self.treeview.set(element.treeview_id, "variants", "") if "barcodes" in element.labels: self.treeview.set(element.treeview_id, "barcodes", u"\u2713") else: self.treeview.set(element.treeview_id, "barcodes", "") self.treeview.set(element.treeview_id, "class", element.treeview_class_name) def populate_tree(self, element, parent_id=""): """ Recursively populate the Treeview. Also populates the *id_cfgstrings*. Parameters ---------- element : :py:class:`~enrich2.base.storemanager.StoreManager` The storemanager object to configure. parent_id : `int` ``treeview_id`` of element's parent. """ # insert into the Treeview element.treeview_id = self.treeview.insert(parent_id, "end", text=element.name, open=True) # add id-element pair to dictionary self.element_dict[element.treeview_id] = element # set information fields self.set_treeview_properties(element) # populate for children if element.children is not None: for child in element.children: self.populate_tree(child, parent_id=element.treeview_id) # ---------------------------------------------------------------------- # # Getter Methods # ---------------------------------------------------------------------- # def get_selected_scorer_class(self): """ Returns the currently selected scoring class object. """ return self.scorer def get_selected_scorer_attrs(self): """ Returns the currently selected scoring class attribute `dict`. """ return self.scorer_attrs def get_selected_scorer_path(self): """ Returns the currently selected scoring path. """ return self.scorer_path def get_element(self, treeview_id): """ Returns the element with *treeview_id*. Parameters ---------- treeview_id : `int` ``treeview_id`` attribute of element to get. Returns ------- :py:class:`~enrich2.base.storemanager.StoreManager` The instance with matching ``treeview_id`` """ return self.element_dict[treeview_id] def get_focused_element(self): """ Gets the focused element in the treeview. Returns ------- :py:class:`~enrich2.base.storemanager.StoreManager` Returns the element that is currently being focused in the Treeview. ``None`` if nothing is focused. """ if self.treeview.focus() != "": return self.get_element(self.treeview.focus()) else: return None def get_selected_elements(self): """ Returns a list of currently selected elements in the treeview. Returns ------- `list` Returns a list of elements that are currently selected in the Treeview. If no elements are selected, it returns an empty list. """ return [self.get_element(x) for x in self.treeview.selection()] # ---------------------------------------------------------------------- # # Menubar Methods # ---------------------------------------------------------------------- # def menu_open(self): """ Spawns an `askopenfilename` dialog to open a configuration file. """ message_title = "Open Configuration" fname = tkinter.filedialog.askopenfilename() if len(fname) > 0: # file was selected try: with open(fname, "rU") as handle: cfg = json.load(handle) except ValueError: tkinter.messagebox.showerror(message_title, "Failed to parse config file.") except IOError: tkinter.messagebox.showerror(message_title, "Could not read config file.") else: if is_experiment(cfg): obj = Experiment() elif is_selection(cfg): obj = Selection() elif is_seqlib(cfg): sltype = seqlib_type(cfg) obj = globals()[sltype]() else: tkinter.messagebox.showerror( message_title, "Unrecognized config format.") return obj.output_dir_override = False try: if isinstance(obj, Experiment) or isinstance( obj, Selection): obj.configure(cfg, init_from_gui=True) else: obj.configure(cfg) # Try load the scorer into the GUI scorer_path = cfg.get(SCORER, {}).get(SCORER_PATH, "") scorer_attrs = cfg.get(SCORER, {}).get(SCORER_OPTIONS, {}) if scorer_path: self.scorer_widget.load_from_cfg_file( scorer_path, scorer_attrs) else: log_message( logging_callback=logging.warning, msg="No plugin could be loaded from configuration.", extra={"oname": self.__class__.__name__}, ) except Exception as e: tkinter.messagebox.showerror( message_title, "Failed to load config file:\n\n{}".format(e)) else: self.root_element = obj self.cfg_file_name.set(fname) self.refresh_treeview() def menu_save(self): """ Asks the user where to save the current configuration. """ if len(self.cfg_file_name.get()) == 0: self.menu_saveas() elif self.root_element is None: tkinter.messagebox.showwarning("Save Configuration", "Cannot save empty configuration.") else: save = askyesno("Save Configuration", "Overwrite existing configuration?") if not save: return try: with open(self.cfg_file_name.get(), "w") as handle: cfg = self.root_element.serialize() # Get the currently selected scorer if not isinstance(self.root_element, SeqLib) and not isinstance( self.root_element, Condition): ( _, attrs, scorer_path, ) = self.scorer_widget.get_scorer_class_attrs_path() cfg[SCORER] = { SCORER_PATH: scorer_path, SCORER_OPTIONS: attrs } write_json(cfg, handle) except IOError: tkinter.messagebox.showerror("Save Configuration", "Failed to save config file.") else: tkinter.messagebox.showinfo( "Save Configuration", "Saved file at location:\n\n{}".format( self.cfg_file_name.get()), ) def menu_saveas(self): """ Asks the user where to save the current configuration. """ if self.root_element is None: tkinter.messagebox.showwarning("Save Configuration", "Cannot save empty configuration.") else: fname = tkinter.filedialog.asksaveasfilename() if len(fname) > 0: # file was selected try: with open(fname, "w") as handle: cfg = self.root_element.serialize() # Get the currently selected scorer if not isinstance(self.root_element, SeqLib) and not isinstance( self.root_element, Condition): ( _, attrs, scorer_path, ) = self.scorer_widget.get_scorer_class_attrs_path( ) cfg[SCORER] = { SCORER_PATH: scorer_path, SCORER_OPTIONS: attrs, } write_json(cfg, handle) except IOError: tkinter.messagebox.showerror( "Save Configuration", "Failed to save config file.") else: self.cfg_file_name.set(fname) tkinter.messagebox.showinfo( "Save Configuration", "Saved file at location:\n\n{}".format( self.cfg_file_name.get()), ) def menu_selectall(self): """ Add all elements in the Treeview to the selection. """ for k in self.element_dict.keys(): self.treeview.selection_add(k) def show_plugin_source_window(self): """ Show the pop-up window to modify plugin sources """ if not self.plugin_source_window: self.plugin_source_window = SourceWindow(master=self) else: self.plugin_source_window.toggle_show() # ---------------------------------------------------------------------- # # Run Analysis Methods # ---------------------------------------------------------------------- # def go_button_press(self): """ Starts the analysis if all elements have been properly configured. This will run the analysis in a new thread and block out GUI editing to prevent the analysis breaking. """ ( self.scorer, self.scorer_attrs, self.scorer_path, ) = self.scorer_widget.get_scorer_class_attrs_path() if self.scorer is None or self.scorer_attrs is None: tkinter.messagebox.showwarning("Incomplete Configuration", "No scoring plugin selected.") elif self.root_element is None: tkinter.messagebox.showwarning( "Incomplete Configuration", "No experimental design specified.") else: plugin, *_ = self.scorer_widget.get_selected_plugin() if plugin.md5_has_changed(): proceed = askokcancel( "Selected plugin has been modified.", "The selected plugin has been modified on disk. Do you " "want to proceed with the current version? To see changes " "click 'Cancel' and refresh plugins before proceeding.", ) if not proceed: return if askyesno( "Save Configuration?", "Would you like to save the confiugration " "file before proceeding?", ): self.menu_save() run = askyesno( "Begin Analysis?", "Click Yes when you are ready to start.\n\nThis could " "take some time so grab a cup of tea, or a beer if that's " "your thing, and enjoy the show.", ) if run: self.configure_analysis() self.set_gui_state(tk.DISABLED) thread = threading.Thread(target=self.run_analysis) thread.setDaemon(True) self.analysis_thread = thread self.analysis_thread.start() self.after(100, self.poll_analysis_thread) def poll_logging_queue(self): """ Polls the logging queue for messages to log. """ try: log = get_logging_queue(init=True).get(0) log[CALLBACK](log[MESSAGE], **log[KWARGS]) self.after(10, self.poll_logging_queue) except queue.Empty: self.after(10, self.poll_logging_queue) def poll_analysis_thread(self): """ Polls the thread to check it's state. When it is finished, all stores are closed. """ try: analysis_result = self.queue.get(0) self.handle_analysis_result(analysis_result) except queue.Empty: self.after(100, self.poll_analysis_thread) def handle_analysis_result(self, success): """ Shows the appropriate messagebox and logs exceptions upon analysis completing. Parameters ---------- success : `bool` Exception object if an error occured during analysis, otherwise None to indicate successful computation. """ log_message( logging_callback=logging.info, msg="Closing stores...", extra={"oname": self.root_element.name}, ) self.root_element.store_close(children=True) log_message( logging_callback=logging.info, msg="Stores closed.", extra={"oname": self.root_element.name}, ) if success: showinfo("Analysis completed.", "Analysis has completed successfully!") log_message( logging_callback=logging.info, msg="Completed successfully!", extra={"oname": self.root_element.name}, ) else: showwarning( "Error during analysis.", "An error occurred during the analysis. See log for details", ) log_message( logging_callback=logging.info, msg="Completed, but with errors!", extra={"oname": self.root_element.name}, ) self.set_gui_state(tk.NORMAL) def run_analysis(self): """ Runs the storemanager compute method. """ try: self.root_element.validate() self.root_element.store_open(children=True) self.root_element.calculate() if self.root_element.tsv_requested: self.root_element.write_tsv() self.queue.put(True, block=False) except Exception as exception: log_message( logging_callback=logging.exception, msg=exception, extra={"oname": self.root_element.name}, ) self.queue.put(False, block=False) finally: return def configure_analysis(self): """ Configures the attributes of the root_element by querying the GUI options. """ try: self.root_element.force_recalculate = self.force_recalculate.get() self.root_element.component_outliers = self.component_outliers.get( ) self.root_element.tsv_requested = self.tsv_requested.get() scorer_class = self.get_selected_scorer_class() scorer_class_attrs = self.get_selected_scorer_attrs() scorer_path = self.get_selected_scorer_path() self.root_element.scorer_class = scorer_class self.root_element.scorer_class_attrs = scorer_class_attrs self.root_element.scorer_path = scorer_path except Exception as e: log_message( logging_callback=logging.info, msg="An error occurred when trying to configure the " "root element.", extra={"oname": self.root_element.name}, ) log_message( logging_callback=logging.exception, msg=e, extra={"oname": self.root_element.name}, ) # ---------------------------------------------------------------------- # # GUI Modifications # ---------------------------------------------------------------------- # def set_gui_state(self, state): """ Sets the state of the `go_button`, `treeview` and `treeview_buttons`. Parameters ---------- state : `str` State to set, usually ``'normal'`` or ``'disabled'`` """ for btn in self.treeview_buttons: btn.config(state=state) self.go_button.config(state=state) if state == "normal": self.treeview.bind("<Button-2>", self.treeview_context_menu) else: self.treeview.bind("<Button-2>", lambda event: event) def refresh_plugins(self): """ Refresh the plugins by re-checking the sources file. """ if self.plugin_source_window: sources = self.plugin_source_window.sources self.scorer_widget.refresh_sources(sources)
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 NameView(object): """Shows a treeview of unique names.""" def __init__(self, master, names): self.widget = Frame(master) self._tree = Treeview(self.widget, columns='name') self._tree.grid(row=0, column=0, sticky=(N, S, W, E)) self._tree.view = self self.widget.columnconfigure(0, weight=1) self.widget.rowconfigure(0, weight=1) self._tree.column('name', width=50) self._tree['show'] = 'tree' actions = { 'edit': lambda e: self.edit(), 'search': lambda e: self.search(), 'focus_next': lambda e: self.focus_next(), 'focus_prev': lambda e: self.focus_prev(), 'select': lambda e: self._tree.selection_toggle(self._tree.focus()), 'clear_selection': lambda e: self._tree.selection_set([]) } kb.make_bindings(kb.tagview, actions, self._tree.bind) self._iids = dict() self._names = dict() logger.debug('Names: %s', names) self.widget.focus_set = self._tree.focus_set for name in sorted(names): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid self._scroll = Scrollbar(self.widget, command=self._tree.yview) self._tree['yscrollcommand'] = self._scroll.set self._scroll.grid(row=0, column=1, sticky=(N, S)) self.widget.columnconfigure(1, weight=0) def selection(self): logger.debug('Selection: %s', self._tree.selection()) return [self._names[iid] for iid in self._tree.selection()] def edit(self): self._tree.event_generate('<<NameViewEdit>>') def search(self): if len(self._tree.selection()) == 0: self._tree.selection_add(self._tree.focus()) self._tree.event_generate('<<NameViewSearch>>') def append(self, names): logger.debug('Append names: %s', names) for name in names: if name not in self._names.values(): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid def delete(self, name): self._tree.delete(self._iids[name]) del self._names[self._iids[name]] del self._iids[name] def _focus(self, iid): self._tree.focus(iid) self._tree.see(iid) def focus_next(self): cur_iid = self._tree.focus() next_iid = self._tree.next(cur_iid) if next_iid == '': iids = self._tree.get_children() next_iid = iids[0] self._focus(next_iid) def focus_prev(self): cur_iid = self._tree.focus() prev_iid = self._tree.prev(cur_iid) if prev_iid == '': iids = self._tree.get_children() prev_iid = iids[-1] self._focus(prev_iid) def jump_to(self, name): try: iid = self._iids[name] self._focus(iid) except KeyError: pass def get_names(self): return tuple(self._names.values()) def set(self, names): self._tree.delete(*self._iids.values()) self._iids.clear() self._names.clear() for name in sorted(names): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid
class Demo(Tk): icon_res = [] file_name = None def __init__(self): super().__init__() self._set_windows_() self._create_menu_bar_() self._create_shortcut_bar_() self._create_body_() self.DATAS = pd.DataFrame(columns=INFOS) def _set_windows_(self): self.title('学生成绩管理系统') scn_width, scn_height = self.maxsize() wm_val = '1200x450+%d+%d' % ((scn_width - 800) / 2, (scn_height - 450) / 2) self.geometry(wm_val) # self.iconbitmap("img/editor.ico") def _create_menu_bar_(self): menu_bar = Menu(self) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='add', accelerator='Ctrl+N', command=self.add) file_menu.add_command(label='save_to_file', command=self.save_to_file) file_menu.add_command(label='Exit', command=self.edit) file_menu.add_command(label='Search', command=self.search) file_menu.add_command(label='Total', command=self.total) file_menu.add_command(label='save_to_DATAS', command=self.save_to_DATAS) file_menu.add_command(label='Average', command=self.average) file_menu.add_command(label='open_file', command=self.open_file) file_menu.add_command(label='add', command=self.add) file_menu.add_command(label='delete_selected_item', command=self.delete_item) file_menu.add_command(label='clear_all', command=self.clear_all) file_menu.add_command(label='delete_item', command=self.delete_item) file_menu.add_command(label='init_demo', command=self.init_demo) file_menu.add_command(label='sort_as_total', command=self.sort_as_total) file_menu.add_command(label='average_stu', command=self.average_stu) menu_bar.add_cascade(label='程序中所有函数(测试)', menu=file_menu) help_menu = Menu(menu_bar, tearoff=0) help_menu.add_command(label='帮助', accelerator='Ctrl+H', command=self.about) help_menu.add_command(label='关于', accelerator='Ctrl+H', command=self.about) menu_bar.add_cascade(label='---> 请先阅读帮助中的说明!!!<---', menu=help_menu) self["menu"] = menu_bar def _create_shortcut_bar_(self): shortcut_bar = Frame(self, height=25, background='#00CED1') shortcut_bar.pack(fill=X) right_bar = Frame(self, width=25, background='#FF8C00') right_bar.pack(side=RIGHT, fill=Y) for i, icon in enumerate(ICONS): icon_img = PhotoImage(file='img/%s.gif' % icon) btn = Button(shortcut_bar, image=icon_img, command=lambda x=icon: self._shortcut_action_(x)) btn.pack(side=LEFT) self.icon_res.append(icon_img) def _create_body_(self): scrollBar = Scrollbar(self) scrollBar.pack(side=RIGHT, fill=Y) self.tree = Treeview(self, show='headings', yscrollcommand=scrollBar.set) # 表格 index = tuple([str(i) for i in range(len(INFOS))]) self.tree["columns"] = index for i, info in zip(index, INFOS): self.tree.column(i, width=len(i) * 10, anchor='center') self.tree.heading(i, text=info) #显示表头 self.tree.pack(fill=BOTH, ipady=500) # 将滚动条绑定至Treeview scrollBar.config(command=self.tree.yview) # 快捷键相关设置 # 函数参数需要有event=None self.tree.bind('<Double-Button-1>', self.edit) # 响应快捷菜单 def _shortcut_action_(self, type): if type == "open_file": self.open_file() elif type == "add": self.add() elif type == "edit": self.edit() elif type == "save_file": self.save_to_file() elif type == "save_DATAS": self.save_to_DATAS() elif type == "delete": self.delete_item() elif type == "clear_all": self.clear_all() elif type == "search": self.search() elif type == "total": try: self.total() except: messagebox.showinfo(title='警告', message='请检查输入信息') elif type == "average": try: self.average() except: messagebox.showinfo(title='警告', message='请检查输入信息') elif type == "sort": try: self.sort_as_total() except: messagebox.showinfo(title='警告', message='请检查输入信息') elif type == "sort_no": try: self.sort_as_no() except: messagebox.showinfo(title='警告', message='请检查输入信息') elif type == "exit": self.exit() elif type == "average_stu": try: self.average_stu() except: messagebox.showinfo(title='警告', message='请检查输入信息') elif type == "about": self.about() def showline(self, event=None): print(self.tree.selection()) for item in self.tree.selection(): item_text = self.tree.item(item, 'values') print(item_text) def clear_all(self): print('-------------follow item will be cleared:BEG-------------') print(self.tree.get_children()) print('\n') for item in self.tree.get_children(): self.tree.delete(item) print(item + ' : ', end=' ') print('deleted', end=' ') print('\n-------------items had been cleared:END-------------') def init_demo(self): self.clear_all() self.DATAS = pd.DataFrame(columns=INFOS) print('---------------demo inited----------------') def delete_item(self, event=None): print('-------------Your selection:BEG-------------') print(self.tree.selection()) for item in self.tree.selection(): self.tree.delete(item) print('-------------Your selection cleared:END-------------') def add(self, event=None): add_windows = Toplevel(self) scn_width, scn_height = self.maxsize() wm_val = '320x400+%d+%d' % ((scn_width - 320) / 2, (scn_height - 400) / 2) add_windows.geometry(wm_val) add_windows.resizable(0, 0) add_windows.title('添加新的学生') frame = Frame(add_windows) frame.pack(fill=Y) self.entryList = locals() for i, info in enumerate(INFOS[:-2]): # -2 不添加总分 和 平均分 Label(frame, text=info + ' : ').grid(row=i, column=0, pady=5) self.entryList[info] = Entry(frame) self.entryList[info].grid(row=i, column=1, pady=5) frame_btn = Frame(add_windows) frame_btn.pack(fill=Y) Button(frame_btn, text='添加', command=lambda: update()).grid(row=0, column=0, pady=5) Button(frame_btn, text='清空', command=lambda: clear()).grid(row=0, column=1, pady=5) Button(frame_btn, text='取消', command=lambda: exit()).grid(row=0, column=2, pady=5) def update(): DATAS = [] for info in INFOS: data = self.entryList[info].get() DATAS.append(data) for i in DATAS[:-1]: if i == '': messagebox.showwarning(title='警告', message='输入空白') clear() return self.tree.insert("", END, values=DATAS) def clear(): for info in INFOS: self.entryList[info].delete(0, END) def exit(): add_windows.destroy() def open_file(self, options=None): if options: input_file = options else: input_file = filedialog.askopenfilename( filetypes=[("所有文件", "*.*"), ("Excel文档", "*.xlsx")]) if input_file: print('-----------------成功导入文件:', input_file, type(input_file), '---------------') def read_excel(file): df = pd.read_excel(file) print('---------------reading excel!-----------------') print(df) print('---------------read excel done!-----------------') return df df = read_excel(input_file) # input data to Treeview print('---------------inputing to TreeView!-----------------') for i in df.iloc: data = i.tolist() # 若导入的文件中缺少数据,则缺少的数据用空补上 for i in range(len(INFOS) - len(data)): data.append('') # 打印测试 print(data) # 测试用 把 平均分 和 总分 置空 data[-1] = '' data[-2] = '' self.tree.insert("", END, values=data) print('---------------input to TreeView done!-----------------') def save_to_DATAS(self): print('---------------saving to DATAS!-----------------') self.DATAS = pd.DataFrame(columns=INFOS) indexs = self.tree.get_children() for i in indexs: value = self.tree.item(i, 'values') print(value) self.DATAS.loc[len(self.DATAS)] = list(value) print('---------------save to DATAS done!-----------------') def save_to_file(self): outputfile = filedialog.asksaveasfilename( filetypes=[("所有文件", "*.*"), ("Excel文档", "*.xlsx")]) self.save_to_DATAS() # outputfile = 'new.xlsx' self.DATAS.to_excel(outputfile, index=FALSE) print( '---------------------------save to file done!---------------------------------' ) def edit(self, event=None): item = self.tree.selection() # Layout edit_windows = Toplevel(self) scn_width, scn_height = self.maxsize() wm_val = '320x450+%d+%d' % ((scn_width - 320) / 2, (scn_height - 450) / 2) edit_windows.geometry(wm_val) edit_windows.resizable(0, 0) edit_windows.title('编辑学生信息') # 创建标签和输入框 frame = Frame(edit_windows) frame.pack(fill=Y) self.entryList = locals() for i, info in enumerate(INFOS): Label(frame, text=info + ' : ').grid(row=i, column=0, pady=5) self.entryList[info] = Entry(frame) self.entryList[info].grid(row=i, column=1, pady=5) # 创建按钮布局 frame_btn = Frame(edit_windows) frame_btn.pack(fill=Y) Button(frame_btn, text='确定', command=lambda: update()).grid(row=0, column=0, pady=5) Button(frame_btn, text='清空', command=lambda: clear()).grid(row=0, column=1, pady=5) Button(frame_btn, text='取消', command=lambda: exit()).grid(row=0, column=2, pady=5) # 将待修改的数据导入输入框(方便修改,不用全部重新输入) values = self.tree.item(item, 'values') for value, info in zip(values, INFOS): self.entryList[info].insert(END, value) print(values) print('--------------input to entry done!------------') def update(): temp = [] for i, info in enumerate(INFOS): data = self.entryList[info].get() temp.append(data) self.tree.set(item, i, value=data) print('----------------editing data!-----------------') print(temp) print('---------------edit data done!-----------------') exit() def clear(): for info in INFOS: self.entryList[info].delete(0, END) def exit(): edit_windows.destroy() # item = self.tree.selection() # self.tree.set(item, 0, value='helloworld') def search(self): search_windows = Toplevel(self) search_windows.title('搜索全部') search_windows.transient(self) search_windows.resizable(0, 0) scn_width, scn_height = self.maxsize() wm_val = '380x70+%d+%d' % ((scn_width - 380) / 2, (scn_height - 70) / 2) search_windows.geometry(wm_val) Label(search_windows, text='查找全部:').grid(row=0, column=0, sticky=E) search_entry = Entry(search_windows, width=25) search_entry.grid(row=0, column=1, padx=2, pady=20, sticky='we') search_entry.focus_set() Button(search_windows, text='查找', command=lambda: search_result()).grid(row=0, column=2, sticky=E + W, padx=2, pady=2) def search_result(): result = [] indexs = self.tree.get_children() search_data = search_entry.get() print(search_data, type(search_data)) for i, index in enumerate(indexs): values = self.tree.item(index, 'values') print(values, end=' ') for value in list(values)[:2]: if search_data in value: result.append(search_data) print(result) self.tree.selection_set(index) self.tree.yview_moveto(i / len(indexs)) break else: print('No Found') if result: return else: messagebox.showinfo(title='提示', message='未找到匹配项') # 选择 # self.tree.selection_set('I001') # self.tree.yview_moveto(1) # 取消选择 # self.tree.selection_remove('I001') def update_to_tree(self): # 删除Treeview中所有元素 for index in self.tree.get_children(): self.tree.delete(index) # 插入DATAS中的元素到TreeView中 for i in self.DATAS.iloc: data = i.tolist() self.tree.insert("", END, values=data) def total(self): self.save_to_DATAS() temp = self.DATAS[['成绩A', '成绩B', '成绩C']].astype('int') self.DATAS['总分'] = temp.sum(axis=1) print( '---------------------------Total calculate done!-----------------------------' ) print(self.DATAS) print( '---------------------------Total calculate done!-----------------------------' ) # # 删除Treeview中所有元素 # for index in self.tree.get_children(): # self.tree.delete(index) # # 插入DATAS中的元素到TreeView中 # for i in self.DATAS.iloc: # data = i.tolist() # self.tree.insert("", END, values=data) self.update_to_tree() def average_stu(self): self.save_to_DATAS() temp = self.DATAS[['成绩A', '成绩B', '成绩C']].astype('int') # round 计算并保留两位小数 round( *, 2 ) self.DATAS['平均分'] = round(temp.mean(axis=1), 2) print( '---------------------------per average calculate done!-----------------------------' ) print(self.DATAS) print( '---------------------------per average done!-----------------------------' ) self.update_to_tree() def average(self): self.total() temp = self.DATAS[['总分']].astype('float64') av = temp.mean(axis=0) print('---------------------------AV-----------------------------') print('平均分:', float(av)) print('---------------------------AV-----------------------------') class_grade = '\t总平均分: %.2f' % float(av) messagebox.showinfo(title='平均分', message=class_grade) def sort_as_total(self): self.total() self.DATAS.sort_values(by='总分', ascending=False, inplace=True) print('--------------------sorting values!----------------') print(self.DATAS) print('--------------------sort values done!--------------') # drop 清洗列表 去掉NaN的行 self.DATAS.reset_index(drop=True, inplace=True) print('--------------------cleaning datas!----------------') print(self.DATAS) print('--------------------clean datas done!--------------') self.update_to_tree() print('--------------------update to tree done!--------------') # Text self.DATAS.to_excel('text.xlsx', index=False) def sort_as_no(self): self.save_to_DATAS() self.DATAS['学号'] = self.DATAS[['学号']].astype('int') self.DATAS.sort_values(by='学号', inplace=True) print(self.DATAS) print('-------------------sort as no done!-----------------') self.DATAS.reset_index(drop=True, inplace=True) print(self.DATAS) print('--------------------clean data done!-----------------') self.update_to_tree() print('--------------------update to tree done!--------------') # Text self.DATAS.to_excel('text_sort_no.xlsx', index=False) def about(self): # info = 'Github Page: %s \nWeb: %s\n' % (ADRS[0], ADRS[1]) # messagebox.showinfo(title='About', message=info) help = [ '打开Excel文件 其中所有学生信息将导入', '添加一个学生信息 单个添加', '编辑学生信息 选中一个数据后点击 也可直接双击数据编辑', '保存学生信息至Excel文件 文件可用于导入', '刷新数据列表 将窗口内所有学生信息更新至DATAS 用于计算', '删除学生 选中一个或多个(Shift)数据后单击 即可删除该数据 ', '清空所有信息', '查找学生信息 输入姓名 或 学号即可自动遍历查找', '求各个学生的总分', '求所有学生的平均分', '将所有学生按总分排序 会先自动计算总分', '求各个学生的平均分 会先自动线计算总分', '将所有学生按学号排序', '关于帮助', '退出程序' ] about_windows = Toplevel(self) about_windows.title('程序使用说明') # 容器框 (LabelFrame) group = LabelFrame(about_windows, text="Help", padx=5, pady=5) group.grid(padx=10, pady=10) # w = Label(group, text='本学习项目由 http://pegasu.cn 出品 \n\nGithub: https://github.com/pegasuswiki') # w.pack() for i, icon in enumerate(ICONS): icon_img = PhotoImage(file='img/%s.gif' % icon) Label(group, image=icon_img).grid(row=i, column=0, stick=NW, padx=5, pady=5) Label(group, text=help[i]).grid(row=i, column=1, stick=W, padx=5, pady=5) self.icon_res.append(icon_img) # 必须有 作用: 保存图片 w = Label( about_windows, text= '本学习项目由 http://pegasu.cn 出品 \n\nGithub: https://github.com/pegasuswiki' ) w.grid(pady=10) def exit(self): if messagebox.askokcancel("退出?", "确定退出吗?"): self.quit()
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 FormChildAED: def __init__(self, frm_parent, title, connection): self.connection = connection self.directive = Message() self.title = title self.decide = True self.id_selected = 0 self.frm_child_list = LabelFrame(frm_parent) self.frm_child_crud = LabelFrame(frm_parent) self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.initialize_components() def initialize_components(self): """ Method that initialize the visual components for each form associated with the local administration """ # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") # Components for List Form lbl_sep1 = Label(self.frm_child_list) lbl_sep1.grid(row=0, column=0, padx=10, pady=25) self.trv_available = Treeview(self.frm_child_list, height=15, columns=('N', 'Name', 'Surname', 'E-mail')) self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='N', anchor=CENTER) self.trv_available.heading('#2', text='Name', anchor=CENTER) self.trv_available.heading('#3', text='Surname', anchor=CENTER) self.trv_available.heading('#4', text='E-mail', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available.column('#2', width=200, minwidth=200, stretch=NO) self.trv_available.column('#3', width=200, minwidth=200, stretch=NO) self.trv_available.column('#4', width=400, minwidth=400, stretch=NO) self.trv_available.grid(row=0, column=1, sticky=W, pady=25) vsb_trv_av = Scrollbar(self.frm_child_list, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) frm_aux4 = Frame(self.frm_child_list) btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new) btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E) btn_new_ttp = CreateToolTip(btn_new, 'New ' + self.title.lower()) btn_edit = Button(frm_aux4, image=self.modify_icon, command=self.click_update) btn_edit.grid(row=1, column=0, pady=5, padx=5, sticky=E) btn_edit_ttp = CreateToolTip(btn_edit, 'Edit ' + self.title.lower()) btn_delete = Button(frm_aux4, image=self.remove_icon, command=self.click_delete) btn_delete.grid(row=2, column=0, pady=5, padx=5, sticky=E) btn_delete_ttp = CreateToolTip(btn_delete, 'Delete ' + self.title.lower()) frm_aux4.grid(row=0, column=3, pady=25, padx=25, sticky=NW) # Components for CRUD FRM lbl_name = Label(self.frm_child_crud, text='Name*') lbl_name.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_name.grid(row=0, column=0, pady=10, padx=20, sticky=W) lbl_surname = Label(self.frm_child_crud, text='Surname*') lbl_surname.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_surname.grid(row=1, column=0, pady=10, padx=20, sticky=W) lbl_email = Label(self.frm_child_crud, text='E-mail*') lbl_email.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_email.grid(row=2, column=0, pady=10, padx=20, sticky=W) self.lbl_old_passwd = Label(self.frm_child_crud, text='Old password*') self.lbl_old_passwd.config(fg=TEXT_COLOR, font=LABEL_FONT) self.lbl_passwd = Label(self.frm_child_crud, text='New password*') self.lbl_passwd.config(fg=TEXT_COLOR, font=LABEL_FONT) self.lbl_passwd_conf = Label(self.frm_child_crud, text='Confirm new password*') self.lbl_passwd_conf.config(fg=TEXT_COLOR, font=LABEL_FONT) self.txt_name = Entry(self.frm_child_crud) self.txt_name.grid(row=0, column=1, pady=10, padx=20, sticky=W) self.txt_surname = Entry(self.frm_child_crud) self.txt_surname.grid(row=1, column=1, pady=10, padx=20, sticky=W) self.txt_email = Entry(self.frm_child_crud) self.txt_email.grid(row=2, column=1, pady=10, padx=20, sticky=W) self.txt_old_passwd = Entry(self.frm_child_crud, show="*") self.txt_passwd = Entry(self.frm_child_crud, show="*") self.txt_passwd_conf = Entry(self.frm_child_crud, show="*") sep_aux2 = Separator(self.frm_child_crud, orient=VERTICAL) sep_aux2.grid(row=0, column=2, sticky=NS, rowspan=6) frm_aux = Frame(self.frm_child_crud) btn_save = Button(frm_aux, image=self.save_icon, command=self.click_save) btn_save.grid(row=0, column=0, padx=5, pady=5, sticky=E) btn_save_ttp = CreateToolTip(btn_save, 'Save ' + self.title.lower()) btn_cancel = Button(frm_aux, image=self.cancel_icon, command=self.click_cancel) btn_cancel.grid(row=1, column=0, padx=5, pady=5, sticky=E) btn_cancel_ttp = CreateToolTip(btn_cancel, 'Cancel') frm_aux.grid(row=0, column=3, pady=10, padx=25, sticky=N, rowspan=6) def retrieve_list(self): """ Method that retrieve users information from the server and displays them in the TreeView from the List Form """ # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) # Retrieve information from the server if self.title == 'Experimenter': self.directive = Message(action=17) elif self.title == 'Designer': self.directive = Message(action=22) elif self.title == 'Administrator': self.directive = Message(action=12) else: raise Exception('Error en recuperacion: tipo de usuario') self.connection = self.directive.send_directive(self.connection) # Adding elements in the list for index, item in enumerate(self.connection.message.information): elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(index + 1, elements[1], elements[2], elements[3])) # Mark first element of the treeview if exist if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set( self.trv_available.get_children()[0]) def show_frm(self): """ Show the List form when the User administration is called """ self.retrieve_list() self.frm_child_list.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def hide_frm(self): """ Hide the User administration Forms """ self.clear_fields() self.frm_child_list.grid_forget() self.frm_child_crud.grid_forget() def click_new(self): """ Initialize CRUD Form for creating a new user. """ self.user = Designer() self.frm_child_list.grid_forget() self.txt_name.focus_set() self.frm_child_crud['text'] = 'New ' + self.title.lower() self.lbl_passwd.grid(row=3, column=0, pady=10, padx=20, sticky=W) self.lbl_passwd_conf.grid(row=4, column=0, pady=10, padx=20, sticky=W) self.txt_passwd.grid(row=3, column=1, pady=10, padx=20, sticky=W) self.txt_passwd_conf.grid(row=4, column=1, pady=10, padx=20, sticky=W) self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def click_update(self): """ Initialize CRUD Form for updating a user. It loads information of selected User into visual components """ if len(self.trv_available.selection()) == 1: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) if self.title == 'Experimenter': self.directive = Message(action=20, information=[id_selected]) elif self.title == 'Designer': self.directive = Message(action=25, information=[id_selected]) else: self.directive = Message(action=15, information=[id_selected]) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: # An error ocurred while trying to update the item messagebox.showerror( parent=self.frm_child_list, title='Can not update the item', message=self.connection.message.information[0]) else: self.user = Designer( id=id_selected, name=self.connection.message.information[0], surname=self.connection.message.information[1], user=self.connection.message.information[2], password=self.connection.message.information[3]) self.txt_name.insert(0, self.user.name) self.txt_surname.insert(0, self.user.surname) self.txt_email.insert(0, self.user.user) self.frm_child_list.grid_forget() self.txt_name.focus_set() self.frm_child_crud['text'] = 'Update ' + self.title.lower() self.lbl_old_passwd.grid(row=3, column=0, pady=10, padx=20, sticky=W) self.lbl_passwd.grid(row=4, column=0, pady=10, padx=20, sticky=W) self.lbl_passwd_conf.grid(row=5, column=0, pady=10, padx=20, sticky=W) self.txt_old_passwd.grid(row=3, column=1, pady=10, padx=20, sticky=W) self.txt_passwd.grid(row=4, column=1, pady=10, padx=20, sticky=W) self.txt_passwd_conf.grid(row=5, column=1, pady=10, padx=20, sticky=W) self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_delete(self): """ Method that removes a selected user from the initial list (changes are updated in DB) """ if len(self.trv_available.selection()) == 1: decision = messagebox.askyesno( parent=self.frm_child_list, title='Confirmation', message='Are you sure you want to delete the item?') if decision: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) if self.title == 'Experimenter': self.directive = Message(action=19, information=[id_selected]) elif self.title == 'Designer': self.directive = Message(action=24, information=[id_selected]) else: self.directive = Message(action=14, information=[id_selected]) self.connection = self.directive.send_directive( self.connection) if self.connection.message.action == 5: # An error ocurred while deleting the item messagebox.showerror( parent=self.frm_child_list, title='Can not delete the item', message=self.connection.message.information[0]) else: self.retrieve_list() else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_save(self): """ Saves information of the user inserted into the visual components and sends to the server """ if self.validate_fields(): self.user.name = self.txt_name.get() self.user.surname = self.txt_surname.get() self.user.user = self.txt_email.get() self.user.password = self.txt_passwd.get() if self.user.id == 0: # Creating an user if self.title == 'Experimenter': self.directive = Message( action=16, information=[ self.user.name, self.user.surname, self.user.user, hashlib.sha1( self.user.password.encode()).hexdigest() ]) elif self.title == 'Designer': self.directive = Message( action=21, information=[ self.user.name, self.user.surname, self.user.user, hashlib.sha1( self.user.password.encode()).hexdigest() ]) else: self.directive = Message( action=11, information=[ self.user.name, self.user.surname, self.user.user, hashlib.sha1( self.user.password.encode()).hexdigest() ]) else: # Updating an user if self.title == 'Experimenter': self.directive = Message( action=18, information=[ self.user.id, self.user.name, self.user.surname, self.user.user, hashlib.sha1( self.user.password.encode()).hexdigest() ]) elif self.title == 'Designer': self.directive = Message( action=23, information=[ self.user.id, self.user.name, self.user.surname, self.user.user, hashlib.sha1( self.user.password.encode()).hexdigest() ]) else: self.directive = Message(action=13, information=[ self.user.id, self.user.name, self.user.surname, self.user.user, self.user.password ]) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: messagebox.showwarning(parent=self.frm_child_crud, title='Repeated e-mail', message=self.connection.message.comment) else: self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def click_cancel(self): """ Function activated when 'Cancel' button is pressed in frm_child_crud """ decision = True if self.txt_name.get() != self.user.name or \ self.txt_surname.get() != self.user.surname or \ self.txt_email.get() != self.user.user or len(self.txt_passwd.get()) != 0 or \ len(self.txt_passwd_conf.get()) != 0: if self.user.id != 0 and len( self.txt_passwd_conf.get()) != 0 or self.user.id == 0: decision = messagebox.askyesno( parent=self.frm_child_crud, title='Cancel', message='Are you sure you want to cancel?') if decision: self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def validate_fields(self): if len(self.txt_name.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a name for the {}'.format( self.title.lower())) return False if len(self.txt_surname.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a surname for the {}'.format( self.title.lower())) return False if len(self.txt_email.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert an e-mail for the {}'.format( self.title.lower())) return False # If updating an user if self.user.id != 0 and len(self.txt_old_passwd.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert the old password for the {}'.format( self.title.lower())) return False if len(self.txt_passwd.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a new password for the {}'.format( self.title.lower())) return False if len(self.txt_passwd_conf.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must confirm the new password for the {}'.format( self.title.lower())) return False if self.txt_passwd.get() != self.txt_passwd_conf.get(): messagebox.showwarning( parent=self.frm_child_crud, title='Password field', message= 'The new password you provided does not match the confirmation' ) return False # If updating an user if self.user.id != 0 and self.user.password != hashlib.sha1( self.txt_old_passwd.get().encode()).hexdigest(): messagebox.showwarning(parent=self.frm_child_crud, title='Old password field', message='The old password is incorrect') return False return True def clear_fields(self): self.txt_name.delete(0, END) self.txt_surname.delete(0, END) self.txt_email.delete(0, END) self.txt_old_passwd.delete(0, END) self.txt_passwd.delete(0, END) self.txt_passwd_conf.delete(0, END) self.lbl_old_passwd.grid_forget() self.lbl_passwd.grid_forget() self.lbl_passwd_conf.grid_forget() self.txt_old_passwd.grid_forget() self.txt_passwd.grid_forget() self.txt_passwd_conf.grid_forget()
def listchans(self, index=None, tagsearch="", archived=0, ob=Channel.displayname, so=asc): self.center = Frame(root, bg=config.bgcolor, width=50, height=40, padx=3, pady=3) # layout all of the main containers root.grid_rowconfigure(1, weight=1) root.grid_columnconfigure(0, weight=1) self.center.grid(row=1, sticky="nsew") channels = database.get_channels(archived, ob, so) if so == desc: so = asc else: so = desc tree = Treeview(self.center) sstring = Entry(self.center, width=config.textBoxWidth) sstring.bind( "<KeyRelease-Return>", lambda e: self.listchans(None, sstring.get(), archived, ob, so)) sstring.grid(column=0, row=0) if len(tagsearch) >= 1: sstring.focus() sstring.insert(0, tagsearch) searchbutton = Button(self.center, text="Search", command=lambda: self.listchans( None, sstring.get(), archived, ob, so)) searchbutton.grid(column=1, row=0) clearbutton = Button(self.center, text="Clear Search") clearbutton.configure(command=lambda: self.listchans(archived)) clearbutton.grid(column=3, row=0) tree["columns"] = ("one", "two") tree.column("#0", width=210, minwidth=10, stretch=YES) tree.column("one", width=350, minwidth=250, stretch=YES) tree.column("two", width=210, minwidth=10, stretch=YES) tree.heading("#0", text="Last Check", anchor=W, command=lambda: self.listchans(None, sstring.get( ), archived, Channel.lastcheck, so)) tree.heading("one", text="Channel Name", anchor=E, command=lambda: self.listchans(None, sstring.get( ), archived, Channel.displayname, so)) tree.heading("two", text="Last Published", command=lambda: self.listchans(None, sstring.get( ), archived, Channel.lastpub, so)) i = 0 tree.tag_configure('oddrow', background='#88DD88') tree.tag_configure('evenrow', background='#FFFFFF') tree.tag_configure('archivedodd', background="#88DD88", foreground="#aaaaaa") tree.tag_configure('archivedeven', background='#FFFFFF', foreground="#cccccc") for item in channels: foldername = "folder" + str(i) if i % 2 == 0: color = "evenrow" else: color = "oddrow" if item.archive == True: if i % 2 == 0: color = "archivedeven" else: color = "archivedodd" if tagsearch.lower() in str( item.displayname).lower() or tagsearch.lower() in str( item.dldir).lower() or tagsearch.lower() in str( item.yt_channelid).lower(): if item.lastpub == None: lastpub = "N/A" else: lastpub = time.ctime(item.lastpub) foldername = tree.insert("", "end", text=time.ctime(item.lastcheck), values=(item.displayname, lastpub), tags=(color, item.displayname, item.dldir, item.yt_channelid)) tree.insert(foldername, "end", text="Directory", values=(item.dldir, ), tags=(color)) tree.insert(foldername, "end", text="Last Published", values=(lastpub, ), tags=(color)) i = i + 1 vertscroll = Scrollbar(self.center) vertscroll.config(command=tree.yview) tree.config(yscrollcommand=vertscroll.set, height=20) vertscroll.grid(column=4, row=1, sticky='NSE') tree.bind("<Double-1>", self.item_selected) tree.grid(row=1, columnspan=4, sticky="NSEW") tree.focus(index) tree.selection_set(index) tree.see(index)
class FormParentDesigner: def __init__(self, window, connection, current_designer): self.connection = connection self.current_designer = current_designer self.frm_parent = LabelFrame(window) self.tlevel_auth_scenario = Toplevel(window) self.tlevel_auth_scenario.protocol("WM_DELETE_WINDOW", self.click_authenticate_cancel) self.tlevel_auth_scenario.withdraw() self.frm_general = LabelFrame(window) self.tlevel_image_exp = Toplevel(window) self.tlevel_image_exp.title('Diagram') self.tlevel_image_exp.protocol("WM_DELETE_WINDOW", self.close_tlevel_image) self.tlevel_image_exp.withdraw() self.initialize_components() def initialize_components(self): # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") self.add_icon = PhotoImage(file=r"./Resources/down_arrow.png") self.delete_icon = PhotoImage(file=r"./Resources/up_arrow.png") self.next_icon = PhotoImage(file=r"./Resources/next.png") self.back_icon = PhotoImage(file=r"./Resources/back.png") self.copy_icon = PhotoImage(file=r"./Resources/copy.png") self.open_icon = PhotoImage(file=r"./Resources/open.png") self.complete_icon = PhotoImage(file=r"./Resources/complete.png") self.incomplete_icon = PhotoImage(file=r"./Resources/incomplete.png") self.optional_icon = PhotoImage(file=r"./Resources/optional.png") self.refresh_icon = PhotoImage(file=r"./Resources/refresh.png") # Initialize visual components for displaying available experiment scenarios lbl_title = Label(self.frm_parent, text='Experiments') lbl_title.config(fg=TEXT_COLOR, font=TITLE_FONT) lbl_title.grid(row=0, column=0, columnspan=9, pady=20, sticky=EW) sep_aux5 = Separator(self.frm_parent, orient=HORIZONTAL) sep_aux5.grid(row=1, column=0, sticky=EW, columnspan=9) lbl_sep1 = Label(self.frm_parent) lbl_sep1.grid(row=2, column=0, padx=10, pady=20, rowspan=2) lbl_experimental_trv = Label(self.frm_parent, text='Select an experiment') lbl_experimental_trv.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_experimental_trv.grid(row=2, column=1, pady=20, sticky=W) lbl_sep2 = Label(self.frm_parent) lbl_sep2.grid(row=2, column=3, padx=10, pady=20, rowspan=2) lbl_problem_desc = Label(self.frm_parent, text='Information') lbl_problem_desc.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_problem_desc.grid(row=2, column=4, pady=20, sticky=W) lbl_sep3 = Label(self.frm_parent) lbl_sep3.grid(row=2, column=6, padx=10, pady=20, rowspan=2) self.trv_available = Treeview(self.frm_parent, height=11, columns='Title') self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='Title', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=400, minwidth=400, stretch=NO) self.trv_available.bind("<ButtonRelease-1>", self.select_experimental_scenario) self.trv_available.grid(row=3, column=1, sticky=W, pady=20) vsb_trv_av = Scrollbar(self.frm_parent, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=3, column=2, pady=20, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) self.txt_scenario_desc = Text(self.frm_parent, height=15, width=40) self.txt_scenario_desc.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_scenario_desc.grid(row=3, column=4, pady=20, sticky=W) vsb_txt_sc = Scrollbar(self.frm_parent, orient="vertical", command=self.txt_scenario_desc.yview) vsb_txt_sc.grid(row=3, column=5, pady=20, sticky=NS) self.txt_scenario_desc.configure(yscrollcommand=vsb_txt_sc.set) sep_aux1 = Separator(self.frm_parent, orient=VERTICAL) sep_aux1.grid(row=2, column=7, sticky=NS, rowspan=2) btn_access = Button(self.frm_parent, image=self.next_icon, command=self.click_enter_scenario) btn_access.grid(row=2, column=8, padx=30, pady=5) btn_access_ttp = CreateToolTip(btn_access, 'Go') btn_refresh = Button(self.frm_parent, image=self.refresh_icon, command=self.retrieve_list) btn_refresh.grid(row=3, column=8, padx=30, pady=5, sticky=N) btn_refresh_ttp = CreateToolTip(btn_refresh, 'Refresh list') # Window dialog to authenticate the selected experimental scenario lbl_access_auth = Label(self.tlevel_auth_scenario, text='Insert access code: ') lbl_access_auth.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_access_auth.grid(pady=10, padx=20, sticky=W) self.txt_auth_scenario = Entry(self.tlevel_auth_scenario, width=50) self.txt_auth_scenario.config(font=SUBTITLE2_FONT, show="*") self.txt_auth_scenario.grid(row=0, column=1, padx=20, pady=10, sticky=W) btn_access_auth = Button(self.tlevel_auth_scenario, image=self.next_icon, command=self.click_authenticate_scenario) btn_access_auth.grid(row=0, column=2, padx=20, pady=10, sticky=W) btn_access_auth_ttp = CreateToolTip(btn_access_auth, 'Go') btn_cancel_auth = Button(self.tlevel_auth_scenario, image=self.cancel_icon, command=self.click_authenticate_cancel) btn_cancel_auth.grid(row=1, column=2, padx=20, pady=10, sticky=W) btn_cancel_auth_ttp = CreateToolTip(btn_cancel_auth, 'Cancel') # Experiment form lbl_sep4 = Label(self.frm_general) lbl_sep4.grid(row=0, column=0, padx=10, pady=5, rowspan=2) self.lbl_exp_title = Label(self.frm_general, text='Experiment: {}') self.lbl_exp_title.config(fg=TEXT_COLOR, font=TITLE_FONT) self.lbl_exp_title.grid(row=0, column=1, columnspan=5, pady=5, sticky=EW) self.txt_exp_desc = Text(self.frm_general, height=4, width=157) self.txt_exp_desc.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_exp_desc.grid(row=1, column=1, pady=5, sticky=W) vsb_txt_expd = Scrollbar(self.frm_general, orient="vertical", command=self.txt_exp_desc.yview) vsb_txt_expd.grid(row=1, column=2, pady=5, sticky=NS) self.txt_exp_desc.configure(yscrollcommand=vsb_txt_expd.set) lbl_sep11 = Label(self.frm_general) lbl_sep11.grid(row=1, column=3, padx=10, pady=5) self.btn_view_dd = Button(self.frm_general, text='View >>\ndiagram', command=self.click_expand_dd) btn_view_dd_ttp = CreateToolTip(self.btn_view_dd, 'Experiment description diagram') sep_aux3 = Separator(self.frm_general, orient=HORIZONTAL) sep_aux3.grid(row=2, column=0, sticky=EW, columnspan=6) lbl_sep5 = Label(self.frm_general) lbl_sep5.grid(row=3, column=0, padx=10, pady=5, rowspan=2) self.lbl_prob_title = Label(self.frm_general, text='Problem {} of {}: {}') self.lbl_prob_title.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.lbl_prob_title.grid(row=3, column=1, columnspan=2, pady=5, sticky=W) self.txt_prob_desc = Text(self.frm_general, height=7, width=157) self.txt_prob_desc.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_prob_desc.grid(row=4, column=1, pady=5, sticky=W) vsb_txt_probd = Scrollbar(self.frm_general, orient="vertical", command=self.txt_prob_desc.yview) vsb_txt_probd.grid(row=4, column=2, pady=5, sticky=NS) self.txt_prob_desc.configure(yscrollcommand=vsb_txt_probd.set) lbl_sep12 = Label(self.frm_general) lbl_sep12.grid(row=3, column=3, padx=10, pady=5, rowspan=2) sep_aux2 = Separator(self.frm_general, orient=HORIZONTAL) sep_aux2.grid(row=5, column=0, sticky=EW, columnspan=4) lbl_sep6 = Label(self.frm_general) lbl_sep6.grid(row=6, column=0, padx=10, pady=5, rowspan=2) lbl_solution_title = Label(self.frm_general, text='Your solution') lbl_solution_title.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) lbl_solution_title.grid(row=6, column=1, pady=5, columnspan=2, sticky=W) self.btn_next_scenario = Button(self.frm_general, image=self.next_icon, command=self.click_next_scenario) self.btn_next_scenario.grid(row=3, column=5, padx=30, pady=5, sticky=W) btn_next_scenario_ttp = CreateToolTip(self.btn_next_scenario, 'Next component') sep_aux4 = Separator(self.frm_general, orient=VERTICAL) sep_aux4.grid(row=3, column=4, sticky=NS, rowspan=5) self.tab_control = Notebook(self.frm_general) self.tab_patterns = Frame(self.tab_control) self.tab_control.add(self.tab_patterns, text="Design patterns", padding=10, image=self.incomplete_icon, compound=RIGHT) lbl_av_patterns = Label(self.tab_patterns, text='Patterns browser') lbl_av_patterns.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_av_patterns.grid(row=0, column=1, pady=10, columnspan=6, sticky=W) lbl_content = Label(self.tab_patterns, text='Pattern content') lbl_content.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_content.grid(row=0, column=8, pady=10, sticky=W) lbl_sel_patterns = Label(self.tab_patterns, text='Selected patterns') lbl_sel_patterns.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_sel_patterns.grid(row=3, column=1, pady=10, columnspan=6, sticky=W) lbl_sep7 = Label(self.tab_patterns) lbl_sep7.grid(row=0, column=0, padx=10, pady=10, rowspan=5) self.lbx_av_patterns = Listbox(self.tab_patterns, height=6, width=60, exportselection=0) self.lbx_av_patterns.grid(row=1, column=1, sticky=W, columnspan=5) self.lbx_av_patterns.bind('<<ListboxSelect>>', self.select_available_pattern) vsb_trv_avpat = Scrollbar(self.tab_patterns, orient="vertical", command=self.lbx_av_patterns.yview) vsb_trv_avpat.grid(row=1, column=6, pady=10, sticky=NS) self.lbx_av_patterns.configure(yscrollcommand=vsb_trv_avpat.set) lbl_sep8 = Label(self.tab_patterns) lbl_sep8.grid(row=0, column=7, padx=10, pady=10, rowspan=5) self.btn_view_pd = Button(self.tab_patterns, text='View diagram >>', command=self.click_expand_pd) btn_view_pd_ttp = CreateToolTip(self.btn_view_pd, 'Pattern section diagram') self.txt_pattern_content = Text(self.tab_patterns, height=18, width=85) self.txt_pattern_content.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_pattern_content.tag_configure("center", justify='center') self.txt_pattern_content.tag_add("center", 1.0, "end") self.txt_pattern_content.grid(row=1, column=8, sticky=W, columnspan=2, rowspan=4) vsb_txt_content = Scrollbar(self.tab_patterns, orient="vertical", command=self.txt_pattern_content.yview) vsb_txt_content.grid(row=1, column=10, rowspan=4, pady=10, sticky=NS) self.txt_pattern_content.configure(yscrollcommand=vsb_txt_content.set) btn_add = Button(self.tab_patterns, image=self.add_icon, command=self.click_add_patt) btn_add.grid(row=2, column=3, pady=10, sticky=E) btn_add_ttp = CreateToolTip(btn_add, 'Add pattern') btn_remove = Button(self.tab_patterns, image=self.delete_icon, command=self.click_remove_patt) btn_remove.grid(row=2, column=4, pady=10) btn_remove_ttp = CreateToolTip(btn_remove, 'Remove pattern') self.lbx_sel_patterns = Listbox(self.tab_patterns, height=6, width=60, exportselection=0) self.lbx_sel_patterns.grid(row=4, column=1, sticky=W, columnspan=5) vsb_trv_selpat = Scrollbar(self.tab_patterns, orient="vertical", command=self.lbx_sel_patterns.yview) vsb_trv_selpat.grid(row=4, column=6, pady=10, sticky=NS) self.lbx_sel_patterns.configure(yscrollcommand=vsb_trv_selpat.set) lbl_sep9 = Label(self.tab_patterns) lbl_sep9.grid(row=0, column=11, padx=10, pady=10, rowspan=5) self.tab_desc = Frame(self.tab_control) self.tab_control.add(self.tab_desc, text="Notes", padding=10, image=self.incomplete_icon, compound=RIGHT) lbl_sep10 = Label(self.tab_desc) lbl_sep10.grid(row=0, column=0, padx=10, pady=20) self.txt_solution_desc = Text(self.tab_desc, height=20, width=145) self.txt_solution_desc.config(font=TEXT_FONT) self.txt_solution_desc.bind("<Key>", self.txt_notes_modified) self.txt_solution_desc.grid(row=0, column=1, pady=20, sticky=W) vsb_txt_solution_desc = Scrollbar(self.tab_desc, orient="vertical", command=self.txt_solution_desc.yview) vsb_txt_solution_desc.grid(row=0, column=2, pady=20, sticky=NS) self.txt_solution_desc.configure(yscrollcommand=vsb_txt_solution_desc.set) lbl_sep14 = Label(self.tab_desc) lbl_sep14.grid(row=0, column=3, padx=10, pady=20) self.tab_file = Frame(self.tab_control) self.tab_control.add(self.tab_file, text="File", padding=1, image=self.optional_icon, compound=RIGHT) lbl_upload = Label(self.tab_file, text='Attach a file to the solution: ') lbl_upload.config(fg=TEXT_COLOR, font=SUBTITLE2_FONT) lbl_upload.grid(row=0, column=0, padx=20, pady=20, sticky=W) btn_open = Button(self.tab_file, image=self.open_icon, command=self.click_attach_file) btn_open.grid(row=1, column=0, padx=20, pady=10, sticky=E) btn_open_ttp = CreateToolTip(btn_open, 'Attach file') btn_quit = Button(self.tab_file, image=self.remove_icon, command=self.click_remove_file) btn_quit.grid(row=2, column=0, padx=20, pady=10, sticky=E) btn_quit_ttp = CreateToolTip(btn_quit, 'Remove file') self.canvas_solution = Canvas(self.tab_file, width=350, height=350) self.canvas_solution.config(background='white', borderwidth=1) self.canvas_solution.grid(row=0, column=1, padx=10, pady=10, rowspan=10, sticky=NS) self.tab_control.grid(row=7, column=1, pady=10, sticky=W, columnspan=2) self.canvas_expanded = Canvas(self.tlevel_image_exp, width=500, height=500) self.canvas_expanded.config(background='white', borderwidth=1) self.canvas_expanded.grid() def show_frm(self): self.retrieve_list() self.frm_parent.grid(row=0, column=0, columnspan=9, rowspan=9, pady=10, padx=10) def hide_frm(self): self.frm_parent.grid_forget() self.frm_general.grid_forget() def retrieve_list(self): """ This function shows the existing 'Experimental scenarios for the specified designer' in the home TreeView, if not 'Experimental scenario' then an information box is showed """ # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) # Retrieve scenario for current designer self.directive = Message(action=82, information=['my scenarios', self.current_designer.id]) self.connection = self.directive.send_directive(self.connection) # Set visual components with retrieved scenarios for item in self.connection.message.information: elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(summarize_text(elements[1], 400),)) if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set(self.trv_available.get_children()[0]) self.select_experimental_scenario() else: self.txt_scenario_desc['state'] = NORMAL self.txt_scenario_desc.delete('1.0', 'end-1c') self.txt_scenario_desc.insert('1.0', 'No experiments available for you') self.txt_scenario_desc['state'] = DISABLED def select_experimental_scenario(self, event=None): """ Retrieves information of the selected experimental scenario form the server and saves in a local variable for future usage, also shows the description of the selected experimental scenario in a textbox """ if self.trv_available.item(self.trv_available.selection())['text'] != '': id_selected_ex_scenario = int(self.trv_available.item(self.trv_available.selection())['text']) # Retrieve id of selected item from TreeView self.directive = Message(action=85, information=[id_selected_ex_scenario]) self.connection = self.directive.send_directive(self.connection) self.experimental_scenario = ExperimentalSC(id=id_selected_ex_scenario, title=self.connection.message.information[0], description=self.connection.message.information[1], access_code=self.connection.message.information[2], state=self.connection.message.information[3], availability=self.connection.message.information[4], id_experiment=self.connection.message.information[6], id_description_diagram=self.connection.message.information[5], connection=self.connection) if self.experimental_scenario.description_diagram is not None: self.file_dd = self.experimental_scenario.description_diagram else: self.file_dd = None # Insert description of the current scenario into visual component self.txt_scenario_desc['state'] = NORMAL self.txt_scenario_desc.delete('1.0', 'end-1c') self.txt_scenario_desc.insert('1.0', self.experimental_scenario.description) self.txt_scenario_desc['state'] = DISABLED def click_enter_scenario(self): """ Shows pop-up window that allows validation of access code for the experimental scenario (necessary before showing the problems of the experimental scenario) """ if self.trv_available.item(self.trv_available.selection())['text'] != '': self.tlevel_auth_scenario.title('Need to authenticate') self.tlevel_auth_scenario.deiconify() self.tlevel_auth_scenario.grab_set() self.txt_auth_scenario.focus_set() def click_authenticate_scenario(self): """ If validation process is correct the the experimental scenario and it's problems are loaded into the visual components """ if self.validate_access_code(): self.load_experiment() self.txt_auth_scenario.delete(0, END) self.tlevel_auth_scenario.grab_release() self.tlevel_auth_scenario.withdraw() self.frm_parent.grid_forget() self.frm_general.grid(row=0, column=0, columnspan=9, rowspan=9, pady=10, padx=10) else: messagebox.showerror(parent=self.tlevel_auth_scenario, title='Wrong access code', message='The access code you provided is wrong, retry') def initialize_component_variables(self): """ Initialize to zero or empty state all variables associated with the resolution of a problem for the designers (solutions and measurements) """ self.attached_file = None self.av_patterns_seen = [] self.solution_time = 0 self.selection_time = [] # Time for selecting solution patterns (may be more than one solution pattern) self.selected_pattern_sol = [] # Indicates that any pattern solution has been selected yet def click_authenticate_cancel(self): """ Cancel validation of access code for an experimental scenario, it hides the validation pop-up window """ self.txt_auth_scenario.delete(0, END) self.tlevel_auth_scenario.grab_release() self.tlevel_auth_scenario.withdraw() def validate_access_code(self): """ Verifies that the provided code matches the stored access code for the selected experimental scenario """ if len(self.txt_auth_scenario.get()) != 0: if self.txt_auth_scenario.get() == self.experimental_scenario.access_code: return True return False def click_next_scenario(self): """ After clicking next scenario component. This method prompts the user to confirm his decision. After confirming the system saves the important information (solutions and measurements) and then continue to the next scenario component if available, otherwise the experiment will be closed """ decision = messagebox.askyesno(parent=self.frm_general, title='Confirmation', message="Are you sure you want to continue? Yo won't be able to make any change " "later") if decision: # Confirmation of action if self.save_changes(): self.problems_counter += 1 if self.problems_counter == len(self.experimental_scenario.problems): # If no more problems available messagebox.showinfo(parent=self.frm_general, title='Experiment', message="This concludes the execution of the experiment. Thank you!") self.finish_experiment() self.clear_visual_components() self.hide_frm() self.show_frm() else: # If another scenario component available messagebox.showinfo(parent=self.frm_general, title='Next problem', message="You are about to start a new problem, press Ok when you are ready.") self.clear_visual_components() self.initialize_component_variables() self.load_problem() def save_changes(self): # Validate any problem with info inserted into visual components if self.validate_component_frm(): # No problem, proceed to save info self.solution_time += self.time_thread.seconds self.time_thread.stop() # Stop thread timer acquisition_end_date = datetime.now() # Get measurements of important metrics for the current component problem_id = self.experimental_scenario.problems[self.problems_counter].id current_measurements = [] # Solution time measurement_1 = Measurement(value=float(self.solution_time), acquisition_start_date=self.acquisition_start_date, acquisition_end_date=acquisition_end_date, id_metric=1, id_designer=self.current_designer.id, id_problem=problem_id) current_measurements.append(measurement_1) # Save measurements associated with patterns only when these are available for designer if self.pattern_decision: # Selection time # Getting average if more than one value for this metric selection_time = 0 if len(self.selection_time) == 0 else sum(self.selection_time) / len(self.selection_time) measurement_2 = Measurement(value=float(selection_time), acquisition_start_date=self.acquisition_start_date, acquisition_end_date=acquisition_end_date, id_metric=2, id_designer=self.current_designer.id, id_problem=problem_id) current_measurements.append(measurement_2) # Viewed patterns measurement_3 = Measurement(value=float(len(self.av_patterns_seen)), acquisition_start_date=self.acquisition_start_date, acquisition_end_date=acquisition_end_date, id_metric=3, id_designer=self.current_designer.id, id_problem=problem_id) current_measurements.append(measurement_3) # Chosen patterns measurement_4 = Measurement(value=float(self.lbx_sel_patterns.size()), acquisition_start_date=self.acquisition_start_date, acquisition_end_date=acquisition_end_date, id_metric=4, id_designer=self.current_designer.id, id_problem=problem_id) current_measurements.append(measurement_4) for item in current_measurements: self.directive = Message(action=96, information=[item.value, item.acquisition_start_date, item.acquisition_end_date, item.id_metric, item.id_designer, item.id_problem]) self.connection = self.directive.send_directive(self.connection) # Get info and build the solution to send to the server # Create diagram in DB id_diagram = None if self.attached_file is not None: self.directive = Message(action=61, information=[self.attached_file.file_bytes, self.attached_file.name, 'sent sol']) self.connection = self.directive.send_directive(self.connection) id_diagram = self.connection.message.information[0] # Create object for the solution solution_aux = Solution(annotations=self.txt_solution_desc.get('1.0', 'end-1c'), diagram_id=id_diagram, patterns_id=self.sel_patterns_ids) # Create the solution in DB self.directive = Message(action=101, information=[solution_aux.annotations, solution_aux.diagram_id, self.current_designer.id, problem_id, solution_aux.patterns_id]) self.connection = self.directive.send_directive(self.connection) return True return False def finish_experiment(self): """ This section checks in the server if current designer is the last of the current experimental scenario, and if True changes it's state to finished, otherwise the state remains in executed """ self.directive = Message(action=83, information=['finished', self.experimental_scenario.id]) self.connection = self.directive.send_directive(self.connection) def validate_component_frm(self): if self.pattern_decision: if self.lbx_sel_patterns.size() == 0: messagebox.showwarning(parent=self.frm_general, title='Missing information', message="You haven't selected any pattern") return False if len(self.txt_solution_desc.get('1.0', 'end-1c')) == 0: messagebox.showwarning(parent=self.frm_general, title='Missing information', message='You must add notes to your solution') return False return True def click_attach_file(self): """ Create a File object that is uploaded by the user, validating that there is not a file uploaded already. """ if self.attached_file is None: filename = filedialog.askopenfilename(initialdir=os.getcwd(), title="Select image file", filetypes=[("Diagrams", ".jpg .png .tiff")]) if not filename: return # user cancelled; stop this method self.attached_file = File() self.attached_file.read_file(filename) # Display image into canvas load = Image.open(self.attached_file.filename) load = load.resize((350, 350), Image.ANTIALIAS) self.render = ImageTk.PhotoImage(load) if self.attached_file.image is not None: # if an image was already loaded self.canvas_solution.delete(self.attached_file.image) # remove the previous image self.attached_file.image = self.canvas_solution.create_image(0, 0, anchor='nw', image=self.render) def click_remove_file(self): """ Remove an uploaded file from the system validating it is already uploaded. This method also delete any image in the canvas that may be fulfilled with an image. """ if self.attached_file is not None: # if an image was already loaded self.canvas_solution.delete(self.attached_file.image) self.attached_file = None def click_add_patt(self): """ Adds a pattern to the selected pattern listbox (when available to the designer). """ element = self.lbx_av_patterns.curselection() if element is not None: # Check if listbox is selected index = element[0] id_selected = self.av_patterns_ids[index] if not id_selected in self.sel_patterns_ids: # Check if current pattern_id is not in the 'selected patterns list' if id_selected in self.current_ideal_patterns and not id_selected in self.selected_pattern_sol: # Check if selected pattern matches ideal patterns and for the first time self.selected_pattern_sol.append(id_selected) self.selection_time.append(self.time_thread.seconds) for item in self.available_patterns: if item.id == id_selected: # Find selected pattern in available patterns list self.selected_pattern = item break self.sel_patterns_ids.append(id_selected) # Append pattern_id to selected patterns ids self.lbx_sel_patterns.insert(END, self.selected_pattern.get_joined_main_s()) # Insert pattern name into selected listbox patters self.check_selected_patterns() def click_remove_patt(self): """ Removes a pattern from the selected pattern listbox (when available to the designer). """ element = self.lbx_sel_patterns.curselection() if element is not None: # Check if listbox is selected if element: index = element[0] id_selected = self.sel_patterns_ids[index] self.lbx_sel_patterns.delete(element) # Remove from listbox if id_selected in self.current_ideal_patterns and id_selected in self.selected_pattern_sol: # Check if selected pattern matches ideal patterns and if the the selected pattern correspond to the one selected previously as the pattern solution del self.selection_time[self.selected_pattern_sol.index(id_selected)] self.selected_pattern_sol.remove(id_selected) for item in reversed(self.sel_patterns_ids): if item == id_selected: self.sel_patterns_ids.remove(item) break self.check_selected_patterns() def click_expand_pd(self): # Fill canvas with retrieved image load = Image.open(self.file_pd.filename) load = load.resize((500, 500), Image.ANTIALIAS) self.render_pd = ImageTk.PhotoImage(load) self.canvas_expanded.delete() self.file_pd.image = self.canvas_expanded.create_image(0, 0, anchor='nw', image=self.render_pd) # and display new image self.tlevel_image_exp.deiconify() self.tlevel_image_exp.grab_set() def click_expand_dd(self): # Fill canvas with retrieved image load = Image.open(self.file_dd.filename) load = load.resize((500, 500), Image.ANTIALIAS) self.render_dd = ImageTk.PhotoImage(load) self.canvas_expanded.delete() self.file_dd.image = self.canvas_expanded.create_image(0, 0, anchor='nw', image=self.render_dd) # and display new image self.tlevel_image_exp.deiconify() self.tlevel_image_exp.grab_set() def close_tlevel_image(self): self.tlevel_image_exp.grab_release() self.tlevel_image_exp.withdraw() def clear_visual_components(self): self.txt_prob_desc['state'] = NORMAL self.txt_prob_desc.delete('1.0', 'end-1c') self.lbx_av_patterns.delete(0, END) self.lbx_sel_patterns.delete(0, END) self.txt_pattern_content['state'] = NORMAL self.txt_pattern_content.delete('1.0', 'end-1c') self.txt_pattern_content['state'] = DISABLED self.txt_solution_desc.delete('1.0', 'end-1c') if self.attached_file is not None: # if an image was already loaded self.canvas_solution.delete(self.attached_file.image) self.attached_file = None self.tab_control.tab(0, image=self.incomplete_icon) self.tab_control.tab(1, image=self.incomplete_icon) def load_experiment(self): # Retrieve all information of problems of current scenario self.experimental_scenario.retrieve_problems(Pattern.get_available_patterns(self.connection)) self.initialize_component_variables() self.problems_counter = 0 self.current_designer.get_current_role( self.experimental_scenario.id) # Get role for current experimental scenario # Retrieve patterns for designer in current experimental scenario self.directive = Message(action=42, information=[self.experimental_scenario.id, 1 if self.current_designer.current_group == 'experimental' else 2]) self.connection = self.directive.send_directive(self.connection) self.available_patterns = Pattern.get_patterns(self.connection, self.connection.message.information) # Make patterns visible if the patterns are available for current designer in current experimental scenario if self.available_patterns: self.tab_control.tab(0, state='normal') self.tab_control.select(0) self.pattern_decision = True else: self.tab_control.tab(0, state='hidden') self.tab_control.select(1) self.pattern_decision = False self.btn_view_dd.grid_forget() if self.experimental_scenario.description_diagram is not None: self.btn_view_dd.grid(row=1, column=5, padx=10, pady=10, sticky=N) self.lbl_exp_title['text'] = 'Experiment: {}'.format(self.experimental_scenario.title) self.txt_exp_desc['state'] = NORMAL self.txt_exp_desc.delete('1.0', 'end-1c') self.txt_exp_desc.insert('1.0', self.experimental_scenario.description) self.txt_exp_desc['state'] = DISABLED self.load_problem() def load_problem(self): # Ask for available patterns in current problem for the current designer, depending of the role self.lbl_prob_title['text'] = 'Problem {} of {}: {}'.format(self.problems_counter + 1, len(self.experimental_scenario.problems), self.experimental_scenario.problems[self.problems_counter].brief_description) self.txt_prob_desc['state'] = NORMAL self.txt_prob_desc.delete('1.0', 'end-1c') self.txt_prob_desc.insert('1.0', self.experimental_scenario.problems[self.problems_counter].description) self.txt_prob_desc['state'] = DISABLED self.av_patterns_ids = [] self.sel_patterns_ids = [] for item in self.available_patterns: self.av_patterns_ids.append(item.id) self.lbx_av_patterns.insert(END, item.get_joined_main_s()) self.current_ideal_patterns = self.experimental_scenario.problems[self.problems_counter].solution.patterns_id # Get the patterns of the ideal solution for current problem # Make patterns visible if the patterns are available for current designer in current experimental scenario if self.pattern_decision: self.tab_control.select(0) else: self.tab_control.select(1) self.btn_view_pd.grid_forget() self.time_thread = TimerClass() self.time_thread.begin() self.acquisition_start_date = datetime.now() def select_available_pattern(self, event): id_selected = self.av_patterns_ids[self.lbx_av_patterns.curselection()[0]] # Add selected pattern to the seen pattern (metric) if not id_selected in self.av_patterns_seen: self.av_patterns_seen.append(id_selected) # Retrieve info of the selected pattern (in available patterns list) for item in self.available_patterns: if item.id == id_selected: self.selected_pattern = item break # Set visual components depending on the selected pattern and its info self.btn_view_pd.grid_forget() self.txt_pattern_content['state'] = NORMAL self.txt_pattern_content.delete('1.0', 'end-1c') for item in self.selected_pattern.sections: self.txt_pattern_content.insert('end-1c', item.name + ": ") if item.data_type == 'File': # The section content is a file if item.diagram_id != 0: self.directive = Message(action=65, information=[item.diagram_id]) self.connection = self.directive.send_directive(self.connection) self.file_pd = File() self.file_pd.write_file(self.connection.message.information[0], self.connection.message.information[1]) self.btn_view_pd.grid(row=0, column=9, sticky=E) self.txt_pattern_content.insert('end-1c', "\nClick up button to see diagram ^\n\n") else: self.file_pd = None self.txt_pattern_content.insert('end-1c', "\nNo diagram loaded for this section\n\n") else: self.txt_pattern_content.insert('end-1c', "\n" + item.content + "\n\n") self.txt_pattern_content['state'] = DISABLED def txt_notes_modified(self, event): """ Method that checks if text box of additional notes (solution) is filled or not, so tab image is selected """ if self.txt_solution_desc.get('1.0', 'end-1c') != '': self.tab_control.tab(1, image=self.complete_icon) else: self.tab_control.tab(1, image=self.incomplete_icon) def check_selected_patterns(self): """ Method that checks if list box of selected patterns (solution) is filled or not, so tab image is selected """ if self.lbx_sel_patterns.size() != 0: self.tab_control.tab(0, image=self.complete_icon) else: self.tab_control.tab(0, image=self.incomplete_icon)
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 FormChildSection: def __init__(self, frm_parent, connection): self.connection = connection self.directive = Message() self.decide = True self.id_selected = 0 self.frm_child_list = LabelFrame(frm_parent) self.frm_child_crud = LabelFrame(frm_parent) self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.initialize_components() def initialize_components(self): """ Method that initialize the visual components for each form associated with the local administration """ # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.view_icon = PhotoImage(file=r"./Resources/view.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") self.back_icon = PhotoImage(file=r"./Resources/back.png") # Components for List FRM lbl_sep1 = Label(self.frm_child_list) lbl_sep1.grid(row=0, column=0, padx=10, pady=25) self.trv_available = Treeview(self.frm_child_list, height=15, columns=('N', 'Name', 'Description', 'Data Type')) self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='N', anchor=CENTER) self.trv_available.heading('#2', text='Name', anchor=CENTER) self.trv_available.heading('#3', text='Description', anchor=CENTER) self.trv_available.heading('#4', text='Data Type', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available.column('#2', width=200, minwidth=200, stretch=NO) self.trv_available.column('#3', width=400, minwidth=400, stretch=NO) self.trv_available.column('#4', width=200, minwidth=200, stretch=NO) self.trv_available.grid(row=0, column=1, sticky=W, pady=25) vsb_trv_av = Scrollbar(self.frm_child_list, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) frm_aux4 = Frame(self.frm_child_list) btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new) btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E) btn_new_ttp = CreateToolTip(btn_new, 'New section') btn_view = Button(frm_aux4, image=self.view_icon, command=self.click_view) btn_view.grid(row=1, column=0, pady=5, padx=5, sticky=E) btn_view_ttp = CreateToolTip(btn_view, 'View section') btn_edit = Button(frm_aux4, image=self.modify_icon, command=self.click_update) btn_edit.grid(row=2, column=0, pady=5, padx=5, sticky=E) btn_edit_ttp = CreateToolTip(btn_edit, 'Edit section') btn_delete = Button(frm_aux4, image=self.remove_icon, command=self.click_delete) btn_delete.grid(row=3, column=0, pady=5, padx=5, sticky=E) btn_delete_ttp = CreateToolTip(btn_delete, 'Delete section') frm_aux4.grid(row=0, column=4, pady=25, padx=25, sticky=NW) # Components for CRUD FRM self.frm_aux1 = Frame(self.frm_child_crud) lbl_type = Label(self.frm_aux1, text='Data type*') lbl_type.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_type.grid(row=0, column=0, pady=10, padx=20, sticky=W) lbl_name = Label(self.frm_aux1, text='Name*') lbl_name.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_name.grid(row=1, column=0, pady=10, padx=20, sticky=W) lbl_description = Label(self.frm_aux1, text='Description*\t') lbl_description.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_description.grid(row=2, column=0, pady=10, padx=20, sticky=NW) self.cbx_data = Combobox(self.frm_aux1, state="readonly") self.cbx_data['values'] = ['Text', 'File', 'Classification'] self.cbx_data.grid(row=0, column=2, pady=10, sticky=W) self.cbx_data.bind("<<ComboboxSelected>>", self.cbx_data_selected) self.txt_name = Entry(self.frm_aux1, width=50, font=TEXT_FONT) self.txt_name.grid(row=1, column=2, pady=10, sticky=W) lbl_sep2 = Label(self.frm_aux1) lbl_sep2.grid(row=0, column=1, rowspan=3, padx=10, pady=10) self.txt_description = Text(self.frm_aux1, height=6, width=50, font=TEXT_FONT) self.txt_description.grid(row=2, column=2, pady=10, sticky=W) vsb_txt_desc = Scrollbar(self.frm_aux1, orient="vertical", command=self.txt_description.yview) vsb_txt_desc.grid(row=2, column=3, pady=10, sticky=NS) self.txt_description.configure(yscrollcommand=vsb_txt_desc.set) sep_aux1 = Separator(self.frm_aux1, orient=VERTICAL) sep_aux1.grid(row=0, column=4, sticky=NS, rowspan=4, padx=20) self.btn_save = Button(self.frm_aux1, image=self.save_icon, command=self.click_save) btn_save_ttp = CreateToolTip(self.btn_save, 'Save section') self.btn_back = Button(self.frm_aux1, image=self.back_icon, command=self.click_back) btn_back_ttp = CreateToolTip(self.btn_back, 'Go back') self.btn_cancel = Button(self.frm_aux1, image=self.cancel_icon, command=self.click_cancel) btn_cancel_ttp = CreateToolTip(self.btn_cancel, 'Cancel') self.frm_aux1.grid() # Frame for showing available classifications self.frm_aux2 = Frame(self.frm_aux1) lbl_class = Label(self.frm_aux2, text='Classification\t') lbl_class.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_class.grid(row=0, column=0, pady=10, padx=20, sticky=W) lbl_category = Label(self.frm_aux2, text='Categories') lbl_category.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_category.grid(row=1, column=0, pady=10, padx=20, sticky=NW) lbl_sep3 = Label(self.frm_aux2) lbl_sep3.grid(row=0, column=1, rowspan=2, padx=10, pady=10) self.cbx_classification = Combobox(self.frm_aux2, state="readonly") self.cbx_classification.bind("<<ComboboxSelected>>", self.cbx_class_selected) self.cbx_classification.grid(row=0, column=2, pady=10, sticky=NW) self.lbx_category = Listbox(self.frm_aux2, font=TEXT_FONT, height=10, width=50, selectmode='none') self.lbx_category.config(bg=DISABLED_COLOR) self.lbx_category.grid(row=1, column=2, pady=10, sticky=W) vsb_lbx_cat = Scrollbar(self.frm_aux2, orient="vertical", command=self.lbx_category.yview) vsb_lbx_cat.grid(row=1, column=3, pady=10, sticky=NS) self.lbx_category.configure(yscrollcommand=vsb_lbx_cat.set) def retrieve_list(self): # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) self.directive = Message(action=32) self.connection = self.directive.send_directive(self.connection) # Adding elements into the list for index, item in enumerate(self.connection.message.information): elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 200), summarize_text(elements[2], 400), summarize_text(elements[3], 200))) if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set( self.trv_available.get_children()[0]) def show_frm(self): self.retrieve_list() self.frm_child_list.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def hide_frm(self): self.clear_fields() self.frm_child_list.grid_forget() self.frm_child_crud.grid_forget() def click_new(self): self.section = Section() self.txt_name.focus_set() self.frm_child_crud['text'] = 'New section' self.btn_save.grid(row=0, column=5, padx=20) self.btn_cancel.grid(row=1, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def click_view(self): if len(self.trv_available.selection()) == 1: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) self.directive = Message(action=35, information=[id_selected]) self.connection = self.directive.send_directive(self.connection) self.section = Section( section_id=id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], data_type=self.connection.message.information[2]) self.txt_name.insert(0, self.section.name) self.txt_description.insert('1.0', self.section.description) self.cbx_data.set(self.section.data_type) if self.section.data_type == 'Classification': self.section.classification_id = self.connection.message.information[ 3] self.retrieve_classifications() self.directive = Message( action=70, information=[self.section.classification_id]) self.connection = self.directive.send_directive( self.connection) self.cbx_classification.set( self.connection.message.information[0]) self.cbx_class_selected() self.frm_aux2.grid(row=3, column=0, columnspan=4, sticky=W) self.txt_name['bg'] = DISABLED_COLOR self.txt_description['bg'] = DISABLED_COLOR self.lbx_category['bg'] = DISABLED_COLOR self.txt_name['state'] = DISABLED self.txt_description['state'] = DISABLED self.cbx_data['state'] = DISABLED self.cbx_classification['state'] = DISABLED self.lbx_category['state'] = DISABLED self.frm_child_crud['text'] = 'View section' self.btn_back.grid(row=0, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_update(self): if len(self.trv_available.selection()) == 1: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) self.directive = Message(action=35, information=[id_selected, 'validate']) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: # An error ocurred while trying to update the item messagebox.showerror( parent=self.frm_child_list, title='Can not update the item', message=self.connection.message.information[0]) else: self.section = Section( section_id=id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], data_type=self.connection.message.information[2]) self.txt_name.insert(0, self.section.name) self.txt_description.insert('1.0', self.section.description) self.cbx_data.set(self.section.data_type) if self.section.data_type == 'Classification': self.section.classification_id = self.connection.message.information[ 3] self.retrieve_classifications() self.directive = Message( action=70, information=[self.section.classification_id]) self.connection = self.directive.send_directive( self.connection) self.cbx_classification.set( self.connection.message.information[0]) self.cbx_class_selected() self.frm_aux2.grid(row=3, column=0, columnspan=4, sticky=W) self.frm_child_crud['text'] = 'Update section' self.btn_save.grid(row=0, column=5, padx=20) self.btn_cancel.grid(row=1, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_delete(self): if len(self.trv_available.selection()) == 1: decision = messagebox.askyesno( parent=self.frm_child_list, title='Confirmation', message='Are you sure you want to delete the item?') if decision: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) self.directive = Message(action=34, information=[id_selected]) self.connection = self.directive.send_directive( self.connection) if self.connection.message.action == 5: # An error ocurred while deleting the item messagebox.showerror( parent=self.frm_child_list, title='Can not delete the item', message=self.connection.message.information[0]) else: self.retrieve_list() else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_save(self): if self.validate_fields(): self.section.name = self.txt_name.get() self.section.description = self.txt_description.get( '1.0', 'end-1c') self.section.data_type = self.cbx_data.get() if self.section.section_id == 0: # If creating a section if self.section.data_type == 'Classification': id_class = self.classifications[ self.cbx_classification.current()] self.directive = Message(action=31, information=[ self.section.name, self.section.description, self.section.data_type, id_class ]) else: self.directive = Message(action=31, information=[ self.section.name, self.section.description, self.section.data_type ]) else: # If updating a section if self.section.data_type == 'Classification': id_class = self.classifications[ self.cbx_classification.current()] self.directive = Message(action=33, information=[ self.section.section_id, self.section.name, self.section.description, self.section.data_type, id_class ]) else: self.directive = Message(action=33, information=[ self.section.section_id, self.section.name, self.section.description, self.section.data_type ]) self.connection = self.directive.send_directive(self.connection) self.click_back() def click_back(self): self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def click_cancel(self): decision = True if self.txt_name.get() != self.section.name or \ self.txt_description.get('1.0', 'end-1c') != self.section.description or \ self.cbx_data.get() != self.section.data_type: decision = messagebox.askyesno( parent=self.frm_child_crud, title='Cancel', message='Are you sure you want to cancel?') if decision: self.click_back() def cbx_data_selected(self, event): if self.cbx_data.get() == 'Classification': self.retrieve_classifications() self.frm_aux2.grid(row=3, column=0, columnspan=4, sticky=W) else: self.frm_aux2.grid_forget() self.txt_name.focus_set() def cbx_class_selected(self, event=None): id_class = self.classifications[self.cbx_classification.current()] self.directive = Message(action=72, information=[id_class]) self.connection = self.directive.send_directive(self.connection) self.lbx_category.delete(0, END) for index, item in enumerate(self.connection.message.information): item = item.split('¥') self.lbx_category.insert(END, '{}) {}'.format(index + 1, item[1])) def retrieve_classifications(self): self.classifications = [] self.lbx_category.delete(0, END) self.directive = Message(action=67) self.connection = self.directive.send_directive(self.connection) classifications = [] for item in self.connection.message.information: elements = item.split('¥') classifications.append(elements[1]) self.classifications.append(int(elements[0])) self.cbx_classification['values'] = [] self.cbx_classification['values'] = classifications def validate_fields(self): if len(self.txt_name.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a name for the section') return False if len(self.txt_description.get('1.0', 'end-1c')) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a description for the section') return False if len(self.cbx_data.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must select data type for the section') return False if self.cbx_data.get() == 'Classification' and len( self.cbx_classification.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must select a classification for this section') return False return True def clear_fields(self): self.btn_save.grid_forget() self.btn_cancel.grid_forget() self.btn_back.grid_forget() self.txt_name['state'] = NORMAL self.txt_description['state'] = NORMAL self.cbx_data['state'] = NORMAL self.cbx_classification['state'] = NORMAL self.lbx_category['state'] = NORMAL self.txt_name['bg'] = ENABLED_COLOR self.txt_description['bg'] = ENABLED_COLOR self.lbx_category['bg'] = ENABLED_COLOR self.txt_name.delete(0, END) self.txt_description.delete('1.0', 'end-1c') self.cbx_data.set('') self.cbx_classification.set('') self.lbx_category.delete(0, END) self.frm_aux2.grid_forget()
class FormChildTemplate: def __init__(self, frm_parent, connection): self.connection = connection self.directive = Message() self.decide_template = True self.id_selected = 0 self.frm_child_list = LabelFrame(frm_parent) self.frm_child_crud = LabelFrame(frm_parent) self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.initialize_components() def initialize_components(self): """ Method that initialize the visual components for each form associated with the local administration """ # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") self.add_icon = PhotoImage(file=r"./Resources/right.png") self.delete_icon = PhotoImage(file=r"./Resources/left.png") self.up_arrow = PhotoImage(file=r"./Resources/up_arrow.png") self.down_arrow = PhotoImage(file=r"./Resources/down_arrow.png") self.star_icon = PhotoImage(file=r"./Resources/star.png") self.back_icon = PhotoImage(file=r"./Resources/back.png") self.view_icon = PhotoImage(file=r"./Resources/view.png") # Components for List FRM lbl_sep1 = Label(self.frm_child_list) lbl_sep1.grid(row=0, column=0, padx=10, pady=25) self.trv_available = Treeview(self.frm_child_list, height=20, columns=('N', 'Name')) self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='N', anchor=CENTER) self.trv_available.heading('#2', text='Name', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available.column('#2', width=375, minwidth=375, stretch=NO) self.trv_available.bind("<ButtonRelease-1>", self.select_template_summary) self.trv_available.grid(row=0, column=1, sticky=W, pady=25) vsb_trv_av = Scrollbar(self.frm_child_list, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) frm_aux4 = Frame(self.frm_child_list) btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new) btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E) btn_new_ttp = CreateToolTip(btn_new, 'New template') btn_view = Button(frm_aux4, image=self.view_icon, command=self.click_view) btn_view.grid(row=1, column=0, pady=5, padx=5, sticky=E) btn_view_ttp = CreateToolTip(btn_new, 'View template') btn_edit = Button(frm_aux4, image=self.modify_icon, command=self.click_update) btn_edit.grid(row=2, column=0, pady=5, padx=5, sticky=E) btn_edit_ttp = CreateToolTip(btn_edit, 'Edit template') btn_delete = Button(frm_aux4, image=self.remove_icon, command=self.click_delete) btn_delete.grid(row=3, column=0, pady=5, padx=5, sticky=E) btn_delete_ttp = CreateToolTip(btn_delete, 'Delete template') frm_aux4.grid(row=0, column=3, pady=25, padx=25, sticky=NW) sep_template = Separator(self.frm_child_list, orient=VERTICAL) sep_template.grid(row=0, column=4, sticky=NS, padx=25) frm_aux3 = Frame(self.frm_child_list) lbl_sep3 = Label(frm_aux3) lbl_sep3.grid(row=0, column=0, padx=10, pady=25, rowspan=3) lbl_details = Label(frm_aux3, text='Details') lbl_details.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) lbl_details.grid(row=0, column=1, sticky=W, pady=25, columnspan=2) self.txt_summary = Text(frm_aux3, height=22, width=50) self.txt_summary.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_summary.grid(row=1, column=1) vsb_txt_sum = Scrollbar(frm_aux3, orient="vertical", command=self.txt_summary.yview) vsb_txt_sum.grid(row=1, column=2, sticky=NS) self.txt_summary.configure(yscrollcommand=vsb_txt_sum.set) lbl_sep4 = Label(frm_aux3) lbl_sep4.grid(row=0, column=3, padx=10, pady=25, rowspan=3) lbl_sep5 = Label(frm_aux3) lbl_sep5.grid(row=2, column=1, pady=5, columnspan=2) frm_aux3.grid(row=0, column=5) # Components for CRUD FRM lbl_sep6 = Label(self.frm_child_crud) lbl_sep6.grid(row=0, column=0, padx=10, pady=25, rowspan=10) lbl_name = Label(self.frm_child_crud, text='Name*') lbl_name.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_name.grid(row=0, column=1, pady=25, sticky=NW) lbl_description = Label(self.frm_child_crud, text='Description*') lbl_description.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_description.grid(row=0, column=6, pady=25, sticky=NW) lbl_sep3 = Label(self.frm_child_crud) lbl_sep3.grid(row=0, column=2, padx=10, pady=25) self.txt_name = Entry(self.frm_child_crud, width=30, font=TEXT_FONT) self.txt_name.grid(row=0, column=3, pady=25, sticky=NW) lbl_sep4 = Label(self.frm_child_crud) lbl_sep4.grid(row=0, column=7, padx=10, pady=25) self.txt_description = Text(self.frm_child_crud, height=5, width=49) self.txt_description.config(font=TEXT_FONT) self.txt_description.grid(row=0, column=8, pady=25, sticky=W) vsb_txt_desc = Scrollbar(self.frm_child_crud, orient="vertical", command=self.txt_description.yview) vsb_txt_desc.grid(row=0, column=9, pady=25, sticky=NS) self.txt_description.configure(yscrollcommand=vsb_txt_desc.set) lbl_sep7 = Label(self.frm_child_crud) lbl_sep7.grid(row=0, column=5, padx=10, pady=25, rowspan=3) lbl_sep8 = Label(self.frm_child_crud) lbl_sep8.grid(row=0, column=10, padx=10, pady=25, rowspan=2) lbl_available_d = Label(self.frm_child_crud, text='Available sections') lbl_available_d.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_available_d.grid(row=1, column=1, pady=10, sticky=W, columnspan=4) lbl_selected_d = Label(self.frm_child_crud, text='Selected sections*') lbl_selected_d.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_selected_d.grid(row=1, column=6, pady=10, sticky=W, columnspan=4) self.trv_available_sections = Treeview(self.frm_child_crud, height=10, columns=('N', 'Name', 'Data Type')) self.trv_available_sections.heading('#0', text='ID', anchor=CENTER) self.trv_available_sections.heading('#1', text='N', anchor=CENTER) self.trv_available_sections.heading('#2', text='Name', anchor=CENTER) self.trv_available_sections.heading('#3', text='Data Type', anchor=CENTER) self.trv_available_sections.column('#0', width=0, minwidth=20, stretch=NO) self.trv_available_sections.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available_sections.column('#2', width=150, minwidth=150, stretch=NO) self.trv_available_sections.column('#3', width=120, minwidth=120, stretch=NO) self.trv_available_sections.bind("<Button-1>", self.click_trv_asections) self.trv_available_sections.grid(row=2, column=1, rowspan=7, columnspan=3, sticky=W, pady=10) vsb_trv_avs = Scrollbar(self.frm_child_crud, orient="vertical", command=self.trv_available_sections.yview) vsb_trv_avs.grid(row=2, column=4, rowspan=7, pady=10, sticky=NS) self.trv_available_sections.configure(yscrollcommand=vsb_trv_avs.set) self.trv_selected_sections = Treeview(self.frm_child_crud, height=10, columns=('N', 'Name', 'Data type', 'Mandatory', 'Main')) self.trv_selected_sections.heading('#0', text='ID', anchor=CENTER) self.trv_selected_sections.heading('#1', text='N', anchor=CENTER) self.trv_selected_sections.heading('#2', text='Name', anchor=CENTER) self.trv_selected_sections.heading('#3', text='Data type', anchor=CENTER) self.trv_selected_sections.heading('#4', text='Mandatory', anchor=CENTER) self.trv_selected_sections.heading('#5', text='Main', anchor=CENTER) self.trv_selected_sections.column('#0', width=0, minwidth=20, stretch=NO) self.trv_selected_sections.column('#1', width=20, minwidth=20, stretch=NO) self.trv_selected_sections.column('#2', width=150, minwidth=150, stretch=NO) self.trv_selected_sections.column('#3', width=120, minwidth=120, stretch=NO) self.trv_selected_sections.column('#4', width=80, minwidth=80, stretch=NO) self.trv_selected_sections.column('#5', width=80, minwidth=80, stretch=NO) self.trv_selected_sections.bind("<Button-1>", self.click_trv_ssections) self.trv_selected_sections.bind("<Double-1>", self.click_switch_mandatory) self.trv_selected_sections.grid(row=2, column=6, rowspan=7, columnspan=3, sticky=W, pady=10) vsb_trv_ses = Scrollbar(self.frm_child_crud, orient="vertical", command=self.trv_selected_sections.yview) vsb_trv_ses.grid(row=2, column=9, rowspan=7, pady=10, sticky=NS) self.trv_selected_sections.configure(yscrollcommand=vsb_trv_ses.set) self.lbl_note_optional = Label( self.frm_child_crud, text= 'NOTES:\tTo switch between optional and mandatory, double click ' 'on selected section.\n\tChoose one or up to three main sections ' 'by first selecting the target sections\n\tand then clicking the ' 'star button.\n') self.lbl_note_optional.config(fg=TEXT_COLOR, font=NOTE_FONT, justify=LEFT) self.btn_add = Button(self.frm_child_crud, image=self.add_icon, command=self.click_add) btn_add_ttp = CreateToolTip(self.btn_add, 'Add section') self.btn_remove = Button(self.frm_child_crud, image=self.delete_icon, command=self.click_remove) btn_remove_ttp = CreateToolTip(self.btn_remove, 'Remove section') self.btn_main_section = Button(self.frm_child_crud, image=self.star_icon, command=self.click_main_section) btn_main_section_ttp = CreateToolTip(self.btn_main_section, 'Main section(s)') self.btn_up = Button(self.frm_child_crud, image=self.up_arrow, command=self.click_up) btn_up_ttp = CreateToolTip(self.btn_up, 'Move up') self.btn_down = Button(self.frm_child_crud, image=self.down_arrow, command=self.click_down) btn_down_ttp = CreateToolTip(self.btn_down, 'Move down') sep_aux1 = Separator(self.frm_child_crud, orient=VERTICAL) sep_aux1.grid(row=0, column=11, sticky=NS, rowspan=10) frm_aux1 = Frame(self.frm_child_crud) self.btn_save = Button(frm_aux1, image=self.save_icon, command=self.click_save) btn_save_ttp = CreateToolTip(self.btn_save, 'Save template') self.btn_back = Button(frm_aux1, image=self.back_icon, command=self.click_back) btn_back_ttp = CreateToolTip(self.btn_back, 'Go back') self.btn_cancel = Button(frm_aux1, image=self.cancel_icon, command=self.click_cancel) btn_cancel_ttp = CreateToolTip(self.btn_cancel, 'Cancel') frm_aux1.grid(row=0, column=12, pady=10, padx=25, sticky=NW, rowspan=10) def retrieve_list(self): # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) self.directive = Message(action=37) self.connection = self.directive.send_directive(self.connection) # Adding elements into the list for index, item in enumerate(self.connection.message.information): elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 375))) if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set( self.trv_available.get_children()[0]) self.select_template_summary() def select_template_summary(self, event=None): """ Function activated when the event of selecting an item in the available templates TV is generated. It fills the summary text box with information of the selected template :param event: """ if self.trv_available.item( self.trv_available.selection())['text'] != '': # Clear summary txt box self.txt_summary['state'] = NORMAL self.txt_summary.delete('1.0', 'end-1c') self.id_selected = int( self.trv_available.item(self.trv_available.selection()) ['text']) # Retrieve id of selected item from TreeView self.directive = Message(action=40, information=[self.id_selected ]) # ask for the template self.connection = self.directive.send_directive(self.connection) # Insert template's name and description self.txt_summary.insert( 'end-1c', "Name:\n{}\n\n".format(self.connection.message.information[0])) self.txt_summary.insert( 'end-1c', "Description:\n{}\n\nSections:\n".format( self.connection.message.information[1])) self.directive = Message(action=77, information=[ self.id_selected ]) # ask for the sections of the selected template self.connection = self.directive.send_directive(self.connection) # Adding elements in the summary text box index = 0 for item in self.connection.message.information: elements = item.split('¥') self.txt_summary.insert('end-1c', "{}) {}\t\t{}\t\t{}\n".format(index + 1, elements[3], 'optional' \ if elements[7] == '' else 'mandatory', '' if elements[8] == '' else '(MAIN)')) index += 1 self.txt_summary['state'] = DISABLED def show_frm(self): self.retrieve_list() self.frm_child_list.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def hide_frm(self): self.clear_fields() self.frm_child_list.grid_forget() self.frm_child_crud.grid_forget() def click_new(self): self.view_decision = False # Decision when viewing a template self.template = Template() self.retrieve_sections() self.txt_name.focus_set() self.show_cu_buttons() self.frm_child_crud['text'] = 'New template' self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def click_view(self): if len(self.trv_available.selection()) == 1: self.view_decision = True # Decision when viewing a template self.directive = Message(action=40, information=[self.id_selected]) self.connection = self.directive.send_directive(self.connection) self.template = Template( id=self.id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], sections=self.connection.message.information[2]) self.txt_name.insert(0, self.template.name) self.txt_description.insert('1.0', self.template.description) self.retrieve_sections(self.template.sections) self.txt_name.focus_set() self.disable_visual_components() self.btn_back.grid(row=0, column=0, padx=5, pady=5, sticky=W) self.frm_child_list.grid_forget() self.frm_child_crud['text'] = 'View template' self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_update(self): if len(self.trv_available.selection()) == 1: self.view_decision = False # Decision when viewing a template self.directive = Message( action=40, information=[self.id_selected, 'validate']) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: # An error ocurred while trying to update the item messagebox.showerror( parent=self.frm_child_list, title='Can not edit the template', message=self.connection.message.information[0]) else: self.template = Template( id=self.id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], sections=self.connection.message.information[2]) self.txt_name.insert(0, self.template.name) self.txt_description.insert('1.0', self.template.description) self.retrieve_sections(self.template.sections) self.txt_name.focus_set() self.show_cu_buttons() self.frm_child_crud['text'] = 'Update template' self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_delete(self): if len(self.trv_available.selection()) == 1: decision = messagebox.askyesno( parent=self.frm_child_list, title='Confirmation', message='Are you sure you want to delete the item?') if decision: self.directive = Message(action=39, information=[self.id_selected]) self.connection = self.directive.send_directive( self.connection) if self.connection.message.action == 5: # An error ocurred while deleting the item messagebox.showerror( parent=self.frm_child_list, title='Can not delete the item', message=self.connection.message.information[0]) else: self.retrieve_list() else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def show_cu_buttons(self): self.btn_add.grid(row=5, column=5, padx=25) self.btn_remove.grid(row=6, column=5, padx=25) self.btn_main_section.grid(row=2, column=10, padx=25) self.btn_down.grid(row=6, column=10, padx=25) self.btn_up.grid(row=5, column=10, padx=25) self.btn_save.grid(row=0, column=0, padx=5, pady=5, sticky=W) self.btn_cancel.grid(row=1, column=0, padx=5, pady=5, sticky=W) self.lbl_note_optional.grid(row=9, column=6, columnspan=4, sticky=W) def disable_visual_components(self): self.txt_name['bg'] = DISABLED_COLOR self.txt_description['bg'] = DISABLED_COLOR self.txt_name['state'] = DISABLED self.txt_description['state'] = DISABLED self.btn_add.grid_forget() self.btn_remove.grid_forget() self.btn_main_section.grid_forget() self.btn_down.grid_forget() self.btn_up.grid_forget() def retrieve_sections(self, s_sections=None): if s_sections is None: s_sections = [] self.directive = Message(action=32) self.connection = self.directive.send_directive(self.connection) a_sections = self.connection.message.information for item in self.trv_available_sections.get_children(): self.trv_available_sections.delete(item) for item in self.trv_selected_sections.get_children(): self.trv_selected_sections.delete(item) for item in s_sections: item_aux1 = item.split('¥')[2:6] item_aux2 = [item.split('¥')[-1]] item = item_aux1 + item_aux2 item = '¥'.join(item) if item in a_sections: a_sections.remove(item) for index, item in enumerate(a_sections): elements = item.split('¥') self.trv_available_sections.insert( '', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 150), summarize_text(elements[3], 120))) for index, item in enumerate(s_sections): elements = item.split('¥') self.trv_selected_sections.insert( '', 'end', text=elements[2], values=(index + 1, summarize_text(elements[3], 150), summarize_text(elements[5], 120), elements[7], elements[8])) def click_add(self): """ Function that moves a 'Section' from available tree view to selected tree view (in frm_child_crud) """ if len(self.trv_available_sections.selection()) != 0 and len( self.trv_selected_sections.selection()) == 0: if len(self.trv_selected_sections.get_children()) != 0: index = self.trv_selected_sections.item( self.trv_selected_sections.get_children()[-1])['values'][0] else: index = 0 for row in self.trv_available_sections.selection(): index += 1 values = self.trv_available_sections.item(row)['values'] self.trv_selected_sections.insert( '', 'end', text=self.trv_available_sections.item(row)['text'], values=(index, values[1], values[2], '✓', '')) self.trv_available_sections.delete(row) def click_remove(self): """ Function that moves a 'Section' from selected tree view to available tree view (in frm_child_crud) """ if len(self.trv_selected_sections.selection()) != 0 and len( self.trv_available_sections.selection()) == 0: if len(self.trv_available_sections.get_children()) != 0: index = self.trv_available_sections.item( self.trv_available_sections.get_children() [-1])['values'][0] else: index = 0 for row in self.trv_selected_sections.selection(): index += 1 values = self.trv_selected_sections.item(row)['values'] self.trv_available_sections.insert( '', 'end', text=self.trv_selected_sections.item(row)['text'], values=(index, values[1], values[2])) self.trv_selected_sections.delete(row) def click_up(self): # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: item = self.trv_selected_sections.selection() index = self.trv_selected_sections.index(item) self.trv_selected_sections.move(item, '', index - 1) def click_down(self): # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: item = self.trv_selected_sections.selection() index = self.trv_selected_sections.index(item) self.trv_selected_sections.move(item, '', index + 1) def click_main_section(self): # Make sure a max of three items of 'selected sections' are selected if 4 > len(self.trv_selected_sections.selection()) > 0 and len( self.trv_available_sections.selection()) == 0: # First change all sections as normal (not main) for item in self.trv_selected_sections.get_children(): if self.trv_selected_sections.item(item)['values'][3] != '': values = self.trv_selected_sections.item(item)['values'] self.trv_selected_sections.item( item, values=(values[0], values[1], values[2], values[3], '')) # Set new main sections cont_error = False for row in self.trv_selected_sections.selection(): values = self.trv_selected_sections.item(row)['values'] if values[2] == 'Classification' or values[2] == 'Text': self.trv_selected_sections.item( row, values=(values[0], values[1], values[2], values[3], '✓')) else: # File sections can not be main cont_error = True if cont_error: messagebox.showwarning( parent=self.frm_child_crud, title='Main section(s)', message= "Main section(s) must be of 'Text' or 'Classification' data type" ) else: messagebox.showwarning( parent=self.frm_child_crud, title='Main section(s)', message= 'You must select a minimum of one and a maximum of three sections' ) def click_trv_asections(self, event): """ Function that removes selection from 'available' tree view when 'selected' tree view is selected (in frm_child_crud) """ self.trv_selected_sections.selection_remove( self.trv_selected_sections.selection()) def click_trv_ssections(self, event): """ Function that removes selection from 'selected' tree view when 'available' tree view is selected (in frm_child_crud) """ self.trv_available_sections.selection_remove( self.trv_available_sections.selection()) def click_save(self): if self.validate_fields(): self.template.name = self.txt_name.get() self.template.description = self.txt_description.get( '1.0', 'end-1c') if self.template.id == 0: # Creating a template self.directive = Message(action=36, information=[ self.template.name, self.template.description, [], [], [] ]) for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] self.directive.information[2].append( int(self.trv_selected_sections.item(item)['text'])) self.directive.information[3].append(values[4]) if values[4] != '': self.directive.information[4].append('✓') else: self.directive.information[4].append(values[3]) else: # Updating a template self.directive = Message(action=38, information=[ self.template.id, self.template.name, self.template.description, [], [], [] ]) for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] self.directive.information[3].append( int(self.trv_selected_sections.item(item)['text'])) self.directive.information[4].append(values[4]) if values[4] != '': self.directive.information[5].append('✓') else: self.directive.information[5].append(values[3]) self.connection = self.directive.send_directive(self.connection) self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def click_cancel(self): decision = True if self.txt_name.get() != self.template.name or \ self.txt_description.get('1.0', 'end-1c') != self.template.description or \ len(self.trv_selected_sections.get_children()) != len(self.template.sections): decision = messagebox.askyesno( parent=self.frm_child_crud, title='Cancel', message='Are you sure you want to cancel?') if decision: self.click_back() def click_back(self): self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def validate_fields(self): text_section = False if len(self.txt_name.get()) == 0: messagebox.showwarning(parent=self.frm_child_crud, title='Missing information', message='You must provide a name') return False if len(self.txt_description.get('1.0', 'end-1c')) == 0: messagebox.showwarning(parent=self.frm_child_crud, title='Missing information', message='You must provide a description') return False if len(self.trv_selected_sections.get_children()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must select at least one section') return False for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] if values[2] == 'Text' or values[2] == 'Classification': text_section = True break if not text_section: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='At least one section has to be of text type') return False for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] if values[4] == '✓': if values[2] == 'Text' or values[2] == 'Classification': return True else: messagebox.showwarning( parent=self.frm_child_crud, title='Main section', message= 'The main section has to be of text or classification type' ) return False messagebox.showwarning( parent=self.frm_child_crud, title='Main section', message='You must set one of the selected section as main') return False def clear_fields(self): self.txt_name['state'] = NORMAL self.txt_description['state'] = NORMAL self.txt_name['bg'] = ENABLED_COLOR self.txt_description['bg'] = ENABLED_COLOR self.txt_name.delete(0, END) self.txt_description.delete('1.0', 'end-1c') self.btn_save.grid_forget() self.btn_cancel.grid_forget() self.btn_back.grid_forget() self.lbl_note_optional.grid_forget() def click_switch_mandatory(self, event): if not self.view_decision: # Only if not viewing a template # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: values = self.trv_selected_sections.item( self.trv_selected_sections.focus())['values'] if values[3] == '': self.trv_selected_sections.item( self.trv_selected_sections.focus(), values=(values[0], values[1], values[2], '✓', values[4])) else: self.trv_selected_sections.item( self.trv_selected_sections.focus(), values=(values[0], values[1], values[2], '', values[4]))
class NameView(object): """Shows a treeview of unique names.""" def __init__(self, master, names): self.widget = Frame(master) self._tree = Treeview(self.widget, columns='name') self._tree.grid(row=0,column=0, sticky=(N,S,W,E)) self._tree.view = self self.widget.columnconfigure(0, weight=1) self.widget.rowconfigure(0,weight=1) self._tree.column('name', width=50) self._tree['show'] = 'tree' actions = {'edit': lambda e: self.edit(), 'search': lambda e: self.search(), 'focus_next': lambda e: self.focus_next(), 'focus_prev': lambda e: self.focus_prev(), 'select': lambda e: self._tree.selection_toggle(self._tree.focus()), 'clear_selection': lambda e: self._tree.selection_set([]) } kb.make_bindings(kb.tagview, actions, self._tree.bind) self._iids = dict() self._names = dict() logger.debug('Names: %s', names) self.widget.focus_set = self._tree.focus_set for name in sorted(names): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid self._scroll = Scrollbar(self.widget, command=self._tree.yview) self._tree['yscrollcommand'] = self._scroll.set self._scroll.grid(row=0, column=1, sticky=(N, S)) self.widget.columnconfigure(1, weight=0) def selection(self): logger.debug('Selection: %s', self._tree.selection()) return [self._names[iid] for iid in self._tree.selection()] def edit(self): self._tree.event_generate('<<NameViewEdit>>') def search(self): if len(self._tree.selection()) == 0: self._tree.selection_add(self._tree.focus()) self._tree.event_generate('<<NameViewSearch>>') def append(self, names): logger.debug('Append names: %s', names) for name in names: if name not in self._names.values(): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid def delete(self, name): self._tree.delete(self._iids[name]) del self._names[self._iids[name]] del self._iids[name] def _focus(self, iid): self._tree.focus(iid) self._tree.see(iid) def focus_next(self): cur_iid = self._tree.focus() next_iid = self._tree.next(cur_iid) if next_iid == '': iids = self._tree.get_children() next_iid = iids[0] self._focus(next_iid) def focus_prev(self): cur_iid = self._tree.focus() prev_iid = self._tree.prev(cur_iid) if prev_iid == '': iids = self._tree.get_children() prev_iid = iids[-1] self._focus(prev_iid) def jump_to(self, name): try: iid = self._iids[name] self._focus(iid) except KeyError: pass def get_names(self): return tuple(self._names.values()) def set(self, names): self._tree.delete(*self._iids.values()) self._iids.clear() self._names.clear() for name in sorted(names): iid = self._tree.insert('', 'end', text=name) self._names[iid] = name self._iids[name] = iid
class ManagerWindow: """ 主管理界面入口类,直接无参数创建对象即可。 """ # 窗口宽高 WIN_WIDTH = 800 WIN_HEIGHT = 600 def __init__(self): # 界面根节点 self.root = Tk() # 主窗口标题 self.root.title(MANAGER_TITLE) # 读取config self.config_dict = ConfigOperation.get_dir_from_file() # 主窗口分辨率 self.root.geometry("%sx%s+%s+%s" % ( self.WIN_WIDTH, self.WIN_HEIGHT, int((self.root.winfo_screenwidth() - self.WIN_WIDTH) / 2), int((self.root.winfo_screenheight() - self.WIN_HEIGHT) / 2) )) self.root.minsize(self.WIN_WIDTH, self.WIN_HEIGHT) # 选项卡 self.tab_main = Notebook(self.root) self.tab_main.pack(expand=True, fill=BOTH) # 登录选项卡 self.frame_login = Frame(self.tab_main, bg=BG_COLOR) self.frame_login.pack(side=TOP) self.tab_main.add(self.frame_login, text=TAB_NAME_LIST["login"]["text"]) # 管理选项卡 self.frame_manage = Frame(self.tab_main, bg=BG_COLOR) self.tab_main.add(self.frame_manage, text=TAB_NAME_LIST["manage"]["text"]) # 好友选项卡 self.frame_friend = Frame(self.tab_main, bg=BG_COLOR) self.frame_friend.pack(side=TOP) self.tab_main.add(self.frame_friend, text=TAB_NAME_LIST["friends"]["text"]) # 群选项卡 self.frame_group = Frame(self.tab_main, bg=BG_COLOR) self.frame_group.pack(side=TOP) self.tab_main.add(self.frame_group, text=TAB_NAME_LIST["groups"]["text"]) # 插件选项卡 self.frame_plugin = Frame(self.tab_main, bg=BG_COLOR) self.frame_plugin.pack(side=TOP) self.tab_main.add(self.frame_plugin, text=TAB_NAME_LIST["plugins"]["text"]) # 初始化登录选项卡 self.__init_login_tab() # 初始化好友选项卡 self.__init_friend_tab() # 初始化群选项卡 self.__init_group_tab() # 初始化管理选项卡 self.__init_manage_tab() # 初始化插件选项卡 self.__init_plugin_tab() # 关闭窗口自动释放Session self.root.protocol("WM_DELETE_WINDOW", lambda: self.__on_close_root()) # 刷新显示 self.__refresh() # 运行相关线程 fetch_message_thread = FetchMessageThread() fetch_message_thread.daemon = True fetch_message_thread.start() # 运行插件初始化方法 PluginHandler.call_init() # 执行自动连接一次 self.__auto_connect() # 显示 self.root.mainloop() def __init_login_tab(self): """ 初始化登录选项卡界面 :return: 无 """ # 左边列表的frame frame_login_list = Frame(self.frame_login, bg=BG_COLOR) frame_login_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 列表,用于保存连接记录 self.treeview_login_list = Treeview( frame_login_list, columns=[ LOGIN_GUIDE["host"], LOGIN_GUIDE["port"], LOGIN_GUIDE["authkey"], LOGIN_GUIDE["qq"] ], show="headings", selectmode=BROWSE ) self.treeview_login_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_login_list.column( LOGIN_GUIDE["host"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["host"], text=LOGIN_GUIDE["host"] ) self.treeview_login_list.column( LOGIN_GUIDE["port"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["port"], text=LOGIN_GUIDE["port"] ) self.treeview_login_list.column( LOGIN_GUIDE["authkey"], width=40 ) self.treeview_login_list.heading( LOGIN_GUIDE["authkey"], text=LOGIN_GUIDE["authkey"] ) self.treeview_login_list.column( LOGIN_GUIDE["qq"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["qq"], text=LOGIN_GUIDE["qq"] ) # 设定双击事件 self.treeview_login_list.bind( "<Double-Button-1>", lambda event: self.__on_double_click_login_list_content() ) # 设定登录列表的滚动条 scrollbar_login_list = Scrollbar(frame_login_list) scrollbar_login_list.pack(fill="y", expand=True) self.treeview_login_list.config(yscrollcommand=scrollbar_login_list.set) scrollbar_login_list.config(command=self.treeview_login_list.yview) # 设置列表右键菜单 self.treeview_login_list.bind("<Button-3>", self.__show_login_list_pop_up_menu) # 登录界面显示的那一坨 frame_login_menu = Frame(self.frame_login, bg=BG_COLOR) frame_login_menu.pack(side=LEFT, padx=5, pady=5) # mirai端地址 Label(frame_login_menu, text=LOGIN_GUIDE["host"], bg=BG_COLOR).grid(row=0, sticky=E, padx=5, pady=5) self.entry_host = Entry(frame_login_menu) self.entry_host.grid(row=0, column=1, sticky=W, padx=5, pady=5) # mirai端端口号 Label(frame_login_menu, text=LOGIN_GUIDE["port"], bg=BG_COLOR).grid(row=1, sticky=E, padx=5, pady=5) self.entry_port = Entry(frame_login_menu) self.entry_port.grid(row=1, column=1, sticky=W, padx=5, pady=5) # mirai端http授权码 Label(frame_login_menu, text=LOGIN_GUIDE["authkey"], bg=BG_COLOR).grid( row=2, sticky=E, padx=5, pady=5 ) self.entry_authkey = Entry(frame_login_menu, show=PWD_CHAR_CIRCLE) self.entry_authkey.grid(row=2, column=1, sticky=W, padx=5, pady=5) # 用于激活sessioKey的qq号码 Label(frame_login_menu, text=LOGIN_GUIDE["qq"], bg=BG_COLOR).grid( row=3, sticky=E, padx=5, pady=5 ) self.entry_qq = Entry(frame_login_menu) self.entry_qq.grid(row=3, column=1, sticky=W, padx=5, pady=5) # 自动连接复选框 self.auto_connect_var = BooleanVar() self.checkbutton_auto_connect = Checkbutton( frame_login_menu, text=AUTO_CONNECT_GUIDE, onvalue=True, offvalue=False, variable=self.auto_connect_var, bg=BG_COLOR ) self.checkbutton_auto_connect.grid(row=4, column=0, padx=5, pady=5, columnspan=2) # 连接按钮 self.btn_connect = Button( frame_login_menu, text=BTN_TEXT_CONN["connect"], width=15, command=lambda: self.__on_click_connect_event(), ) self.btn_connect.grid(row=5, columnspan=2, padx=5, pady=5) # 添加到登录列表按钮 self.btn_save_login = Button( frame_login_menu, width=15, text=BTN_TEXT_ADD_LOGIN, command=lambda: self.__on_click_add_to_login_list() ) self.btn_save_login.grid(row=6, columnspan=2, padx=5, pady=5) # 状态栏 self.label_login_status_bar = Label( self.root, text=LOGIN_STATUS_BAR_TEXT["notConnect"], fg=STATUS_BAR_COLOR["normal"] ) self.label_login_status_bar.pack(side=LEFT) # 下面开始从config中将内容填充进文本框中 self.entry_host.delete(0, END) self.entry_host.insert(END, self.config_dict["lastConnection"]["host"]) self.entry_port.delete(0, END) self.entry_port.insert(END, self.config_dict["lastConnection"]["port"]) self.entry_authkey.delete(0, END) self.entry_authkey.insert(END, self.config_dict["lastConnection"]["authkey"]) self.entry_qq.delete(0, END) self.entry_qq.insert(END, self.config_dict["lastConnection"]["qq"]) # 自动连接复选框内容 self.auto_connect_var.set(self.config_dict["autoConnect"]) def __init_friend_tab(self): """ 初始化好友选项卡内容 :return: 无 """ # 创建好友列表框架 frame_friend_list = Frame(self.frame_friend, bg=BG_COLOR) frame_friend_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 创建消息测试发送框架 frame_friend_send = Frame(self.frame_friend, bg=BG_COLOR) frame_friend_send.pack( side=LEFT, padx=5, pady=5 ) # 设置列表 self.treeview_friend_list = Treeview( frame_friend_list, columns=[ FRIEND_GUIDE["qq"], FRIEND_GUIDE["nickname"], FRIEND_GUIDE["remark"] ], show="headings", selectmode=BROWSE ) self.treeview_friend_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_friend_list.column( FRIEND_GUIDE["qq"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["qq"], text=FRIEND_GUIDE["qq"] ) self.treeview_friend_list.column( FRIEND_GUIDE["nickname"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["nickname"], text=FRIEND_GUIDE["nickname"] ) self.treeview_friend_list.column( FRIEND_GUIDE["remark"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["remark"], text=FRIEND_GUIDE["remark"] ) # 设定好友列表的滚动条 scrollbar_friend_list = Scrollbar(frame_friend_list) scrollbar_friend_list.pack(fill="y", expand=True) self.treeview_friend_list.config(yscrollcommand=scrollbar_friend_list.set) scrollbar_friend_list.config(command=self.treeview_friend_list.yview) # 刷新列表按钮 Button( frame_friend_send, text=BTN_FRIEND_REFRESH, command=lambda: self.__on_click_refresh_friend_list_event() ).grid(row=0, padx=5, pady=5) # 发送纯文本窗口标题 Label(frame_friend_send, text=SEND_TITLE, bg=BG_COLOR).grid(row=1, padx=5, pady=5) # 发送纯文本窗口 self.text_friend_send = Text(frame_friend_send, width=30, height=5) self.text_friend_send.grid(row=2, padx=5, pady=5) # 发送按钮 Button( frame_friend_send, text=BTN_SEND, command=lambda: self.__on_click_send_friend_message() ).grid(row=3, padx=5, pady=5) def __init_group_tab(self): """ 初始化群选项卡内容 :return: 无 """ # 创建好友列表框架 frame_group_list = Frame(self.frame_group, bg=BG_COLOR) frame_group_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 创建消息测试发送框架 frame_group_send = Frame(self.frame_group, bg=BG_COLOR) frame_group_send.pack( side=LEFT, padx=5, pady=5 ) # 设置列表 self.treeview_group_list = Treeview( frame_group_list, columns=[ GROUP_GUIDE["group"], GROUP_GUIDE["name"], GROUP_GUIDE["permission"] ], show="headings", selectmode=BROWSE ) self.treeview_group_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_group_list.column( GROUP_GUIDE["group"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["group"], text=GROUP_GUIDE["group"] ) self.treeview_group_list.column( GROUP_GUIDE["name"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["name"], text=GROUP_GUIDE["name"] ) self.treeview_group_list.column( GROUP_GUIDE["permission"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["permission"], text=GROUP_GUIDE["permission"] ) # 设定群列表的滚动条 scrollbar_group_list = Scrollbar(frame_group_list) scrollbar_group_list.pack(fill="y", expand=True) self.treeview_group_list.config(yscrollcommand=scrollbar_group_list.set) scrollbar_group_list.config(command=self.treeview_group_list.yview) # 刷新列表按钮 Button( frame_group_send, text=BTN_GROUP_REFRESH, command=lambda: self.__on_click_refresh_group_list_event() ).grid(row=0, padx=5, pady=5) # 发送纯文本窗口标题 Label(frame_group_send, text=SEND_TITLE, bg=BG_COLOR).grid(row=1, padx=5, pady=5) # 发送纯文本窗口 self.text_group_send = Text(frame_group_send, width=30, height=5) self.text_group_send.grid(row=2, padx=5, pady=5) # 发送按钮 Button( frame_group_send, text=BTN_SEND, command=lambda: self.__on_click_send_group_message() ).grid(row=3, padx=5, pady=5) def __init_manage_tab(self): """ 初始化管理选项卡 :return: 无 """ f_manage = Frame(self.frame_manage, bg=BG_COLOR) f_manage.pack(padx=5, pady=5, expand=True) # 指定头指示 Label(f_manage, text=MANAGE_GUIDE["commandHead"], bg=BG_COLOR).grid( row=0, column=0, padx=5, pady=5, sticky=E ) # 指令头文本框 self.entry_command_head = Entry(f_manage) self.entry_command_head.grid(row=0, column=1, padx=5, pady=5, sticky=EW) # 调试复选框 self.debug_var = BooleanVar() checkbutton_debug = Checkbutton( f_manage, text=MANAGE_GUIDE["debug"], onvalue=True, offvalue=False, variable=self.debug_var, bg=BG_COLOR ) checkbutton_debug.grid(row=1, column=0, columnspan=3, padx=5, pady=5) # 启用机器人 self.enable_var = BooleanVar() checkbutton_enable = Checkbutton( f_manage, text=MANAGE_GUIDE["enable"], onvalue=True, offvalue=False, variable=self.enable_var, bg=BG_COLOR ) checkbutton_enable.grid(row=2, column=0, columnspan=3, padx=5, pady=5) # 配置保存 Button( f_manage, text=MANAGE_GUIDE["saveConfig"], command=self.__on_click_save_config ).grid( row=3, column=1, padx=5, pady=5, sticky=EW ) # bot管理qq列表 self.treeview_op_list = Treeview( f_manage, columns=[ MANAGE_GUIDE["botOpQQ"] ], show="headings", selectmode=BROWSE ) self.treeview_op_list.column(MANAGE_GUIDE["botOpQQ"], width=200) self.treeview_op_list.heading(MANAGE_GUIDE["botOpQQ"], text=MANAGE_GUIDE["botOpQQ"]) self.treeview_op_list.grid( row=4, column=0, columnspan=3, rowspan=10, sticky=EW ) # 列表右键 self.treeview_op_list.bind("<Button-3>", self.__show_op_list_pop_up_menu) # 添加管理标签 Label(f_manage, text=MANAGE_GUIDE["addOpQQ"], bg=BG_COLOR).grid(row=14, column=0, padx=5, pady=5) # 添加管理文本框 self.entry_add_op = Entry(f_manage) self.entry_add_op.grid(row=14, column=1, padx=5, pady=5) # 添加添加按钮 Button( f_manage, text=MANAGE_GUIDE["btnAddOpQQ"], command=lambda: self.__on_click_add_op() ).grid(row=14, column=2, padx=5, pady=5, sticky=EW) def __init_plugin_tab(self): """ 初始化插件选项卡 :return: 无 """ # 指示标签 Label(self.frame_plugin, text=PLUGIN_LABEL_TEXT, bg=BG_COLOR).pack(side=TOP) # 插件列表frame frame_plugin_list = Frame(self.frame_plugin, bg=BG_COLOR) frame_plugin_list.pack( side=TOP, expand=True, fill=BOTH, padx=5, pady=5 ) # 插件列表 self.treeview_plugin_list = Treeview( frame_plugin_list, columns=[ PLUGIN_GUIDE["pluginName"] ], show="headings", selectmode=BROWSE ) self.treeview_plugin_list.pack(fill=BOTH, expand=True, side=LEFT) self.treeview_plugin_list.column(PLUGIN_GUIDE["pluginName"]) self.treeview_plugin_list.heading(PLUGIN_GUIDE["pluginName"], text=PLUGIN_GUIDE["pluginName"]) # 设定插件列表滚动条 scrollbar_plugin_list = Scrollbar(frame_plugin_list) scrollbar_plugin_list.pack(fill="y", expand=True) self.treeview_plugin_list.config(yscrollcommand=scrollbar_plugin_list.set) scrollbar_plugin_list.config(command=self.treeview_plugin_list.yview) def __on_click_connect_event(self): """ 点击连接按钮事件 :return: 无 """ if not GlobalValues.is_connected: # 如果是要连接 # 存到全局使用变量 GlobalValues.conn_host = self.entry_host.get() GlobalValues.conn_port = self.entry_port.get() GlobalValues.conn_authkey = self.entry_authkey.get() try: # 转换为整型后保存 GlobalValues.conn_qq = int(self.entry_qq.get()) except ValueError: self.label_login_status_bar.config(text=LOGIN_STATUS_BAR_TEXT["wrongQQ"], fg=STATUS_BAR_COLOR["failed"]) return # 修改界面上的一些内容为不可修改 self.__set_login_tools_active(False) # 修改按钮内容 self.btn_connect.config(text=BTN_TEXT_CONN["disconnect"]) # 修改状态栏内容 self.label_login_status_bar.config(text=LOGIN_STATUS_BAR_TEXT["connecting"], fg=STATUS_BAR_COLOR["normal"]) # 调用连接 try: Conn.new_session_key() except ( requests.exceptions.InvalidURL, requests.exceptions.ConnectionError, ): # 连接错误 # 错误信息显示到状态栏 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["connectFailed"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return except WrongAuthkeyException: # 授权码错误 # 显示到状态栏 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["wrongAuthkey"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return except BotNotExistException: # bot不存在错误 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["qqNotExist"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["connected"], fg=STATUS_BAR_COLOR["passed"] ) # 修改连接状态值 GlobalValues.is_connected = True # 修改上次连接键值对 ConfigOperation.modify_dict("lastConnection", { "host": GlobalValues.conn_host, "port": GlobalValues.conn_port, "authkey": GlobalValues.conn_authkey, "qq": GlobalValues.conn_qq }) # 修改文件中自动连接开关 ConfigOperation.modify_dict("autoConnect", self.auto_connect_var.get()) else: # 如果要断开连接 # 修改文本框为可修改 self.__set_login_tools_active(True) # 修改按钮名称 self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) # 修改属性值 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["disconnectSuccess"], fg=STATUS_BAR_COLOR["normal"] ) # 释放session Conn.release_session_key() # 修改连接状态值 GlobalValues.is_connected = False def __set_login_tools_active(self, active: bool): """ 修改界面上的一些内容为不可修改 :param active: bool,如果为False则禁用掉文本框,否则启用 :return: 无 """ if active: self.entry_host.config(state=ACTIVE) self.entry_port.config(state=ACTIVE) self.entry_authkey.config(state=ACTIVE) self.entry_qq.config(state=ACTIVE) self.checkbutton_auto_connect.config(state=ACTIVE) else: self.entry_host.config(state=DISABLED) self.entry_port.config(state=DISABLED) self.entry_authkey.config(state=DISABLED) self.entry_qq.config(state=DISABLED) self.checkbutton_auto_connect.config(state=DISABLED) def __on_close_root(self): """ 关闭窗口的事件 :return: 无 """ # 如果正在连接则释放连接 if GlobalValues.is_connected: Conn.release_session_key() # 杀掉root self.root.destroy() def __refresh(self): """ 用于刷新界面,在必要时调用 :return: 无 """ def refresh_login_list(): """ 刷新登录列表 :return: 无 """ # 删除目前表中的所有内容 self.treeview_login_list.delete(*self.treeview_login_list.get_children()) # 把登录列表内容添加到显示中 for one_record in LoginListOperation.get_list_from_file(): self.treeview_login_list.insert("", index=END, values=( one_record["host"], one_record["port"], one_record["authkey"], one_record["qq"] )) def refresh_op_list(): """ 刷新bot管理员qq列表 :return: 无 """ # 删除目前表中的所有内容 self.treeview_op_list.delete(*self.treeview_op_list.get_children()) # 把内容添加到显示中 for one_record in OpListOperation.get_list(): self.treeview_op_list.insert("", index=END, values=( one_record )) def refresh_config(): """ 刷新配置 :return: 无 """ # 重新获取config self.config_dict = ConfigOperation.get_dir_from_file() # 将文件中的内容显示到界面中 self.entry_command_head.delete(0, END) self.entry_command_head.insert(END, self.config_dict["commandHead"]) # 设置复选框默认勾选 self.debug_var.set(self.config_dict["debug"]) self.enable_var.set(self.config_dict["enable"]) # 将内容设置到全局变量 GlobalValues.command_head = self.config_dict["commandHead"] GlobalValues.debug_var = self.debug_var GlobalValues.enable_var = self.enable_var def refresh_plugin_list(): # 获取插件名称 plugin_names = PluginHandler.get_plugin_name_list() # 显示 self.treeview_plugin_list.delete(*self.treeview_plugin_list.get_children()) for name in plugin_names: self.treeview_plugin_list.insert("", index=END, values=( name )) # 调用刷新登录列表 refresh_login_list() # 调用刷新op列表 refresh_op_list() # 刷新config显示 refresh_config() # 刷新插件列表显示 refresh_plugin_list() def __on_click_add_to_login_list(self): """ 将填写内容添加到列表中 :return: 无 """ # 非空检测 if [ self.entry_host.get(), self.entry_port.get(), self.entry_authkey.get(), self.entry_qq.get() ] == [""] * 4: return # 调用添加登录项方法 LoginListOperation.add_to_list( self.entry_host.get(), self.entry_port.get(), self.entry_authkey.get(), self.entry_qq.get() ) # 刷新显示 self.__refresh() def __on_double_click_login_list_content(self): """ 双击登录列表项目时,自动填充到右侧 :return: 无 """ # 获取item的值 item_list = self.treeview_login_list.item(self.treeview_login_list.focus(), "values") # 获取需要的项目并设置 self.entry_host.delete(0, END) self.entry_host.insert(END, item_list[0]) self.entry_port.delete(0, END) self.entry_port.insert(END, item_list[1]) self.entry_authkey.delete(0, END) self.entry_authkey.insert(END, item_list[2]) self.entry_qq.delete(0, END) self.entry_qq.insert(END, item_list[3]) def __show_login_list_pop_up_menu(self, event): """ 显示右键菜单 :param event: 事件 :return: 无 """ def on_delete_event(item_id): """ 删除选项的事件 :return: 无 """ # 删除该项 LoginListOperation.remove_from_list(*self.treeview_login_list.item(item_id, "values")) self.treeview_login_list.delete(item_id) self.__refresh() # 获取选择对象 iid = self.treeview_login_list.identify_row(event.y) # 如果有选择,则弹出右键菜单 if iid: self.treeview_login_list.selection_set(iid) menu_pop_up = Menu(self.treeview_login_list, tearoff=False) menu_pop_up.add_command( label=POP_UP_MENU_DELETE_STR, command=lambda: on_delete_event(iid) ) menu_pop_up.post(event.x_root, event.y_root) def __on_click_refresh_friend_list_event(self): """ 点击刷新好友列表事件 :return: 无 """ try: # 如果未连接,则可能会抛出异常,此处直接弹出错误消息框 friend_list = Conn.get_friend_list() except: messagebox.showerror(message=REFRESH_ERROR_MSG) return # 删除列表内容 self.treeview_friend_list.delete(*self.treeview_friend_list.get_children()) # 解析friend_list for friend_block in friend_list: self.treeview_friend_list.insert("", index=END, values=( friend_block["id"], friend_block["nickname"], friend_block["remark"] )) def __on_click_refresh_group_list_event(self): """ 点击刷新群列表事件 :return: 无 """ try: # 如果未连接,则可能会抛出异常,此处直接弹出错误消息框 group_list = Conn.get_group_list() except: messagebox.showerror(message=REFRESH_ERROR_MSG) return # 删除列表内容 self.treeview_group_list.delete(*self.treeview_group_list.get_children()) # 解析group_list for group_block in group_list: self.treeview_group_list.insert("", index=END, values=( group_block["id"], group_block["name"], group_block["permission"] )) def __on_click_send_friend_message(self): """ 点击发送消息给好友按钮 :return: 无 """ # 获取到选中好友的值列表 value_list = self.treeview_friend_list.item(self.treeview_friend_list.focus(), "values") try: # 获取qq并发送消息 qq = value_list[0] message_chain = MessageChain() text = self.text_friend_send.get(1.0, END) if text == "\n": return message_chain.add_plain_text(text) Conn.send_friend_message(qq, message_chain) self.text_friend_send.delete(1.0, END) except: messagebox.showerror(message=SEND_ERROR_MSG) return def __on_click_send_group_message(self): """ 点击发送消息给群按钮 :return: 无 """ # 获取到选中群的值列表 value_list = self.treeview_group_list.item(self.treeview_group_list.focus(), "values") try: # 获取qq并发送消息 qq = value_list[0] message_chain = MessageChain() text = self.text_group_send.get(1.0, END) if text == "\n": return message_chain.add_plain_text(text) Conn.send_group_message(qq, message_chain) self.text_group_send.delete(1.0, END) except: messagebox.showerror(message=SEND_ERROR_MSG) return def __on_click_add_op(self): """ 点击添加op按钮事件 :return: 无 """ content = self.entry_add_op.get() # 如果添加op的文本框中没有东西,则不添加 if content == "": return # 如果转换数字出错则不添加 try: op_qq = int(content) except ValueError: return # 添加到op列表中 OpListOperation.add_to_list(op_qq) # 刷新显示 self.__refresh() def __show_op_list_pop_up_menu(self, event): """ op列表右键菜单 :return: 无 """ def on_delete_event(): """ 删除选项的事件 :return: 无 """ # 删除该项 # 注意此处的强转,由于能够保证显示出来的内容一定只含有数字,故可以直接转换 OpListOperation.remove_from_list(int(self.treeview_op_list.item(op_qq, "values")[0])) self.treeview_op_list.delete(op_qq) self.__refresh() # 获取选择对象 op_qq = self.treeview_op_list.identify_row(event.y) # 如果有选择,则弹出右键菜单 if op_qq: menu_pop_up = Menu(self.treeview_op_list, tearoff=False) self.treeview_op_list.selection_set(op_qq) menu_pop_up.add_command( label=POP_UP_MENU_DELETE_STR, command=lambda: on_delete_event() ) menu_pop_up.post(event.x_root, event.y_root) def __on_click_save_config(self): """ 点击保存配置事件 :return: 无 """ content = self.entry_command_head.get() # 如果为空,则不存入,但是刷新 # 这样是为了保证点击后会显示原来的设置 if content == "": self.__refresh() return ConfigOperation.modify_dict("commandHead", content) ConfigOperation.modify_dict("debug", self.debug_var.get()) ConfigOperation.modify_dict("enable", self.enable_var.get()) # 刷新 self.__refresh() # 弹出对话框 messagebox.showinfo(message=MANAGE_GUIDE["successSaveCommandHeadMsg"]) def __auto_connect(self): if self.config_dict["autoConnect"]: self.__on_click_connect_event()
class Multicolumn_Listbox(Frame): _style_index = 0 class List_Of_Rows(object): def __init__(self, multicolumn_listbox): self._multicolumn_listbox = multicolumn_listbox def data(self, index): return self._multicolumn_listbox.row_data(index) def get(self, index): return Row(self._multicolumn_listbox, index) def insert(self, data, index=None): self._multicolumn_listbox.insert_row(data, index) def delete(self, index): self._multicolumn_listbox.delete_row(index) def update(self, index, data): self._multicolumn_listbox.update_row(index, data) def select(self, index): self._multicolumn_listbox.select_row(index) def deselect(self, index): self._multicolumn_listbox.deselect_row(index) def set_selection(self, indices): self._multicolumn_listbox.set_selection(indices) def __getitem__(self, index): return self.get(index) def __setitem__(self, index, value): return self._multicolumn_listbox.update_row(index, value) def __delitem__(self, index): self._multicolumn_listbox.delete_row(index) def __len__(self): return self._multicolumn_listbox.number_of_rows class List_Of_Columns(object): def __init__(self, multicolumn_listbox): self._multicolumn_listbox = multicolumn_listbox def data(self, index): return self._multicolumn_listbox.get_column(index) def get(self, index): return Column(self._multicolumn_listbox, index) def delete(self, index): self._multicolumn_listbox.delete_column(index) def update(self, index, data): self._multicolumn_listbox.update_column(index, data) def __getitem__(self, index): return self.get(index) def __setitem__(self, index, value): return self._multicolumn_listbox.update_column(index, value) def __delitem__(self, index): self._multicolumn_listbox.delete_column(index) def __len__(self): return self._multicolumn_listbox.number_of_columns def __init__(self, master, columns, data=None, command=None, sort=True, select_mode=None, heading_anchor=CENTER, cell_anchor=W, style=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, field_background=None, heading_font=None, heading_background=None, heading_foreground=None, cell_pady=2, cell_background=None, cell_foreground=None, cell_font=None, headers=True): self._stripped_rows = stripped_rows self._columns = columns self._number_of_rows = 0 self._number_of_columns = len(columns) self.row = self.List_Of_Rows(self) self.column = self.List_Of_Columns(self) s = Style() if style is None: style_name = "Multicolumn_Listbox%s.Treeview" % self._style_index self._style_index += 1 else: style_name = style style_map = {} if selection_background is not None: style_map["background"] = [('selected', selection_background)] if selection_foreground is not None: style_map["foeground"] = [('selected', selection_foreground)] if style_map: s.map(style_name, **style_map) style_config = {} if cell_background is not None: style_config["background"] = cell_background if cell_foreground is not None: style_config["foreground"] = cell_foreground if cell_font is None: font_name = s.lookup(style_name, "font") cell_font = nametofont(font_name) else: if not isinstance(cell_font, Font): if isinstance(cell_font, basestring): cell_font = nametofont(cell_font) else: if len(Font) == 1: cell_font = Font(family=cell_font[0]) elif len(Font) == 2: cell_font = Font(family=cell_font[0], size=cell_font[1]) elif len(Font) == 3: cell_font = Font(family=cell_font[0], size=cell_font[1], weight=cell_font[2]) else: raise ValueError( "Not possible more than 3 values for font") style_config["font"] = cell_font self._cell_font = cell_font self._rowheight = cell_font.metrics("linespace") + cell_pady style_config["rowheight"] = self._rowheight if field_background is not None: style_config["fieldbackground"] = field_background s.configure(style_name, **style_config) heading_style_config = {} if heading_font is not None: heading_style_config["font"] = heading_font if heading_background is not None: heading_style_config["background"] = heading_background if heading_foreground is not None: heading_style_config["foreground"] = heading_foreground heading_style_name = style_name + ".Heading" s.configure(heading_style_name, **heading_style_config) treeview_kwargs = {"style": style_name} if height is not None: treeview_kwargs["height"] = height if padding is not None: treeview_kwargs["padding"] = padding if headers: treeview_kwargs["show"] = "headings" else: treeview_kwargs["show"] = "" if select_mode is not None: treeview_kwargs["selectmode"] = select_mode self.interior = Treeview(master, columns=columns, **treeview_kwargs) if command is not None: self._command = command self.interior.bind("<<TreeviewSelect>>", self._on_select) for i in range(0, self._number_of_columns): if sort: self.interior.heading( i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self.sort_by(col, descending=False)) else: self.interior.heading(i, text=columns[i], anchor=heading_anchor) if adjust_heading_to_content: self.interior.column(i, width=Font().measure(columns[i])) self.interior.column(i, anchor=cell_anchor) if data is not None: for row in data: self.insert_row(row) @property def row_height(self): return self._rowheight @property def font(self): return self._cell_font def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None): kwargs = {} for config_name in ("width", "anchor", "stretch", "minwidth"): config_value = locals()[config_name] if config_value is not None: kwargs[config_name] = config_value self.interior.column('#%s' % (index + 1), **kwargs) def row_data(self, index): try: item_ID = self.interior.get_children()[index] except IndexError: raise ValueError("Row index out of range: %d" % index) return self.item_ID_to_row_data(item_ID) def update_row(self, index, data): try: item_ID = self.interior.get_children()[index] except IndexError: raise ValueError("Row index out of range: %d" % index) if len(data) == len(self._columns): self.interior.item(item_ID, values=data) else: raise ValueError("The multicolumn listbox has only %d columns" % self._number_of_columns) def delete_row(self, index): list_of_items = self.interior.get_children() try: item_ID = list_of_items[index] except IndexError: raise ValueError("Row index out of range: %d" % index) self.interior.delete(item_ID) self._number_of_rows -= 1 if self._stripped_rows: for i in range(index, self._number_of_rows): self.interior.tag_configure(list_of_items[i + 1], background=self._stripped_rows[i % 2]) def insert_row(self, data, index=None): if len(data) != self._number_of_columns: raise ValueError("The multicolumn listbox has only %d columns" % self._number_of_columns) if index is None: index = self._number_of_rows - 1 item_ID = self.interior.insert('', index, values=data) self.interior.item(item_ID, tags=item_ID) self._number_of_rows += 1 if self._stripped_rows: list_of_items = self.interior.get_children() self.interior.tag_configure(item_ID, background=self._stripped_rows[index % 2]) for i in range(index + 1, self._number_of_rows): self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i % 2]) def column_data(self, index): return [ self.interior.set(child_ID, index) for child_ID in self.interior.get_children('') ] def update_column(self, index, data): for i, item_ID in enumerate(self.interior.get_children()): data_row = self.item_ID_to_row_data(item_ID) data_row[index] = data[i] self.interior.item(item_ID, values=data_row) return data def clear(self): # Another possibility: # self.interior.delete(*self.interior.get_children()) for row in self.interior.get_children(): self.interior.delete(row) self._number_of_rows = 0 def update(self, data): self.clear() for row in data: self.insert_row(row) def focus(self, index=None): if index is None: return self.interior.item(self.interior.focus()) else: item = self.interior.get_children()[index] self.interior.focus(item) def state(self, state=None): if state is None: return self.interior.state() else: self.interior.state(state) @property def number_of_rows(self): return self._number_of_rows @property def number_of_columns(self): return self._number_of_columns def toogle_selection(self, index): list_of_items = self.interior.get_children() try: item_ID = list_of_items[index] except IndexError: raise ValueError("Row index out of range: %d" % index) self.interior.selection_toggle(item_ID) def select_row(self, index): list_of_items = self.interior.get_children() try: item_ID = list_of_items[index] except IndexError: raise ValueError("Row index out of range: %d" % index) self.interior.selection_add(item_ID) def deselect_row(self, index): list_of_items = self.interior.get_children() try: item_ID = list_of_items[index] except IndexError: raise ValueError("Row index out of range: %d" % index) self.interior.selection_remove(item_ID) def deselect_all(self): self.interior.selection_remove(self.interior.selection()) def set_selection(self, indices): list_of_items = self.interior.get_children() self.interior.selection_set(" ".join(list_of_items[row_index] for row_index in indices)) @property def selected_rows(self): data = [] for item_ID in self.interior.selection(): data_row = self.item_ID_to_row_data(item_ID) data.append(data_row) return data @property def indices_of_selected_rows(self): list_of_indices = [] for index, item_ID in enumerate(self.interior.get_children()): if item_ID in self.interior.selection(): list_of_indices.append(index) return list_of_indices def delete_all_selected_rows(self): selected_items = self.interior.selection() for item_ID in selected_items: self.interior.delete(item_ID) number_of_deleted_rows = len(selected_items) self._number_of_rows -= number_of_deleted_rows return number_of_deleted_rows def _on_select(self, event): for item_ID in event.widget.selection(): data_row = self.item_ID_to_row_data(item_ID) self._command(data_row) def item_ID_to_row_data(self, item_ID): item = self.interior.item(item_ID) return item["values"] @property def table_data(self): data = [] for item_ID in self.interior.get_children(): data_row = self.item_ID_to_row_data(item_ID) data.append(data_row) return data @table_data.setter def table_data(self, data): self.update(data) def cell_data(self, row, column): """Get the value of a table cell""" try: item = self.interior.get_children()[row] except IndexError: raise ValueError("Row index out of range: %d" % row) return self.interior.set(item, column) def update_cell(self, row, column, value): """Set the value of a table cell""" item_ID = self.interior.get_children()[row] data = self.item_ID_to_row_data(item_ID) data[column] = value self.interior.item(item_ID, values=data) def __getitem__(self, index): if isinstance(index, tuple): row, column = index return self.cell_data(row, column) else: raise Exception("Row and column indices are required") def __setitem__(self, index, value): if isinstance(index, tuple): row, column = index self.update_cell(row, column, value) else: raise Exception("Row and column indices are required") def bind(self, event, handler): self.interior.bind(event, handler) def sort_by(self, col, descending): """ sort tree contents when a column header is clicked """ # grab values to sort data = [(self.interior.set(child_ID, col), child_ID) for child_ID in self.interior.get_children('')] # if the data to be sorted is numeric change to float try: data = [(float(number), child_ID) for number, child_ID in data] except ValueError: pass # now sort the data in place data.sort(reverse=descending) for idx, item in enumerate(data): self.interior.move(item[1], '', idx) # switch the heading so that it will sort in the opposite direction self.interior.heading( col, command=lambda col=col: self.sort_by(col, not descending)) if self._stripped_rows: list_of_items = self.interior.get_children('') for i in range(len(list_of_items)): self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i % 2]) def destroy(self): self.interior.destroy() def item_ID(self, index): return self.interior.get_children()[index]
class Cerberus: def __init__(self, master, root): self.exportToCSV = False self.versionApp, self.key, self.salt = self.initApp() self.key = cerberusCryptography.getMasterKey() self.cipher_suite = Fernet(self.key) self.master = master self.master.title('Cerberus') self.windowWidth = 1060 self.windowHeight = 450 self.screenWidth = self.master.winfo_screenwidth() self.screenHeight = self.master.winfo_screenheight() self.positionRight = int(self.screenWidth / 2 - self.windowWidth / 2) self.positionDown = int(self.screenHeight / 3 - self.windowHeight / 2) self.master.geometry("{}x{}+{}+{}".format(self.windowWidth, self.windowHeight, self.positionRight, self.positionDown)) self.img = PhotoImage(data=icons.getAppIcon()) self.master.wm_iconphoto(True, self.img) self.master.resizable(0, 0) self.menubar = Menu(master) filemenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Cerberus", menu=filemenu) self.addIcon = PhotoImage(data=icons.getAddIcon()) filemenu.add_command(label="Εισαγωγή Υπηρεσίας", image=self.addIcon, compound='left', command=self.getAddNewServiceForm) self.editIcon = PhotoImage(data=icons.getEditIcon()) filemenu.add_command(label="Επεξεργασία Υπηρεσίας", image=self.editIcon, compound='left', command=self.getEditServiceForm) self.deleteIcon = PhotoImage(data=icons.getDeleteIcon()) filemenu.add_command(label="Διαγραφή Υπηρεσίας", image=self.deleteIcon, compound='left', command=self.deleteService) filemenu.add_separator() self.excelIcon = PhotoImage(data=icons.getExcelIcon()) filemenu.add_command(label="Εξαγωγή σε Excel", image=self.excelIcon, compound='left', command=self.checkPasswordToExportToCSV) filemenu.add_separator() self.exitIcon = PhotoImage(data=icons.getExitIcon()) filemenu.add_command(label="Έξοδος", image=self.exitIcon, compound='left', command=self.exitApp) settingsMenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Ρυθμίσεις", menu=settingsMenu) self.settingsIcon = PhotoImage(data=icons.getSettingsIcon()) settingsMenu.add_command(label="Επεξεργασία Στοιχείων", image=self.settingsIcon, compound='left') #command=self.getSettingsForm) aboutMenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Βοήθεια", menu=aboutMenu) self.infoIcon = PhotoImage(data=icons.getInfoIcon()) aboutMenu.add_command(label="Περί", image=self.infoIcon, compound='left', command=self.getAboutAppForm) self.master.config(menu=self.menubar) self.copyIcon = PhotoImage(data=icons.getCopyIcon()) self.popup = Menu(root, tearoff=0) self.popup.add_command(label=" Αντιγραφή Email", image=self.copyIcon, compound='left', command=self.copyEmail) self.popup.add_command(label=" Αντιγραφή Username", image=self.copyIcon, compound='left', command=self.copyUsername) self.popup.add_command(label=" Αντιγραφή Κωδικού", image=self.copyIcon, compound='left', command=self.copyPasswd) self.popup.add_command(label=" Αντιγραφή ID", image=self.copyIcon, compound='left', command=self.copyID) self.popup.add_separator() self.popup.add_command(label=" Επεξεργασία Υπηρεσίας", image=self.editIcon, compound='left', command=self.getEditServiceForm) self.popup.add_command(label=" Διαγραφή Υπηρεσίας", image=self.deleteIcon, compound='left', command=self.deleteService) self.popup.add_separator() self.popup.add_command(label=" Έξοδος", image=self.exitIcon, compound='left', command=self.exitApp) self.frame = Frame(self.master, background="white", borderwidth=1, relief="sunken", highlightthickness=1) self.frame.pack(side="top", fill="x", padx=4, pady=4) self.search = StringVar() self.searchEntry = Entry(self.frame, textvariable=self.search, borderwidth=0, highlightthickness=0, background="white") self.searchEntry.insert(0, 'Αναζήτηση Υπηρεσίας') self.searchEntry['fg'] = 'grey' self.search.trace( "w", lambda name, index, mode, sv=self.search: self.searchService()) self.searchEntry.image = PhotoImage(data=icons.getSearchIcon()) imageLabel = Label(self.frame, image=self.searchEntry.image) imageLabel.pack(side="left") imageLabel['bg'] = 'white' self.searchEntry.pack(side="left", fill="both", expand=True) # Fix BUG with Treeview colors in Python3.7 def fixed_map(option): return [ elm for elm in style.map('Treeview', query_opt=option) if elm[:2] != ('!disabled', '!selected') ] style = ttk.Style(root) style.map('Treeview', foreground=fixed_map('foreground'), background=fixed_map('background')) # Fix BUG with Treeview colors in Python3.7 self.table = Treeview(self.master) self.table['show'] = 'headings' self.table['columns'] = ('Services', 'email', 'username', 'passwd', 'id', 'category', 'url', 'ID') self.table["displaycolumns"] = ('Services', 'email', 'username', 'passwd', 'id', 'category', 'url') for col in self.table['columns']: self.table.heading( col, command=lambda c=col: self.sortby(self.table, c, 0)) self.table.heading('Services', text='Services') self.table.column('Services', anchor='center', width=200) self.table.heading('email', text='Email') self.table.column('email', anchor='center', width=200) self.table.heading('username', text='Username') self.table.column('username', anchor='center', width=100) self.table.heading('passwd', text='Password') self.table.column('passwd', anchor='center', width=100) self.table.heading('url', text='URL') self.table.column('url', anchor='center', width=120) self.table.heading('id', text='ID') self.table.column('id', anchor='center', width=100) self.table.heading('category', text='Category') self.table.column('category', anchor='center', width=100) self.table.heading('ID', text='ID') self.table.column('ID', anchor='center', width=200) self.table.tag_configure('oddrow', background='#e6eef2') self.table.tag_configure('evenrow', background='#b3cfdd') self.table.tag_configure('focus', background='#c6b6b4') self.last_focus = None self.last_focus_tag = None self.table.focus() self.table.pack(fill=BOTH, expand=1) self.table.bind("<<TreeviewSelect>>", self.onTableSelect) self.table.bind("<ButtonRelease-1>", self.openURLService) self.table.bind("<Motion>", self.changePointerOnHover) self.table.bind("<Button-3>", self.popupMenu) self.searchEntry.bind("<FocusIn>", self.foc_in) self.searchEntry.bind("<FocusOut>", self.foc_out) self.popup.bind("<FocusOut>", self.popupFocusOut) self.master.protocol("WM_DELETE_WINDOW", self.exitApp) self.loadTable(self) self.master.bind("<Escape>", self.exitApp) def popupFocusOut(self, event=None): self.popup.unpost() def foc_in(self, *args): if self.search.get() == 'Αναζήτηση Υπηρεσίας': self.searchEntry.delete('0', 'end') self.searchEntry['fg'] = 'black' def foc_out(self, *args): if not self.search.get(): self.searchEntry.insert(0, 'Αναζήτηση Υπηρεσίας') self.searchEntry['fg'] = 'grey' self.loadTable(self) def changePointerOnHover(self, event): _iid = self.table.identify_row(event.y) if _iid != self.last_focus: if self.last_focus: self.table.item(self.last_focus, tags=[self.last_focus_tag]) self.last_focus_tag = self.table.item(_iid, "tag") self.table.item(_iid, tags=['focus']) self.last_focus = _iid curItem = self.table.item(self.table.identify('item', event.x, event.y)) if curItem['values'] != '': col = self.table.identify_column(event.x) url = curItem['values'][int(col[-1]) - 1] if col[-1] == "7" and url != '---': self.master.config(cursor="hand2") else: self.master.config(cursor="") def openURLService(self, event): curItem = self.table.item(self.table.focus()) col = self.table.identify_column(event.x) region = self.table.identify("region", event.x, event.y) if col[-1] == "7" and region != 'heading': url = curItem['values'][int(col[-1]) - 1] if url != '---': webbrowser.open_new_tab('http://' + str(url)) def onTableSelect(self, event): for item in self.table.selection(): item_text = self.table.item(item, "values") print(item_text[0]) def getSelectedService(self, event): for item in self.table.selection(): selectedRow = self.table.item(item, "value") return selectedRow def initApp(self): print("Initialize Cerberus App") try: conn = sqlite3.connect('cerberus.db') except sqlite3.Error as e: print(e) cur = conn.cursor() cur.execute( "SELECT version, masterToken, salt FROM cerberusParameters") row = cur.fetchone() cur.close() return row def copyEmail(self): for item in self.table.selection(): item_text = self.table.item(item, "values") self.master.clipboard_clear() root.clipboard_append(item_text[1]) def copyUsername(self): for item in self.table.selection(): item_text = self.table.item(item, "values") self.master.clipboard_clear() root.clipboard_append(item_text[2]) def copyPasswd(self): for item in self.table.selection(): item_text = self.table.item(item, "values") self.master.clipboard_clear() root.clipboard_append(item_text[3]) def copyID(self): for item in self.table.selection(): item_text = self.table.item(item, "values") self.master.clipboard_clear() root.clipboard_append(item_text[4]) def searchService(self): try: conn = sqlite3.connect('cerberus.db') except sqlite3.Error as e: print(e) cur = conn.cursor() if self.search.get() == 'Αναζήτηση Υπηρεσίας': pass elif self.search.get(): cur.execute( "SELECT id, name, email, username, password, value, category, url FROM service WHERE name LIKE '%" + self.search.get() + "%' or name LIKE '%" + self.search.get().upper() + "%'") # ('%'+self.search.get()+'%',),'Α') elif not self.search.get(): cur.execute( "SELECT id, name, email, username, password, value, category, url FROM service " ) rows = cur.fetchall() cur.close() for k in self.table.get_children(): self.table.delete(k) i = 1 for row in rows: if (i % 2) == 0: tag = "oddrow" else: tag = "evenrow" self.table.insert( '', 'end', values=( row[1], self.cipher_suite.decrypt(row[2]).decode("utf-8").split(), self.cipher_suite.decrypt(row[3]).decode("utf-8").split(), self.cipher_suite.decrypt(row[4]).decode("utf-8").split(), self.cipher_suite.decrypt(row[5]).decode("utf-8").split(), self.cipher_suite.decrypt(row[6]).decode("utf-8").split(), self.cipher_suite.decrypt(row[7]).decode("utf-8").split(), row[0]), tags=tag) i = i + 1 @staticmethod def exitApp(event=None): root.destroy() @staticmethod def getAboutAppForm(): import aboutApp aboutApp.aboutApp() def getAddNewServiceForm(self): self.master.withdraw() import addNewServiceForm addNewServiceForm.addNewServiceForm(self) def getEditServiceForm(self): service = self.getSelectedService(self) if service is None: messagebox.showerror( "Μήνυμα Σφάλματος", "Παρακαλώ επιλέξτε την Υπηρεσία που θέλετε να Επεξεργαστείτε.") else: self.master.withdraw() import editServiceForm editServiceForm.editServiceForm(self, service) def getSettingsForm(self): import settingsForm settingsForm.settingsForm() def sortby(self, tree, col, descending): data = [(tree.set(child, col), child) for child in tree.get_children('')] data.sort(reverse=descending) for ix, item in enumerate(data): if (ix % 2) == 0: tag = "evenrow" else: tag = "oddrow" tree.move(item[1], '', ix) tree.item(item[1], tags=tag) # switch the heading so that it will sort in the opposite direction tree.heading( col, command=lambda x=col: self.sortby(tree, col, int(not descending))) @staticmethod def loadTable(self): try: conn = sqlite3.connect('cerberus.db') except sqlite3.Error as e: print(e) cur = conn.cursor() cur.execute( "SELECT id, name, email, username, password, value, category, url value FROM service" ) rows = cur.fetchall() for row in self.table.get_children(): self.table.delete(row) i = 1 for row in rows: if (i % 2) == 0: tag = "oddrow" else: tag = "evenrow" self.table.insert( '', 'end', values=( row[1], self.cipher_suite.decrypt(row[2]).decode("utf-8").split(), self.cipher_suite.decrypt(row[3]).decode("utf-8").split(), self.cipher_suite.decrypt(row[4]).decode("utf-8").split(), self.cipher_suite.decrypt(row[5]).decode("utf-8").split(), self.cipher_suite.decrypt(row[6]).decode("utf-8").split(), self.cipher_suite.decrypt(row[7]).decode("utf-8").split(), row[0]), tags=tag) i = i + 1 conn.close() self.last_focus = None self.table.selection() def deleteService(self): service = self.getSelectedService(self) if service is None: messagebox.showerror( "Μήνυμα Σφάλματος", "Παρακαλώ επιλέξτε την Υπηρεσία που θέλετε να Διαγράξετε.") else: msgBox = messagebox.askquestion( 'Διαγραφή: {}'.format(service[0]), 'Είστε σίγουρος ότι θέλετε να διαγράψετε την Υπηρεσία: ' '{}' ' ?'.format(service[0]), icon='warning') if msgBox == 'yes': try: conn = sqlite3.connect('cerberus.db') except sqlite3.Error as e: print(e) sql = 'DELETE FROM service WHERE id=?' cur = conn.cursor() cur.execute(sql, (service[-1], )) conn.commit() conn.close() self.loadTable(self) def popupMenu(self, event): serviceId = self.table.identify_row(event.y) if serviceId: self.table.selection_set(serviceId) try: self.popup.tk_popup(event.x_root, event.y_root) finally: self.popup.grab_release() def checkPasswordToExportToCSV(self): print("Check Password..") import logInForm self.master.withdraw() logInForm.logIn(self) @staticmethod def exportToCSV(): print("Export Services to CSV...") try: conn = sqlite3.connect('cerberus.db') except sqlite3.Error as e: print(e) key = cerberusCryptography.getMasterKey() cipher_suite = Fernet(key) cur = conn.cursor() cur.execute( "SELECT category, name, email, username, password, value, url value FROM service" ) rows = cur.fetchall() csvData = [[ 'Κατηγορία', 'Υπηρεσία', 'Email', 'Όνομα Χρήστη', 'Κωδικός', 'ID', 'URL', ]] for row in rows: csvData = csvData + [[ cipher_suite.decrypt(row[0]).decode("utf-8").split(), cipher_suite.decrypt(row[1]).decode("utf-8").split(), cipher_suite.decrypt(row[2]).decode("utf-8").split(), cipher_suite.decrypt(row[3]).decode("utf-8").split(), cipher_suite.decrypt(row[4]).decode("utf-8").split(), cipher_suite.decrypt(row[5]).decode("utf-8").split(), cipher_suite.decrypt(row[6]).decode("utf-8").split(), ]] try: homeFolder = str(Path.home()) filePath = filedialog.asksaveasfile( initialdir=homeFolder, initialfile='cerberus.csv', title="Επιλογή Αρχείου", filetypes=(("csv files", "*.csv"), ("all files", "*.*"))) if filePath: try: with open(filePath.name, 'w') as csvFile: csvFile = csv.writer(csvFile, delimiter='\t') csvFile.writerows(csvData) messagebox.showinfo( "Μήνυμα Επιτυχίας", "Το αρχείο αποθηκέυτηκε με Επιτυχία στην τοποθεσία {}." .format(filePath.name)) except Exception as e: messagebox.showerror( "Μήνυμα Σφάλματος", "Δεν ήταν δυνατή η Εξαγωγή του αρχείου.") except Exception as e: print(e) messagebox.showerror("Μήνυμα Σφάλματος", "Δεν ήταν δυνατή η Εξαγωγή του αρχείου.")
class FormChildClassification: def __init__(self, frm_parent, connection): self.connection = connection self.directive = Message() self.decide = True self.id_selected = 0 self.frm_child_list = LabelFrame(frm_parent) self.frm_child_crud = LabelFrame(frm_parent) self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.initialize_components() def initialize_components(self): """ Method that initialize the visual components for each form associated with the local administration """ # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.view_icon = PhotoImage(file=r"./Resources/view.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.back_icon = PhotoImage(file=r"./Resources/back.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") # Components for List FRM lbl_sep1 = Label(self.frm_child_list) lbl_sep1.grid(row=0, column=0, padx=10, pady=25) self.trv_available = Treeview(self.frm_child_list, height=15, columns=('N', 'Name', '# categories')) self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='N', anchor=CENTER) self.trv_available.heading('#2', text='Name', anchor=CENTER) self.trv_available.heading('#3', text='# categories', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available.column('#2', width=200, minwidth=200, stretch=NO) self.trv_available.column('#3', width=150, minwidth=150, stretch=NO) self.trv_available.grid(row=0, column=1, sticky=W, pady=25) vsb_trv_av = Scrollbar(self.frm_child_list, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) frm_aux4 = Frame(self.frm_child_list) btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new) btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E) btn_new_ttp = CreateToolTip(btn_new, 'New classification') btn_view = Button(frm_aux4, image=self.view_icon, command=self.click_view) btn_view.grid(row=1, column=0, pady=5, padx=5, sticky=E) btn_view_ttp = CreateToolTip(btn_view, 'View classification') btn_edit = Button(frm_aux4, image=self.modify_icon, command=self.click_update) btn_edit.grid(row=2, column=0, pady=5, padx=5, sticky=E) btn_edit_ttp = CreateToolTip(btn_edit, 'Edit classification') btn_delete = Button(frm_aux4, image=self.remove_icon, command=self.click_delete) btn_delete.grid(row=3, column=0, pady=5, padx=5, sticky=E) btn_delete_ttp = CreateToolTip(btn_delete, 'Delete classification') frm_aux4.grid(row=0, column=4, pady=25, padx=25, sticky=NW) # Components CRUD self.frm_class = LabelFrame(self.frm_child_crud) self.frm_class.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) lbl_class = Label(self.frm_child_crud, text='Name*') lbl_class.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_class.grid(row=0, column=0, pady=10, padx=20, sticky=W) lbl_desc_categories = Label( self.frm_child_crud, text='Enter categories separated by an Enter (\\n)') lbl_desc_categories.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_desc_categories.grid(row=1, column=0, pady=10, padx=20, columnspan=4, sticky=W) lbl_categories = Label(self.frm_child_crud, text='Categories*') lbl_categories.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_categories.grid(row=2, column=0, pady=10, padx=20, sticky=NW) lbl_sep2 = Label(self.frm_child_crud) lbl_sep2.grid(row=2, column=1, padx=20, pady=10) self.txt_name_class = Entry(self.frm_child_crud, width=50, font=TEXT_FONT) self.txt_name_class.grid(row=0, column=2, columnspan=2, pady=10, sticky=W) self.txt_categories = Text(self.frm_child_crud, height=10, width=50, font=TEXT_FONT) self.txt_categories.grid(row=2, column=2, pady=10, sticky=W) vsb_txt_cat = Scrollbar(self.frm_child_crud, orient="vertical", command=self.txt_categories.yview) vsb_txt_cat.grid(row=2, column=3, pady=10, sticky=NS) self.txt_categories.configure(yscrollcommand=vsb_txt_cat.set) sep_aux1 = Separator(self.frm_child_crud, orient=VERTICAL) sep_aux1.grid(row=0, column=4, sticky=NS, rowspan=3, padx=20) self.btn_save = Button(self.frm_child_crud, image=self.save_icon, command=self.click_save) btn_save_ttp = CreateToolTip(self.btn_save, 'Save classification') self.btn_back = Button(self.frm_child_crud, image=self.back_icon, command=self.click_back) btn_back_ttp = CreateToolTip(self.btn_back, 'Go back') self.btn_cancel = Button(self.frm_child_crud, image=self.cancel_icon, command=self.click_cancel) btn_cancel_ttp = CreateToolTip(self.btn_cancel, 'Cancel') def retrieve_list(self): # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) self.directive = Message(action=67) self.connection = self.directive.send_directive(self.connection) # Adding elements into the list for index, item in enumerate(self.connection.message.information): elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 200), summarize_text(elements[2], 150))) if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set( self.trv_available.get_children()[0]) def show_frm(self): self.retrieve_list() self.frm_child_list.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def hide_frm(self): self.clear_fields() self.frm_child_list.grid_forget() self.frm_child_crud.grid_forget() def click_new(self): self.classification = Classification() self.frm_child_crud['text'] = 'New Classification' self.txt_name_class.focus_set() self.btn_save.grid(row=0, column=5, padx=20) self.btn_cancel.grid(row=1, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def click_view(self): if len(self.trv_available.selection()) == 1: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) # Retrieve selected classification from the database self.directive = Message(action=70, information=[id_selected]) self.connection = self.directive.send_directive(self.connection) self.classification = Classification( id=id_selected, name=self.connection.message.information[0], categories=self.connection.message.information[1]) # Insert information into visual components self.txt_name_class.insert(0, self.classification.name) # Section to insert categories in textbox length_string = 0 for item in self.classification.categories: elements = item.split('¥') self.txt_categories.insert('end-1c', elements[1] + '\n') length_string += len(elements[1]) + 1 self.txt_categories.delete('end-2c', 'end') self.frm_child_crud['text'] = 'View classification' self.txt_name_class['bg'] = DISABLED_COLOR self.txt_categories['bg'] = DISABLED_COLOR self.txt_name_class['state'] = DISABLED self.txt_categories['state'] = DISABLED self.btn_back.grid(row=0, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_update(self): if len(self.trv_available.selection()) == 1: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) self.directive = Message(action=70, information=[id_selected, 'validate']) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: # An error ocurred while trying to update the item messagebox.showerror( parent=self.frm_child_list, title='Can not update the item', message=self.connection.message.information[0]) else: self.classification = Classification( id=id_selected, name=self.connection.message.information[0], categories=self.connection.message.information[1]) self.txt_name_class.insert(0, self.classification.name) # Section to insert categories in textbox length_string = 0 for item in self.classification.categories: elements = item.split('¥') self.txt_categories.insert('end-1c', elements[1] + '\n') length_string += len(elements[1]) + 1 self.txt_categories.delete('end-2c', 'end') self.frm_child_crud['text'] = 'Update classification' self.txt_name_class.focus_set() self.btn_save.grid(row=0, column=5, padx=20) self.btn_cancel.grid(row=1, column=5, padx=20) self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_delete(self): if len(self.trv_available.selection()) == 1: decision = messagebox.askyesno( parent=self.frm_child_list, title='Confirmation', message='Are you sure you want to delete the item?') if decision: id_selected = int( self.trv_available.item( self.trv_available.selection())['text']) self.directive = Message(action=69, information=[id_selected]) self.connection = self.directive.send_directive( self.connection) if self.connection.message.action == 5: # An error ocurred while deleting the item messagebox.showerror( parent=self.frm_child_list, title='Can not delete the item', message=self.connection.message.information[0]) else: self.retrieve_list() else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_save(self): if self.validate_fields(): self.classification.name = self.txt_name_class.get() if self.classification.id == 0: # Creating a new classification self.directive = Message( action=66, information=[self.classification.name]) self.connection = self.directive.send_directive( self.connection) self.classification.id = self.connection.message.information[0] self.classification.categories = self.txt_categories.get( '1.0', 'end-1c').split('\n') for item in self.classification.categories: if item: # None blank space will be saved self.directive = Message( action=71, information=[item, self.classification.id]) self.connection = self.directive.send_directive( self.connection) else: # Updating a classification self.directive = Message(action=68, information=[ self.classification.id, self.classification.name ]) self.connection = self.directive.send_directive( self.connection) # Delete categories associated with the current classification self.directive = Message(action=74, information=[self.classification.id]) self.connection = self.directive.send_directive( self.connection) # Create categories for the current classification self.classification.categories = self.txt_categories.get( '1.0', 'end-1c').split('\n') for item in self.classification.categories: if item: # None blank space will be saved self.directive = Message( action=71, information=[item, self.classification.id]) self.connection = self.directive.send_directive( self.connection) self.click_back() def click_back(self): self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def click_cancel(self): decision = True categories_aux = len( self.txt_categories.get('1.0', 'end-1c').split('\n')) categories_aux = categories_aux - 1 if self.classification.id == 0 else categories_aux if self.txt_name_class.get() != self.classification.name or \ categories_aux != len(self.classification.categories): decision = messagebox.askyesno( parent=self.frm_child_crud, title='Cancel', message='Are you sure you want to cancel?') if decision: self.click_back() def validate_fields(self): if len(self.txt_name_class.get()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert a name for the classification') return False if len(self.txt_categories.get('1.0', 'end-1c')) != 0: categories_aux = self.txt_categories.get('1.0', 'end-1c').split('\n') for item in categories_aux: for char in item: if char.isspace( ) or char == '\t' or not char or char == '\n': messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='A category can not be empty') return False return True else: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must insert at least one category') return False def clear_fields(self): self.btn_save.grid_forget() self.btn_cancel.grid_forget() self.btn_back.grid_forget() self.txt_name_class['state'] = NORMAL self.txt_categories['state'] = NORMAL self.txt_name_class['bg'] = ENABLED_COLOR self.txt_categories['bg'] = ENABLED_COLOR self.txt_name_class.delete(0, END) self.txt_categories.delete('1.0', 'end-1c')
class AutoCorrectConfig(Frame): """Configuration window for autocorrect.""" def __init__(self, master, app, **kwargs): Frame.__init__(self, master, padding=4, **kwargs) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.tree = Treeview(self, columns=('replace', 'by'), show='', selectmode='browse') scroll_x = AutoScrollbar(self, orient='horizontal', command=self.tree.xview) scroll_y = AutoScrollbar(self, orient='vertical', command=self.tree.yview) self.tree.configure(xscrollcommand=scroll_x.set, yscrollcommand=scroll_y.set) self.reset() self.replace = StringVar(self) self.by = StringVar(self) add_trace(self.replace, 'write', self._trace_replace) add_trace(self.by, 'write', self._trace_by) b_frame = Frame(self) self.b_add = Button(b_frame, text=_('New'), command=self.add) self.b_rem = Button(b_frame, text=_('Delete'), command=self.remove) self.b_add.state(('disabled', )) self.b_rem.state(('disabled', )) self.b_add.pack(pady=4, fill='x') self.b_rem.pack(pady=4, fill='x') Button(b_frame, text=_('Reset'), command=self.reset).pack(pady=8, fill='x') Label(self, text=_('Replace')).grid(row=0, column=0, sticky='w', pady=4) Label(self, text=_('By')).grid(row=0, column=1, sticky='w', pady=4) Entry(self, textvariable=self.replace).grid(row=1, column=0, sticky='ew', pady=4, padx=(0, 4)) Entry(self, textvariable=self.by).grid(row=1, column=1, sticky='ew', pady=4) self.tree.grid(row=2, columnspan=2, sticky='ewsn', pady=(4, 0)) scroll_x.grid(row=3, columnspan=2, sticky='ew', pady=(0, 4)) scroll_y.grid(row=2, column=2, sticky='ns', pady=(4, 0)) b_frame.grid(row=1, rowspan=2, padx=(4, 0), sticky='nw', column=3) self.tree.bind('<<TreeviewSelect>>', self._on_treeview_select) def _trace_by(self, *args): key = self.replace.get().strip() val = self.by.get().strip() self.by.set(val) if key in self.tree.get_children(''): if val != self.tree.set(key, 'by'): self.b_add.state(('!disabled', )) else: self.b_add.state(('disabled', )) else: self.b_add.state(('!disabled', )) if not val: self.b_add.state(('disabled', )) def _trace_replace(self, *args): key = self.replace.get().strip() val = self.by.get().strip() self.replace.set(key) if not key: self.b_add.state(('disabled', )) self.b_rem.state(('disabled', )) else: self.b_add.state(('!disabled', )) sel = self.tree.selection() if key in self.tree.get_children(''): if key not in sel: self.tree.selection_set(key) self.b_add.configure(text=_('Replace')) self.b_rem.state(('!disabled', )) if val != self.tree.set(key, 'by'): self.b_add.state(('!disabled', )) else: self.b_add.state(('disabled', )) else: self.b_rem.state(('disabled', )) self.b_add.configure(text=_('New')) if sel: self.tree.selection_remove(*sel) if not val: self.b_add.state(('disabled', )) def _on_treeview_select(self, event): sel = self.tree.selection() if sel: key, val = self.tree.item(sel[0], 'values') self.replace.set(key) self.by.set(val) def reset(self): self.tree.delete(*self.tree.get_children('')) keys = list(AUTOCORRECT.keys()) keys.sort() for key in keys: self.tree.insert('', 'end', key, values=(key, AUTOCORRECT[key])) def add(self): key = self.replace.get().strip() val = self.by.get().strip() if key in self.tree.get_children(''): self.tree.item(key, values=(key, val)) elif key and val: self.tree.insert('', 'end', key, values=(key, val)) def remove(self): key = self.replace.get() if key in self.tree.get_children(''): self.tree.delete(key) def ok(self): keys = self.tree.get_children('') AUTOCORRECT.clear() for key in keys: AUTOCORRECT[key] = self.tree.set(key, 'by')
class ConfigurationControl(object): ''' classdocs ''' __root_id: str = '' __observers: List[ConfigurationControlObserver] = [] __configuration: Treeview = None __inserted: List[str] = [] def __init__(self, master: Frame): ''' Constructor ''' log_enter_func('ConfigurationControl', '__init__', {'master': master}) self.__configuration = Treeview(master=master) self.__configuration.heading('#0', text='Configuration', anchor=tkinter.W) self.__configuration.pack(fill=BOTH, side=tkinter.LEFT, expand=True) self.__configuration.bind('<<TreeviewSelect>>', self.__notifiy_observer) log_leave_func('ConfigurationControl', '__init__') def select(self, _id: str): log_enter_func('ConfigurationControl', 'select', {'_id': _id}) if not _id == None: self.__configuration.focus(_id) self.__configuration.selection_set(_id) log_leave_func('ConfigurationControl', 'select') def insert_conf(self, conf_id: str, conf_name: str, parent: str = 'confs', pos: str = 'end'): log_enter_func( 'ConfigurationControl', 'insert_conf', { 'conf_id': conf_id, 'conf_name': conf_name, 'parent': parent, 'pos': pos }) if parent == None: parent = self.__root_id log_set_var('ConfigurationControl', 'insert_conf', 'parent', parent) self.__inserted.append(conf_id) self.__configuration.insert(parent, pos, conf_id, text=conf_name) self.__configuration.item(conf_id, open=True) log_leave_func('ConfigurationControl', 'insert_conf') def __remove(self, conf_id): log_enter_func('ConfigurationControl', '__remove', {'conf_id': conf_id}) self.__configuration.delete(conf_id) log_leave_func('ConfigurationControl', '__remove') def remove_all(self): log_enter_func('ConfigurationControl', 'remove_all') for conf_id in self.__inserted: if self.__configuration.exists(conf_id): self.__remove(conf_id) self.__inserted.clear() log_leave_func('ConfigurationControl', 'remove_all') def add_obeserver(self, observer: ConfigurationControlObserver): log_enter_func('ConfigurationControl', 'add_obeserver', {'observer': observer}) self.__observers.append(observer) log_leave_func('ConfigurationControl', 'add_obeserver') def remove_observer(self, observer: ConfigurationControlObserver): log_enter_func('ConfigurationControl', 'remove_observer', {'observer': observer}) self.__observers.remove(observer) log_leave_func('ConfigurationControl', 'remove_observer') def __notifiy_observer(self, event): # @UnusedVariable log_enter_func('ConfigurationControl', '__notifiy_observer', {'event': event}) conf_id = self.__configuration.focus() if conf_id == self.__root_id or conf_id == '': conf_id = None log_set_var('ConfigurationControl', '__notifiy_observer', 'conf_id', conf_id) for observer in self.__observers: observer.on_conf_selected(conf_id) log_leave_func('ConfigurationControl', '__notifiy_observer')
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 OverviewControl(object): ''' classdocs ''' __root_id: str = 'pages' __observers: List[OverviewControlObserver] = [] __overview: Treeview = None __inserted: List[str] = [] def __init__(self, master: tk.Frame): ''' Constructor ''' log_enter_func('OverviewControl', '__init__', {'master': master}) self.__overview = Treeview(master=master, selectmode='browse') self.__overview.heading('#0', text='Overview', anchor=tk.W) self.__overview.pack(fill=tk.Y, side=tk.LEFT) self.__overview.bind('<<TreeviewSelect>>', self.__notifiy_observer) self.__overview.insert('', 0, self.__root_id, text='Pages') self.__overview.item(self.__root_id, open=True) self.__overview.insert('', 1, 'css_rules', text='CSS Rules') self.__overview.item('css_rules', open=True) self.__overview.insert('', 2, 'javascripts', text='JavaScripts') self.__overview.item('javascripts', open=True) self.__overview.insert('', 3, 'text', text='Text') self.__overview.item('text', open=True) self.__overview.insert('', 4, 'variables', text='Variables') self.__overview.item('variables', open=True) log_leave_func('OverviewControl', '__init__') def select(self, page_id: str): log_enter_func('OverviewControl', 'select', {'page_id': page_id}) if not page_id == None: self.__overview.focus(page_id) self.__overview.selection_set(page_id) log_leave_func('OverviewControl', 'select') def insert(self, page_id: str, page_name: str): log_enter_func('OverviewControl', 'insert', { 'page_id': page_id, 'page_name': page_name }) self.__inserted.append(page_id) parent_id = page_id.split(sep='.')[0] self.__overview.insert(parent_id, 'end', page_id, text=page_name) log_leave_func('OverviewControl', 'insert') def remove_page(self, page_id: str): log_enter_func('OverviewControl', 'remove_page', {'page_id': page_id}) self.__remove(page_id) self.__inserted.remove(page_id) log_leave_func('OverviewControl', 'remove_page') def __remove(self, tag_id): log_enter_func('OverviewControl', '__remove', {'tag_id': tag_id}) self.__overview.delete(tag_id) log_leave_func('OverviewControl', '__remove') def remove_all(self): log_enter_func('OverviewControl', 'remove_all') for page_id in self.__inserted: self.__remove(page_id) self.__inserted.clear() log_leave_func('OverviewControl', 'remove_all') def add_obeserver(self, observer: OverviewControlObserver): log_enter_func('OverviewControl', 'add_obeserver', {'observer': observer}) self.__observers.append(observer) log_leave_func('OverviewControl', 'add_obeserver') def remove_observer(self, observer: OverviewControlObserver): log_enter_func('OverviewControl', 'remove_observer', {'observer': observer}) self.__observers.remove(observer) log_leave_func('OverviewControl', 'remove_observer') def __notifiy_observer(self, event): # @UnusedVariable log_enter_func('OverviewControl', '__notifiy_observer', {'event': event}) page_id = event.widget.selection()[0] if '.' not in page_id: page_id = None log_set_var('OverviewControl', '__notifiy_observer', 'page_id', page_id) for observer in self.__observers: observer.on_page_selected(page_id) log_leave_func('OverviewControl', '__notifiy_observer')