def _find_item(menu: tkinter.Menu, label: str) -> Optional[int]: last_index = menu.index('end') if last_index is not None: # menu not empty for index in range(last_index + 1): if menu.type( index) in _MENU_ITEM_TYPES_WITH_LABEL and menu.entrycget( index, 'label') == label: return index return None
def _find_item(menu: tkinter.Menu, label: str) -> Optional[int]: last_index = menu.index("end") # type: ignore[no-untyped-call] if last_index is not None: # menu not empty for index in range(last_index + 1): if (menu.type(index) in _MENU_ITEM_TYPES_WITH_LABEL # type: ignore[no-untyped-call] and menu.entrycget(index, "label") == label # type: ignore[no-untyped-call] ): return index return None
def configure_menu(self): menu_bar = Menu(self) self.configure(menu=menu_bar) # controls command menu_bar.add_command(label='controls', command=self.popup_controls) # speed menu speed_menu = Menu(menu_bar) menu_bar.add_cascade(label='speed', menu=speed_menu) self.speed_index_int_var = IntVar() for speed_string, scalar in data.FREQ_SCALAR_KEYS.items(): speed_menu.add_radiobutton(label=speed_string, value=scalar, variable=self.speed_index_int_var) speed_menu.invoke(speed_menu.index('1.00x')) # shapes menu shapes_menu = Menu(menu_bar) menu_bar.add_cascade(label='shapes', menu=shapes_menu) self.shapes_string_var = StringVar() self.shapes_string_var.set('default') for shape_set in data.SHAPES[self.shape_size].keys(): shapes_menu.add_radiobutton(label=shape_set, value=shape_set, variable=self.shapes_string_var) shapes_menu.invoke(shapes_menu.index('default')) # color menu colors_menu = Menu(menu_bar) menu_bar.add_cascade(label='colors', menu=colors_menu) self.cs_string_var = StringVar() for scheme in data.COLOR_SCHEMES[self.shape_size].keys(): colors_menu.add_radiobutton(label=scheme, value=scheme, variable=self.cs_string_var) colors_menu.invoke(colors_menu.index('default'))
def accounts_postcommand(menu: tk.Menu): """Update the accounts menu whenever the user clicks on the menu bar. Args: menu: The accounts menu. This will delete every menu item from the second item to the end. Each account will be added showing a combination of institution and account name. Menu postcommands are subordinate to the main menu in the module hierarchy so are best placed in the main menu module. """ start_item_ix = 2 menu.delete(start_item_ix, menu.index(tk.END)) inst_name_accs = sorted([("{} {}…".format(acc.inst, acc.name), acc) for acc in config.data.accs]) for inst_name, acc in inst_name_accs: menu.add_command(label=inst_name, command=lambda acc=acc: accountsmenu.account_edit(acc), state=tk.ACTIVE)
def __init__( self, f: tk.Frame, menu: tk.Menu, *, cmd_clear: Callable[[], None], cmd_shuffle: Callable[[], None], get_items: Callable[[], list[tuple[str, int]]], set_items: Callable[[Palette], None], ) -> None: """Initialises the palette pane. The parameters are used to communicate with the item list: - cmd_clear and cmd_shuffle are called to do those actions to the list. - pal_get_items is called to retrieve the current list of selected items. - cmd_save_btn_state is the .state() method on the save button. - cmd_set_items is called to apply a palette to the list of items. """ self.palettes: dict[UUID, Palette] = { pal.uuid: pal for pal in paletteLoader.load_palettes() } try: self.selected_uuid = UUID(hex=BEE2_config.GEN_OPTS.get_val( 'Last_Selected', 'palette_uuid', '')) except ValueError: self.selected_uuid = UUID_PORTAL2 f.rowconfigure(1, weight=1) f.columnconfigure(0, weight=1) self.var_save_settings = tk.BooleanVar( value=BEE2_config.GEN_OPTS.get_bool('General', 'palette_save_settings')) self.var_pal_select = tk.StringVar(value=self.selected_uuid.hex) self.get_items = get_items self.set_items = set_items # Overwritten to configure the save state button. self.save_btn_state = lambda s: None ttk.Button( f, text=gettext('Clear Palette'), command=cmd_clear, ).grid(row=0, sticky="EW") self.ui_treeview = treeview = ttk.Treeview(f, show='tree', selectmode='browse') self.ui_treeview.grid(row=1, sticky="NSEW") self.ui_treeview.tag_bind(TREE_TAG_PALETTES, '<ButtonPress>', self.event_select_tree) # Avoid re-registering the double-lambda, just do it here. # This makes clicking the groups return selection to the palette. evtid_reselect = self.ui_treeview.register(self.treeview_reselect) self.ui_treeview.tag_bind( TREE_TAG_GROUPS, '<ButtonPress>', lambda e: treeview.tk.call('after', 'idle', evtid_reselect)) # And ensure when focus returns we reselect, in case it deselects. f.winfo_toplevel().bind('<FocusIn>', lambda e: self.treeview_reselect(), add=True) scrollbar = tk_tools.HidingScroll( f, orient='vertical', command=self.ui_treeview.yview, ) scrollbar.grid(row=1, column=1, sticky="NS") self.ui_treeview['yscrollcommand'] = scrollbar.set self.ui_remove = ttk.Button( f, text=gettext('Delete Palette'), command=self.event_remove, ) self.ui_remove.grid(row=2, sticky="EW") if tk_tools.USE_SIZEGRIP: ttk.Sizegrip(f).grid(row=2, column=1) self.ui_menu = menu self.ui_group_menus: dict[str, tk.Menu] = {} self.ui_group_treeids: dict[str, str] = {} menu.add_command( label=gettext('Clear'), command=cmd_clear, ) menu.add_command( # Placeholder.. label=gettext('Delete Palette'), # This name is overwritten later command=self.event_remove, ) self.ui_menu_delete_index = menu.index('end') menu.add_command( label=gettext('Change Palette Group...'), command=self.event_change_group, ) self.ui_menu_regroup_index = menu.index('end') menu.add_command( label=gettext('Rename Palette...'), command=self.event_rename, ) self.ui_menu_rename_index = menu.index('end') menu.add_command( label=gettext('Fill Palette'), command=cmd_shuffle, ) menu.add_separator() menu.add_checkbutton( label=gettext('Save Settings in Palettes'), variable=self.var_save_settings, ) menu.add_separator() menu.add_command( label=gettext('Save Palette'), command=self.event_save, accelerator=tk_tools.ACCEL_SAVE, ) self.ui_menu_save_ind = menu.index('end') menu.add_command( label=gettext('Save Palette As...'), command=self.event_save_as, accelerator=tk_tools.ACCEL_SAVE_AS, ) menu.add_separator() self.ui_menu_palettes_index = menu.index('end') + 1 self.update_state()
class RightClick(): def __init__(self, parent, context): self.parent = parent # create a popup menu self.popup_menu = Menu(self.parent, tearoff=0) self.prev_selection = () self.curr_selection = () self.progress = StringVar() self.progress.set('None') self.context = context self.cached_selection = None #region public methods def popup(self, event): self._add_options() self.popup_menu.post(event.x_root, event.y_root) def set_current_selected(self): # keep track of the files selected each time a right-click occurs if self.prev_selection != self.curr_selection: # assign the previous set of selected files if they have changed self.prev_selection = self.curr_selection # now get the current set of selected files self.curr_selection = self.parent.file_treeview.selection() def undraw(self, event): self.popup_menu.unpost() #region private methods def _add_options(self): # a context dependent function to only add options that are applicable # to the current situation. # first, remove any currently visible entries in the menu so we can # draw only the ones required by the current context self.popup_menu.delete(0, self.popup_menu.index("end")) # now, draw the manu elements required depending on context if self.parent.treeview_select_mode == "NORMAL": # add an option to associate the file as extra data to be included # in the bids output self.popup_menu.add_command(label="Add to BIDS output", command=self._add_to_bids) if ('.CON' in self.context or '.MRK' in self.context): self.popup_menu.add_command(label="Associate", command=self._associate_mrk) else: pass elif self.parent.treeview_select_mode.startswith("ASSOCIATE"): if (('.MRK' in self.context and self.parent.treeview_select_mode == 'ASSOCIATE-MRK') or ('.CON' in self.context and self.parent.treeview_select_mode == 'ASSOCIATE-CON')): self.popup_menu.add_command(label="Associate", command=self._associate_mrk) # give the option to associate one or more mrk files with all con files if (".MRK" in self.context and not self.parent.treeview_select_mode.startswith("ASSOCIATE")): self.popup_menu.add_command( label="Associate with all", command=lambda: self._associate_mrk(all_=True)) # Add an option mark all selected .con files as junk if ".CON" in self.context and not self.context.is_mixed: self.popup_menu.add_command(label="Ignore file(s)", command=self._ignore_cons) self.popup_menu.add_command(label="Include file(s)", command=self._include_cons) if len(self.curr_selection) == 1: fname = self.parent.file_treeview.get_text(self.curr_selection[0]) fpath = self.parent.file_treeview.get_filepath( self.curr_selection[0]) # the selected object selected_obj = self.parent.preloaded_data[self.curr_selection[0]] # if the folder is a BIDS folder allow it to be uploaded to the # archive if BIDS_PATTERN.match(fname): self.popup_menu.add_command( label="Upload to archive", command=lambda: self._upload()) # allow any folder to be sent to another location using the # BIDSMERGE functionality if isinstance(selected_obj, (BIDSTree, Project, Subject, Session)): self.popup_menu.add_command( label="Send to...", command=lambda: self._send_to()) if path.isdir(fpath): if isinstance(self.parent.preloaded_data.get( self.curr_selection[0], None), Folder): self.popup_menu.add_command( label="Assign as BIDS folder", command=self._toggle_bids_folder) def _add_to_bids(self): """Add the selected file(s) to the list of files to be retained across BIDS conversion. """ sids = self.curr_selection parent_obj = None for sid in sids: parent = self.parent.file_treeview.parent(sid) fpath = self.parent.file_treeview.get_filepath(sid) parent_obj = self.parent.preloaded_data.get(parent, None) if isinstance(parent_obj, BIDSContainer): parent_obj.extra_files.append(fpath) def _associate_mrk(self, all_=False): """ Allow the user to select an .mrk file if a .con file has been selected (or vice-versa) and associate the mrk file with the con file. """ # First do a simple check for a mixed selection. if self.context.is_mixed: cont = messagebox.askretrycancel( "Error", ("You have not selected only .mrk or only .con files. Please " "select a single file type at a time or press 'cancel' to " "stop associating.")) if not cont: self.parent.treeview_select_mode = "NORMAL" self.curr_selection = self.prev_selection return mrk_files = None con_files = None issue = False cont = True # Set the current selection to the appropriate type if '.MRK' in self.context: mrk_files = [self.parent.preloaded_data[sid] for sid in self.curr_selection] elif '.CON' in self.context: con_files = [self.parent.preloaded_data[sid] for sid in self.curr_selection] else: # TODO: improve message/dialog messagebox.showerror("Error", "Invalid file selection") self.curr_selection = self.prev_selection return # Associate selected mrk files with all other con files in folder. if all_: # This can only be called if we have mrk files selected. # Get the parent folder and then find all .con file children. parent = self.parent.file_treeview.parent(mrk_files[0].ID) container = self.parent.preloaded_data[parent] for con in container.contained_files['.con']: con.hpi = mrk_files con.validate() return if self.parent.treeview_select_mode == "NORMAL": # Change the treeview mode and display a message if needed. if '.MRK' in self.context: # Make sure the markers are in the same folder and there aren't # too many. issue, cont = validate_markers(self.parent.file_treeview, mrk_files) if not issue: self.parent.set_treeview_mode("ASSOCIATE-CON") self.cached_selection = mrk_files elif '.CON' in self.context: self.parent.set_treeview_mode("ASSOCIATE-MRK") self.cached_selection = con_files # Finish up if there is no issue. If there is we want to handle it # correctly later. if not issue: if self.parent.settings.get('SHOW_ASSOC_MESSAGE', True): msg = ("Please select the {0} file(s) associated with " "this file.\nOnce you have selected all required " "files, right click and press 'associate' again") # getting the context from the variable is a bit hacky... ctx = list(self.context.get())[0].lower() new_ctx = {'.con': '.mrk', '.mrk': '.con'}.get(ctx) messagebox.showinfo("Select", msg.format(new_ctx)) return # make sure the marker files and con files are in the same location elif self.parent.treeview_select_mode == "ASSOCIATE-MRK": con_files = self.cached_selection issue, cont = validate_markers(self.parent.file_treeview, mrk_files, con_files) elif self.parent.treeview_select_mode == "ASSOCIATE-CON": mrk_files = self.cached_selection issue, cont = validate_markers(self.parent.file_treeview, mrk_files, con_files) # Handle any issues/check for continuation. if issue: if not cont: self.parent.set_treeview_mode("NORMAL") self.curr_selection = self.prev_selection return if self.parent.treeview_select_mode.startswith('ASSOCIATE'): # Now associate the mrk files with the con files: for cf in con_files: cf.hpi = mrk_files cf.validate() # Check if the con file is the currently selected file. if self.parent.treeview_select_mode == "ASSOCIATE-CON": # if so, redraw the info panel and call the mrk # association function so GUI is updated self.parent.info_notebook.determine_tabs() self.parent.highlight_associated_mrks(None) self.parent.set_treeview_mode("NORMAL") self.cached_selection = None def _create_folder(self): """ Create a folder at the currently open level. Clicking on a folder and selecting "create folder" will create a sibling folder, not child folder (not sure which to do?) """ # get the current root depth if self.context != set(): # maybe?? dir_ = path.dirname( self.parent.file_treeview.get_filepath(self.curr_selection[0])) else: dir_ = self.parent.settings['DATA_PATH'] # ask the user for the folder name: folder_name = simpledialog.askstring("Folder Name", "Enter a folder Name:", parent=self.parent) # we will need to make sure the folder doesn't already exist at the # selected level if folder_name is not None: # create the folder full_path = path.join(dir_, folder_name) _, exists_already = create_folder(full_path) if not exists_already: try: parent = self.parent.file_treeview.parent( self.parent.selected_files[0]) except IndexError: # we have clicked outside the tree. Set the parent as the # root parent = '' self.parent.file_treeview.ordered_insert( parent, values=['', str(full_path)], text=folder_name, open=False) print('folder created!!') else: print('Folder already exists!') def _ignore_cons(self): """ Set all selected con files to have 'Is Junk' as True """ for sid in self.curr_selection: con = self.parent.preloaded_data.get(sid, None) if con is not None: if isinstance(con, con_file): con.is_junk.set(True) con.validate() def _include_cons(self): """ Set all selected con files to have 'Is Junk' as False """ for sid in self.curr_selection: con = self.parent.preloaded_data.get(sid, None) if con is not None: if isinstance(con, con_file): con.is_junk.set(False) con.validate() def _send_to(self): """Send the selected object to another selected location.""" src_obj = self.parent.preloaded_data[self.curr_selection[0]] dst = filedialog.askdirectory(title="Select BIDS folder") if dst != '': if isinstance(src_obj, (BIDSTree, Project, Subject, Session)): SendFilesWindow(self.parent, src_obj, dst, opt_verify=True) else: # try and convert the object to a BIDSTree try: self._toggle_bids_folder() src_obj = self.parent.preloaded_data[ self.curr_selection[0]] except TypeError: return def _toggle_bids_folder(self): """Assign the selected folder as a BIDS-formatted folder. This will attempt to load the selected folder into a BIDSController.BIDSTree object. If this isn't possible an error will be raised stating this. """ sid = self.curr_selection[0] fpath = self.parent.file_treeview.get_filepath(sid) bids_folder = assign_bids_folder(fpath, self.parent.file_treeview, self.parent.preloaded_data) if bids_folder is not None: # Only needed if the current selection is the same thing that has # been right-clicked. self.parent.info_notebook.data = [bids_folder] else: messagebox.showerror( "Error", "Invalid folder selected. Please select a folder which " "contains the BIDS project folders.") raise TypeError def _upload(self): """Upload the selected object to the MEG_RAW archive.""" src_obj = self.parent.preloaded_data[self.curr_selection[0]] dst = self.parent.settings.get("ARCHIVE_PATH", None) if dst is None: messagebox.showerror("No path set!", "No Archive path has been set. Please set " "one in the settings.") return if not isinstance(src_obj, BIDSTree): # automatically convert to a BIDSTree object try: self._toggle_bids_folder() src_obj = self.parent.preloaded_data[self.curr_selection[0]] except TypeError: return access = authorise(dst) if not access: messagebox.showerror("Error", "Invalid username or password. Please try " "again.") return SendFilesWindow(self.parent, src_obj, dst, set_copied=True)
class App(Tk): """ Main app. Put an icon in the system tray with a right click menu to create notes. """ def __init__(self): Tk.__init__(self) self.withdraw() self.notes = {} self.img = PhotoImage(file=cst.IM_ICON) self.icon = PhotoImage(master=self, file=cst.IM_ICON_48) self.iconphoto(True, self.icon) self.ewmh = ewmh.EWMH() style = Style(self) style.theme_use("clam") self.close1 = PhotoImage("img_close", file=cst.IM_CLOSE) self.close2 = PhotoImage("img_closeactive", file=cst.IM_CLOSE_ACTIVE) self.roll1 = PhotoImage("img_roll", file=cst.IM_ROLL) self.roll2 = PhotoImage("img_rollactive", file=cst.IM_ROLL_ACTIVE) self.protocol("WM_DELETE_WINDOW", self.quit) self.icon = tktray.Icon(self, docked=True) # --- Menu self.menu_notes = Menu(self.icon.menu, tearoff=False) self.hidden_notes = {cat: {} for cat in CONFIG.options("Categories")} self.menu_show_cat = Menu(self.icon.menu, tearoff=False) self.menu_hide_cat = Menu(self.icon.menu, tearoff=False) self.icon.configure(image=self.img) self.icon.menu.add_command(label=_("New Note"), command=self.new) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Show All'), command=self.show_all) self.icon.menu.add_cascade(label=_('Show Category'), menu=self.menu_show_cat) self.icon.menu.add_cascade(label=_('Show Note'), menu=self.menu_notes, state="disabled") self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Hide All'), command=self.hide_all) self.icon.menu.add_cascade(label=_('Hide Category'), menu=self.menu_hide_cat) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Preferences"), command=self.config) self.icon.menu.add_command(label=_("Note Manager"), command=self.manage) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Backup Notes"), command=self.backup) self.icon.menu.add_command(label=_("Restore Backup"), command=self.restore) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Export"), command=self.export_notes) self.icon.menu.add_command(label=_("Import"), command=self.import_notes) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Check for Updates'), command=lambda: UpdateChecker(self)) self.icon.menu.add_command(label=_('About'), command=lambda: About(self)) self.icon.menu.add_command(label=_('Quit'), command=self.quit) # --- Restore notes self.note_data = {} if os.path.exists(PATH_DATA): with open(PATH_DATA, "rb") as fich: dp = pickle.Unpickler(fich) note_data = dp.load() for i, key in enumerate(note_data): self.note_data["%i" % i] = note_data[key] backup() for key in self.note_data: data = self.note_data[key] cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) if data["visible"]: self.notes[key] = Sticky(self, key, **data) else: self.add_note_to_menu(key, data["title"], cat) self.nb = len(self.note_data) self.update_menu() self.update_notes() self.make_notes_sticky() # --- class bindings # newline depending on mode self.bind_class("Text", "<Return>", self.insert_newline) # char deletion taking into account list type self.bind_class("Text", "<BackSpace>", self.delete_char) # change Ctrl+A to select all instead of go to the beginning of the line self.bind_class('Text', '<Control-a>', self.select_all_text) self.bind_class('TEntry', '<Control-a>', self.select_all_entry) # bind Ctrl+Y to redo self.bind_class('Text', '<Control-y>', self.redo_event) # unbind Ctrl+I and Ctrl+B self.bind_class('Text', '<Control-i>', lambda e: None) self.bind_class('Text', '<Control-b>', lambda e: None) # highlight checkboxes when inside text selection self.bind_class("Text", "<ButtonPress-1>", self.highlight_checkboxes, True) self.bind_class("Text", "<ButtonRelease-1>", self.highlight_checkboxes, True) self.bind_class("Text", "<B1-Motion>", self.highlight_checkboxes, True) evs = [ '<<SelectAll>>', '<<SelectLineEnd>>', '<<SelectLineStart>>', '<<SelectNextChar>>', '<<SelectNextLine>>', '<<SelectNextPara>>', '<<SelectNextWord>>', '<<SelectNone>>', '<<SelectPrevChar>>', '<<SelectPrevLine>>', '<<SelectPrevPara>>', '<<SelectPrevWord>>' ] for ev in evs: self.bind_class("Text", ev, self.highlight_checkboxes, True) # check for updates if CONFIG.getboolean("General", "check_update"): UpdateChecker(self) # --- class bindings methods def highlight_checkboxes(self, event): txt = event.widget try: deb = cst.sorting(txt.index("sel.first")) fin = cst.sorting(txt.index("sel.last")) for ch in txt.children.values(): try: i = cst.sorting(txt.index(ch)) if i >= deb and i <= fin: ch.configure(style="sel.TCheckbutton") else: ch.configure(style=txt.master.id + ".TCheckbutton") except TclError: pass except TclError: for ch in txt.children.values(): try: i = cst.sorting(txt.index(ch)) ch.configure(style=txt.master.id + ".TCheckbutton") except TclError: pass def redo_event(self, event): try: event.widget.edit_redo() except TclError: # nothing to redo pass def select_all_entry(self, event): event.widget.selection_range(0, "end") def select_all_text(self, event): event.widget.tag_add("sel", "1.0", "end-1c") self.highlight_checkboxes(event) def delete_char(self, event): txt = event.widget deb_line = txt.get("insert linestart", "insert") tags = txt.tag_names("insert") if txt.tag_ranges("sel"): if txt.tag_nextrange("enum", "sel.first", "sel.last"): update = True else: update = False txt.delete("sel.first", "sel.last") if update: txt.master.update_enum() elif txt.index("insert") != "1.0": if re.match('^\t[0-9]+\.\t$', deb_line) and 'enum' in tags: txt.delete("insert linestart", "insert") txt.insert("insert", "\t\t") txt.master.update_enum() elif deb_line == "\t•\t" and 'list' in tags: txt.delete("insert linestart", "insert") txt.insert("insert", "\t\t") elif deb_line == "\t\t": txt.delete("insert linestart", "insert") elif "todolist" in tags and txt.index("insert") == txt.index( "insert linestart+1c"): try: ch = txt.window_cget("insert-1c", "window") txt.delete("insert-1c") txt.children[ch.split('.')[-1]].destroy() txt.insert("insert", "\t\t") except TclError: txt.delete("insert-1c") else: txt.delete("insert-1c") def insert_newline(self, event): mode = event.widget.master.mode.get() if mode == "list": event.widget.insert("insert", "\n\t•\t") event.widget.tag_add("list", "1.0", "end") elif mode == "todolist": event.widget.insert("insert", "\n") ch = Checkbutton(event.widget, takefocus=False, style=event.widget.master.id + ".TCheckbutton") event.widget.window_create("insert", window=ch) event.widget.tag_add("todolist", "1.0", "end") elif mode == "enum": event.widget.configure(autoseparators=False) event.widget.edit_separator() event.widget.insert("insert", "\n\t0.\t") event.widget.master.update_enum() event.widget.edit_separator() event.widget.configure(autoseparators=True) else: event.widget.insert("insert", "\n") def make_notes_sticky(self): for w in self.ewmh.getClientList(): if w.get_wm_name()[:7] == 'mynotes': self.ewmh.setWmState(w, 1, '_NET_WM_STATE_STICKY') self.ewmh.display.flush() def add_note_to_menu(self, nb, note_title, category): """add note to 'show notes' menu. """ try: name = self.menu_notes.entrycget(category.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] end = menu.index("end") if end is not None: # le menu n'est pas vide titles = self.hidden_notes[category].values() titles = [t for t in titles if t.split(" ~#")[0] == note_title] if titles: title = "%s ~#%i" % (note_title, len(titles) + 1) else: title = note_title else: title = note_title except TclError: # cat is not in the menu menu = Menu(self.menu_notes, tearoff=False) self.menu_notes.add_cascade(label=category.capitalize(), menu=menu) title = note_title menu.add_command(label=title, command=lambda: self.show_note(nb)) self.icon.menu.entryconfigure(4, state="normal") self.hidden_notes[category][nb] = title def backup(self): """Create a backup at the location indicated by user.""" initialdir, initialfile = os.path.split(PATH_DATA_BACKUP % 0) fichier = asksaveasfilename(defaultextension=".backup", filetypes=[], initialdir=initialdir, initialfile="notes.backup0", title=_('Backup Notes')) if fichier: try: with open(fichier, "wb") as fich: dp = pickle.Pickler(fich) dp.dump(self.note_data) except Exception as e: report_msg = e.strerror != 'Permission denied' showerror(_("Error"), _("Backup failed."), traceback.format_exc(), report_msg) def restore(self, fichier=None, confirmation=True): """Restore notes from backup.""" if confirmation: rep = askokcancel( _("Warning"), _("Restoring a backup will erase the current notes."), icon="warning") else: rep = True if rep: if fichier is None: fichier = askopenfilename(defaultextension=".backup", filetypes=[], initialdir=LOCAL_PATH, initialfile="", title=_('Restore Backup')) if fichier: try: keys = list(self.note_data.keys()) for key in keys: self.delete_note(key) if not os.path.samefile(fichier, PATH_DATA): copy(fichier, PATH_DATA) with open(PATH_DATA, "rb") as myfich: dp = pickle.Unpickler(myfich) note_data = dp.load() for i, key in enumerate(note_data): data = note_data[key] note_id = "%i" % i self.note_data[note_id] = data cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) if data["visible"]: self.notes[note_id] = Sticky(self, note_id, **data) self.nb = len(self.note_data) self.update_menu() self.update_notes() except FileNotFoundError: showerror( _("Error"), _("The file {filename} does not exists.").format( filename=fichier)) except Exception as e: showerror(_("Error"), str(e), traceback.format_exc(), True) def show_all(self): """Show all notes.""" for cat in self.hidden_notes.keys(): keys = list(self.hidden_notes[cat].keys()) for key in keys: self.show_note(key) def show_cat(self, category): """Show all notes belonging to category.""" keys = list(self.hidden_notes[category].keys()) for key in keys: self.show_note(key) def hide_all(self): """Hide all notes.""" keys = list(self.notes.keys()) for key in keys: self.notes[key].hide() def hide_cat(self, category): """Hide all notes belonging to category.""" keys = list(self.notes.keys()) for key in keys: if self.note_data[key]["category"] == category: self.notes[key].hide() def manage(self): """Launch note manager.""" Manager(self) def config(self): """Launch the setting manager.""" conf = Config(self) self.wait_window(conf) col_changes, name_changes = conf.get_changes() if col_changes or name_changes: self.update_notes(col_changes, name_changes) self.update_menu() alpha = CONFIG.getint("General", "opacity") / 100 for note in self.notes.values(): note.attributes("-alpha", alpha) note.update_title_font() note.update_text_font() note.update_titlebar() def delete_cat(self, category): """Delete all notes belonging to category.""" keys = list(self.notes.keys()) for key in keys: if self.note_data[key]["category"] == category: self.notes[key].delete(confirmation=False) def delete_note(self, nb): if self.note_data[nb]["visible"]: self.notes[nb].delete(confirmation=False) else: cat = self.note_data[nb]["category"] name = self.menu_notes.entrycget(cat.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] index = menu.index(self.hidden_notes[cat][nb]) menu.delete(index) if menu.index("end") is None: # the menu is empty self.menu_notes.delete(cat.capitalize()) if self.menu_notes.index('end') is None: self.icon.menu.entryconfigure(4, state="disabled") del (self.hidden_notes[cat][nb]) del (self.note_data[nb]) self.save() def show_note(self, nb): """Display the note corresponding to the 'nb' key in self.note_data.""" self.note_data[nb]["visible"] = True cat = self.note_data[nb]["category"] name = self.menu_notes.entrycget(cat.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] index = menu.index(self.hidden_notes[cat][nb]) del (self.hidden_notes[cat][nb]) self.notes[nb] = Sticky(self, nb, **self.note_data[nb]) menu.delete(index) if menu.index("end") is None: # the menu is empty self.menu_notes.delete(cat.capitalize()) if self.menu_notes.index('end') is None: self.icon.menu.entryconfigure(4, state="disabled") self.make_notes_sticky() def update_notes(self, col_changes={}, name_changes={}): """Update the notes after changes in the categories.""" categories = CONFIG.options("Categories") categories.sort() self.menu_notes.delete(0, "end") self.hidden_notes = {cat: {} for cat in categories} for key in self.note_data: cat = self.note_data[key]["category"] if cat in name_changes: cat = name_changes[cat] self.note_data[key]["category"] = cat if self.note_data[key]["visible"]: self.notes[key].change_category(cat) elif cat not in categories: default = CONFIG.get("General", "default_category") default_color = CONFIG.get("Categories", default) if self.note_data[key]["visible"]: self.notes[key].change_category(default) self.note_data[key]["category"] = default self.note_data[key]["color"] = default_color cat = default if cat in col_changes: old_color, new_color = col_changes[cat] if self.note_data[key]["color"] == old_color: self.note_data[key]["color"] = new_color if self.note_data[key]["visible"]: self.notes[key].change_color(cst.INV_COLORS[new_color]) if not self.note_data[key]['visible']: self.add_note_to_menu(key, self.note_data[key]["title"], self.note_data[key]['category']) else: self.notes[key].update_menu_cat(categories) self.save() if self.menu_notes.index("end") is not None: self.icon.menu.entryconfigure(4, state="normal") else: self.icon.menu.entryconfigure(4, state="disabled") def update_menu(self): """Populate self.menu_show_cat and self.menu_hide_cat with the categories.""" self.menu_hide_cat.delete(0, "end") self.menu_show_cat.delete(0, "end") categories = CONFIG.options("Categories") categories.sort() for cat in categories: self.menu_show_cat.add_command( label=cat.capitalize(), command=lambda c=cat: self.show_cat(c)) self.menu_hide_cat.add_command( label=cat.capitalize(), command=lambda c=cat: self.hide_cat(c)) def save(self): """Save the data.""" with open(PATH_DATA, "wb") as fich: dp = pickle.Pickler(fich) dp.dump(self.note_data) def new(self): """Create a new note.""" key = "%i" % self.nb self.notes[key] = Sticky(self, key) data = self.notes[key].save_info() data["visible"] = True self.note_data[key] = data self.nb += 1 self.make_notes_sticky() def export_notes(self): export = Export(self) self.wait_window(export) categories_to_export, only_visible = export.get_export() if categories_to_export: initialdir, initialfile = os.path.split(PATH_DATA_BACKUP % 0) fichier = asksaveasfilename(defaultextension=".html", filetypes=[ (_("HTML file (.html)"), "*.html"), (_("Text file (.txt)"), "*.txt"), (_("All files"), "*") ], initialdir=initialdir, initialfile="", title=_('Export Notes As')) if fichier: try: if os.path.splitext(fichier)[-1] == ".html": # --- html export cats = {cat: [] for cat in categories_to_export} for key in self.note_data: cat = self.note_data[key]["category"] if cat in cats and ( (not only_visible) or self.note_data[key]["visible"]): cats[cat].append( (self.note_data[key]["title"], cst.note_to_html(self.note_data[key], self))) text = "" for cat in cats: cat_txt = "<h1 style='text-align:center'>" + _( "Category: {category}").format( category=cat) + "<h1/>\n" text += cat_txt text += "<br>" for title, txt in cats[cat]: text += "<h2 style='text-align:center'>%s</h2>\n" % title text += txt text += "<br>\n" text += "<hr />" text += "<br>\n" text += '<hr style="height: 8px;background-color:grey" />' text += "<br>\n" with open(fichier, "w") as fich: fich.write('<body style="max-width:30em">\n') fich.write( text.encode( 'ascii', 'xmlcharrefreplace').decode("utf-8")) fich.write("\n</body>") # if os.path.splitext(fichier)[-1] == ".txt": else: # --- txt export # export notes to .txt: all formatting is lost cats = {cat: [] for cat in categories_to_export} for key in self.note_data: cat = self.note_data[key]["category"] if cat in cats and ( (not only_visible) or self.note_data[key]["visible"]): cats[cat].append( (self.note_data[key]["title"], cst.note_to_txt(self.note_data[key]))) text = "" for cat in cats: cat_txt = _("Category: {category}").format( category=cat) + "\n" text += cat_txt text += "=" * len(cat_txt) text += "\n\n" for title, txt in cats[cat]: text += title text += "\n" text += "-" * len(title) text += "\n\n" text += txt text += "\n\n" text += "-" * 30 text += "\n\n" text += "#" * 30 text += "\n\n" with open(fichier, "w") as fich: fich.write(text) # else: # # --- pickle export # note_data = {} # for key in self.note_data: # if self.note_data[key]["category"] in categories_to_export: # if (not only_visible) or self.note_data[key]["visible"]: # note_data[key] = self.note_data[key] # # with open(fichier, "wb") as fich: # dp = pickle.Pickler(fich) # dp.dump(note_data) except Exception as e: report_msg = e.strerror != 'Permission denied' showerror(_("Error"), str(e), traceback.format_exc(), report_msg) def import_notes(self): fichier = askopenfilename(defaultextension=".backup", filetypes=[(_("Notes (.notes)"), "*.notes"), (_("All files"), "*")], initialdir=LOCAL_PATH, initialfile="", title=_('Import')) if fichier: try: with open(fichier, "rb") as fich: dp = pickle.Unpickler(fich) note_data = dp.load() for i, key in enumerate(note_data): data = note_data[key] note_id = "%i" % (i + self.nb) self.note_data[note_id] = data cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) self.hidden_notes[cat] = {} if data["visible"]: self.notes[note_id] = Sticky(self, note_id, **data) self.nb = int(max(self.note_data.keys(), key=lambda x: int(x))) + 1 self.update_menu() self.update_notes() except Exception: message = _("The file {file} is not a valid .notes file." ).format(file=fichier) showerror(_("Error"), message, traceback.format_exc()) def cleanup(self): """Remove unused latex images.""" img_stored = os.listdir(cst.PATH_LATEX) img_used = [] for data in self.note_data.values(): img_used.extend(list(data.get("latex", {}).keys())) for img in img_stored: if img not in img_used: os.remove(os.path.join(cst.PATH_LATEX, img)) def quit(self): self.destroy()
class Facade(Frame): """This is a Frame that contains group info, group order, apex and prime graph. """ def __init__(self, parent, group, show_graph=True, graph_class=None, **kw): """ Parameters: show_graph: whether to create and show graph instantly or provide a button to do that lately graph_factory: callable accepting one argument - the Group instance and returning Graph instance. Note that __str__ method of the callable is used in the UI. """ Frame.__init__(self, parent, **kw) self._group = group # self._show_apex = True self._show_graph = show_graph self._graph_class = graph_class # self._init_variables() # TODO: fix vertex label automatic positioning self._init_menu() self._init_components() @property def group(self): return self._group @property def apex_list_container(self): return self._apex_container @property def graph_canvas(self): return self._graph_canvas def _show_graph_canvas(self): self._show_graph_button.forget() # TODO: add different layouts and other options graph_class = self._graph_class self.graph = graph_class(self._group) self._graph_canvas = GraphCanvas(self._right_pane, SpringLayout(self.graph), caption=str(graph_class)) self._graph_canvas.pack(expand=True, fill='both') self._graph_canvas.vertex_label_mode = self.getvar( name=self.winfo_name() + ".vertexlabelposition") self._iterations_plugin = IterationsPlugin() self._iterations_plugin.apply(self._graph_canvas) self.update_layout() def _init_components(self): self._panes = PanedWindow(self, orient='horizontal', sashrelief='raised') self._panes.pack(expand=True, fill='both') self._left_pane = Frame(self._panes, padx=2, pady=2) self._right_pane = Frame(self._panes) self._panes.add(self._left_pane, width=250) self._panes.add(self._right_pane) # group name group_name_pane = LabelFrame(self._left_pane, text="Group", padx=10, pady=5) group_name_pane.pack(fill='x') self._group_name = GroupNameLabel(group_name_pane, self._group) self._group_name.pack(expand=True, fill='both') # group order group_order_pane = LabelFrame(self._left_pane, text="Order", padx=10, pady=5) group_order_pane.pack(fill='x') self._group_order = IntegerContainer(group_order_pane, integer=self._group.order()) self._group_order.pack(expand=True, fill='both') # apex self._apex_pane = LabelFrame(self._left_pane, text="Apex", padx=10, pady=5) self._apex_pane.pack(expand=True, fill='both') self._apex_container = ApexListContainer(self._apex_pane, apex=self._group.apex()) self._apex_container.pack(expand=True, fill='both') # graph controls cocliques_frame = LabelFrame(self._left_pane, text="Cocliques", padx=10, pady=5) cocliques_frame.pack(fill='x') self._cocliques_button = Button(cocliques_frame, text="Calculate", command=self._show_cocliques) self._cocliques_button.pack(anchor='nw') self._cocliques_container = ListContainer(cocliques_frame) self._cocliques_list = Listbox(self._cocliques_container) self._cocliques_container.set_listbox(self._cocliques_list) # Button(graph_controls, text='Group equivalent vertices').pack(anchor='nw') # this is a button that show up instead of graph canvas if we uncheck 'Show graph' checkbox. self._show_graph_button = Button(self._right_pane, text='Show graph', command=self._show_graph_canvas) self._graph_canvas = None if self._show_graph: self._show_graph_canvas() else: self._show_graph_button.pack() def _init_variables(self): def set_default_var(name): """Sets widget-specific var with same value as root. """ default_var = self.getvar(name) local_var_name = self.winfo_name() + "." + name self.setvar(local_var_name, default_var) return local_var_name local_name = set_default_var("vertexlabelposition") tools.trace_variable(self, local_name, "w", self._change_vertex_label_position) def _change_vertex_label_position(self, name, *arg): # override default value self.setvar("vertexlabelposition", self.getvar(name)) if self._graph_canvas is not None: self._graph_canvas.vertex_label_mode = self.getvar(name) def _init_menu(self): """Init menu bar content. """ toplevel = self.winfo_toplevel() if toplevel['menu']: self._menu = self.nametowidget(name=toplevel['menu']) else: self._menu = Menu(toplevel) toplevel['menu'] = self._menu graph_options = Menu(self._menu, tearoff=0) self._menu.add_cascade(label="Graph", menu=graph_options) self._menu_index = self._menu.index("end") vertex_label_position_menu = Menu(graph_options, tearoff=0) graph_options.add_cascade(label="Label position", menu=vertex_label_position_menu) menu_var = self.winfo_name() + ".vertexlabelposition" vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Auto", value="auto") vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Center", value="center") graph_options.add_command(label="Save graph...", command=self.call_graph_save_dialog) self.bind("<Destroy>", self.__destroy_menu) #noinspection PyUnusedLocal def __destroy_menu(self, event): try: self._menu.delete(self._menu_index) except TclError: pass def _show_cocliques(self): cocliques = self.graph.max_cocliques() def select_coclique(*_): index = next(iter(self._cocliques_list.curselection()), None) if index is not None: selected = cocliques[int(index)] pick_state = self._graph_canvas.picked_vertex_state pick_state.clear() for value in selected: pick_state.pick(self._graph_canvas.get_vertex(value)) self._cocliques_list.insert(0, *[', '.join(map(str, coclique)) for coclique in cocliques]) self._cocliques_list.bind("<Double-Button-1>", select_coclique) self._cocliques_button.forget() self._cocliques_container.pack(expand=True, fill='both') def call_graph_save_dialog(self): file_name = filedialog.asksaveasfilename(defaultextension='.ps', filetypes=[('PostScript', '.ps')], parent=self.winfo_toplevel(), title="Save graph as image") if file_name: with codecs.open(file_name, 'w', encoding='utf-8') as f: f.write(self._graph_canvas.postscript()) def update_layout(self): try: self._iterations_plugin.iterate(50) except AttributeError: pass
class Fenetre: def __init__(self, root, job): # Récupération de l'objet Jeu self.jeu = job self.saved = True # Création de la fenêtre principale self.root = root self.root.title('Rêve de Dragon') self.root.resizable(True, True) # Création des menus # On a 4 menu principaux : filemenu, cmdmenu, viewmenu et helpmenu self.menubar = Menu(root) self.root.config(menu=self.menubar) # filemenu: menu de manipulation des fichiers contenant les personnages self.filemenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Fichier", menu=self.filemenu) self.filemenu.add_command(label="Nouveau", command=self.nouveau) self.filemenu.add_command(label="Ouvrir", command=self.ouvrir) self.filemenu.add_separator() self.filemenu.add_command(label="Enregistrer", command=self.jeu.enregistrer) self.filemenu.add_command(label="Enregistrer sous...", command=self.jeu.enregistrer_sous) self.filemenu.add_separator() self.filemenu.add_command(label="Fermer", command=self.fermer) self.filemenu.add_separator() self.filemenu.add_command(label="Imprimer", command=self.void, state='disabled') self.filemenu.add_separator() self.filemenu.add_command(label="Quitter", command=self.quitter) # cmdmenu: menu des commandes sur les personnages self.cmdmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Commande", menu=self.cmdmenu) self.cmdmenu.add_command(label="Nouvelle Partie", command=self.partie) self.cmdmenu.add_separator() self.cmdmenu.add_command(label="Nouveau Personnage", command=self.creer) self.cmdmenu.add_separator() self.cmdmenu.add_command(label="Valider le Personnage", command=self.valider) # viewmenu: menu de sélection du personnage à l'affichage # Ce menu est vide en l'absence de personnage # Il est rempli au chargement ou à la création d'un personnage self.viewmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Personnage", menu=self.viewmenu) # helpmenu: menu d'aide self.helpmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Aide", menu=self.helpmenu) self.helpmenu.add_command(label="Règles du Jeu", command=self.regles) self.helpmenu.add_command(label="Utilisation du Programme", command=self.utilise) self.helpmenu.add_command(label="A Propos...", command=self.a_propos) # frame1 : Fiche du personnage frame1 = Frame(root, borderwidth=0, relief='flat', height=200, width=600) frame1.grid(row=0, column=0, sticky='NW', padx="10", pady="5") # Nom self.Entry_Nom = StringVar() self.Old_Nom = "" Label(frame1, text='Nom:').grid(row=0, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Nom, justify='left', width=34)\ .grid(row=0, column=2, columnspan=4, sticky='W', padx="5") # Age self.Entry_Age = IntVar() Label(frame1, text='Age:').grid(row=1, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Age, justify='right', width=3)\ .grid(row=1, column=2, sticky='W', padx="5") # Heure de naissance (pour hauts-rêvants) self.Entry_Heure = IntVar() Label(frame1, text='Heure de Naissance:').grid(row=1, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Heure, justify='right', width=3) \ .grid(row=1, column=4, sticky='W', padx="5") # Taille self.Entry_Taille = IntVar() Label(frame1, text='Taille:').grid(row=1, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_Taille, justify='right', width=3)\ .grid(row=1, column=6, sticky='W', padx="5") # Poids self.Entry_Poids = IntVar() Label(frame1, text='Poids:').grid(row=1, column=7, sticky='E') Entry(frame1, textvariable=self.Entry_Poids, justify='right', width=3)\ .grid(row=1, column=8, sticky='W', padx="5") # Beauté self.Entry_Beaute = IntVar() Label(frame1, text='Beauté:').grid(row=2, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Beaute, justify='right', width=3) \ .grid(row=2, column=2, sticky='W', padx="5") # Cheveux self.Entry_Cheveux = StringVar() Label(frame1, text='Cheveux:').grid(row=2, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Cheveux, justify='left', width=8)\ .grid(row=2, column=4, sticky='W', padx="5") # Yeux self.Entry_Yeux = StringVar() Label(frame1, text='Yeux:').grid(row=2, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_Yeux, justify='left', width=8)\ .grid(row=2, column=6, sticky='W', padx="5") # Haut rêvant self.Entry_HRevant = IntVar() Checkbutton(frame1, text="Haut-Rêvant", variable=self.Entry_HRevant, command=self.sel_revant) \ .grid(row=2, column=7, columnspan=2, sticky='W', padx="5") # Sexe self.Entry_Sexe = StringVar() Label(frame1, text='Sexe:').grid(row=3, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Sexe, justify='left', width=2)\ .grid(row=3, column=2, sticky='W', padx="5") # Ambidextre self.Entry_Ambidextre = IntVar() Label(frame1, text='Ambidextre:').grid(row=3, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Ambidextre, justify='right', width=3)\ .grid(row=3, column=4, sticky='W', padx="5") # Signes Particuliers self.Entry_SignesP = StringVar() Label(frame1, text='Signes Particuliers:').grid(row=3, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_SignesP, justify='left', width=37)\ .grid(row=3, column=6, columnspan=3, sticky='W', padx="5") # Frame 2 : Caractéristiques frame2 = LabelFrame(root, text=" Caractéristiques ", borderwidth=2, relief='ridge', height=200, width=600) frame2.grid(row=1, column=0, sticky='NW', padx="10", pady="5") frame20 = LabelFrame(frame2, text=' Physiques ', borderwidth=2, relief='ridge', height=200, width=200) frame20.grid(row=0, column=0, sticky='NW', padx="5", pady="5") frame21 = LabelFrame(frame2, text=' Mentales ', borderwidth=2, relief='ridge', height=200, width=200) frame21.grid(row=0, column=1, sticky='NW', padx="5", pady="5") frame22 = LabelFrame(frame2, text=' Pouvoirs ', borderwidth=2, relief='ridge', height=200, width=200) frame22.grid(row=0, column=2, sticky='NW', padx="5", pady="5") frame23 = LabelFrame(frame2, text=' Dérivées ', borderwidth=2, relief='ridge', height=200, width=200) frame23.grid(row=0, column=3, sticky='NW', padx="5", pady="5") self.Entry_C = [] # Colonne 0 de taille à Dextérité for i in range(0, 6): self.Entry_C.append(IntVar()) Label(frame20, text=" "+personnage.caracteristique(i, 1)+':')\ .grid(row=i, column=0, sticky='E') Entry(frame20, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i, column=1, sticky='W', padx="5") Label(frame20, text=' ').grid(row=6, column=0, sticky='E') # Colonne 1 de Vue à Empathie for i in range(6, 12): self.Entry_C.append(IntVar()) Label(frame21, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-6, column=0, sticky='E') Entry(frame21, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i-6, column=1, sticky='W', padx="5") Label(frame21, text=' ').grid(row=6, column=0, sticky='E') # Colonne 2 de Rêve à Chance for i in range(12, 14): self.Entry_C.append(IntVar()) Label(frame22, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-12, column=0, sticky='E') Entry(frame22, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i-12, column=1, sticky='W', padx="5") for i in range(2, 7): Label(frame22, text=' ').grid(row=i, column=0, sticky='E') # Colonne 3 de Tir à Dérobée (ne peuvent être saisies) for i in range(14, 18): self.Entry_C.append(IntVar()) Label(frame23, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-14, column=0, sticky='E') Entry(frame23, textvariable=self.Entry_C[i], justify='right', width=3, state='disabled')\ .grid(row=i-14, column=1, sticky='W', padx="5") for i in range(4, 7): Label(frame23, text=' ').grid(row=i, column=0, sticky='E') # frame 3 : Points et Seuils (ne peuvent être saisis) frame3 = Frame(root, borderwidth=0, relief='flat', height=200, width=600, padx="5", pady="5") frame3.grid(row=2, column=0, sticky='NW', padx="10") self.Entry_P = [] # Vie - Endurance - Encombrement for i in range(0, 3): self.Entry_P.append(IntVar()) Label(frame3, text=personnage.point(i, 1) + ':').grid(row=0, column=2 * i, sticky='E') Entry(frame3, textvariable=self.Entry_P[i], justify='right', width=3, state='disabled')\ .grid(row=0, column=2*i+1, sticky='W', padx="5") # Bonus aux Dommages - Malus Armure - Seuil de Constitution - Seuil de Sustentation for i in range(3, 7): self.Entry_P.append(IntVar()) Label(frame3, text=personnage.point(i, 1) + ':').grid(row=1, column=2 * i - 6, sticky='E') Entry(frame3, textvariable=self.Entry_P[i], justify='right', width=3, state='disabled')\ .grid(row=1, column=2*i-5, sticky='W', padx="5") # frame 4 : Compétences frame4 = LabelFrame(root, text=" Compétences ", borderwidth=2, relief='ridge', height=200, width=800) frame4.grid(row=3, column=0, columnspan=2, sticky='NW', padx="10", pady="5") frame40 = LabelFrame(frame4, text=' Générales ', borderwidth=2, relief='ridge', height=200, width=300) frame40.grid(row=0, column=0, rowspan=2, sticky='NW', padx="5", pady="5") frame41 = LabelFrame(frame4, text=' Particulières ', borderwidth=2, relief='ridge', height=200, width=300) frame41.grid(row=0, column=1, rowspan=2, sticky='NW', padx="5", pady="5") frame42 = LabelFrame(frame4, text=' Spécialisées ', borderwidth=2, relief='ridge', height=200, width=300) frame42.grid(row=0, column=2, rowspan=2, sticky='NW', padx="5", pady="5") frame43 = LabelFrame(frame4, text=' Connaissances ', borderwidth=2, relief='ridge', height=200, width=300) frame43.grid(row=0, column=3, sticky='NW', padx="5", pady="5") frame44 = LabelFrame(frame4, text=' Draconic ', borderwidth=2, relief='ridge', height=200, width=300) frame44.grid(row=1, column=3, sticky='SW', padx="5", pady="5") frame45 = LabelFrame(frame4, text=' Combat Mélée ', borderwidth=2, relief='ridge', height=200, width=300) frame45.grid(row=0, column=4, rowspan=2, sticky='NW', padx="5", pady="5") frame46 = LabelFrame(frame4, text=' Combat Tir-Lancer ', borderwidth=2, relief='ridge', height=200, width=300) frame46.grid(row=0, column=5, rowspan=2, sticky='NW', padx="5", pady="5") self.Entry_A = [] # Colonne 0 : Générales for i in range(0, 11): self.Entry_A.append(IntVar()) Label(frame40, text=" " + personnage.competence(i, 2) + ':').grid( row=i, column=0, sticky='E') Entry(frame40, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i, column=1, sticky='W', padx="5") for i in range(11, 15): Label(frame40, text=' ').grid(row=i, column=0, sticky='E') # Colonne 1 : Particulières for i in range(11, 26): self.Entry_A.append(IntVar()) Label(frame41, text=" " + personnage.competence(i, 2) + ':').grid(row=i - 11, column=0, sticky='E') Entry(frame41, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-11, column=1, sticky='W', padx="5") # Colonne 2 : Spécialisées for i in range(26, 36): self.Entry_A.append(IntVar()) Label(frame42, text=" "+personnage.competence(i, 2)+':')\ .grid(row=i-25, column=0, sticky='E') Entry(frame42, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-25, column=1, sticky='W', padx="5") for i in range(10, 15): Label(frame42, text=' ').grid(row=i + 1, column=0, sticky='E') # Colonne 3: Connaissances for i in range(36, 43): self.Entry_A.append(IntVar()) Label(frame43, text=" "+personnage.competence(i, 2)+':')\ .grid(row=i-35, column=0, sticky='E') Entry(frame43, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-35, column=1, sticky='W', padx="5") Label(frame43, text=' ').grid(row=8, column=0, sticky='E') # Colonne 3 : Draconic self.Draconic = [] for i in range(0, 4): self.Entry_A.append(IntVar()) Label(frame44, text=" "+personnage.competence(i+43, 2)+':')\ .grid(row=i, column=0, sticky='E') self.Draconic.append( Entry(frame44, textvariable=self.Entry_A[i + 43], justify='right', width=3)) self.Draconic[i].grid(row=i, column=1, sticky='W', padx="5") Label(frame44, text=' ').grid(row=4, column=0, sticky='E') # Colonne 4 : Combat Mélée for i in range(47, 60): self.Entry_A.append(IntVar()) Label(frame45, text=personnage.competence(i, 2) + ':') \ .grid(row=i - 46, column=0, sticky='E') Entry(frame45, textvariable=self.Entry_A[i], justify='right', width=3) \ .grid(row=i - 46, column=1, sticky='W', padx="5") for i in range(13, 15): Label(frame45, text=' ').grid(row=i + 1, column=0, sticky='E') # Colonne 5 : Combat Tir for i in range(60, 66): self.Entry_A.append(IntVar()) Label(frame46, text=" " + personnage.competence(i, 2) + ':') \ .grid(row=i - 59, column=0, sticky='E') Entry(frame46, textvariable=self.Entry_A[i], justify='right', width=3) \ .grid(row=i - 59, column=1, sticky='W', padx="5") for i in range(6, 15): Label(frame46, text=' ').grid(row=i + 1, column=0, sticky='E') # frame5 : table de résolution et lancer de dé frame5 = LabelFrame(root, text=" Résolution et Lancer de Dés ", borderwidth=2, relief='ridge', height=200, width=600) frame5.grid(row=0, column=1, rowspan=3, columnspan=2, sticky='NW', padx="10", pady="5") # Listbox caractéristiques Label(frame5, text=' Caractéristique:').grid(row=0, column=0, columnspan=2, padx="10", sticky='NW') self.liste1 = Listbox(frame5, height=13, width=18, relief='sunken') self.liste1.grid(row=1, column=1, sticky='NW', pady="5") for i in range(0, 18): self.liste1.insert(i, personnage.caracteristique(i, 1)) self.liste1.bind('<<ListboxSelect>>', self.sel_liste1) # Listbox compétences Label(frame5, text='Compétence:').grid(row=0, column=2, columnspan=2, padx="10", sticky='NW') self.liste2 = Listbox(frame5, height=13, width=18, relief='sunken') self.liste2.grid(row=1, column=3, sticky='NW', pady="5") for i in range(0, 66): self.liste2.insert(i, personnage.competence(i, 2)) self.liste2.bind('<<ListboxSelect>>', self.sel_liste2) # Zone de résulats self.Entry_R_C_Val = IntVar() Entry(frame5, textvariable=self.Entry_R_C_Val, justify='right', width=3,) \ .grid(row=16, column=0, sticky='E', padx="10") self.Entry_R_C_Name = StringVar() Entry(frame5, textvariable=self.Entry_R_C_Name, justify='left', width=18, state='disabled') \ .grid(row=16, column=1, sticky='W') self.Entry_R_A_Val = IntVar() Entry(frame5, textvariable=self.Entry_R_A_Val, justify='right', width=3,) \ .grid(row=16, column=2, sticky='E', padx="10") self.Entry_R_A_Name = StringVar() Entry(frame5, textvariable=self.Entry_R_A_Name, justify='left', width=18, state='disabled') \ .grid(row=16, column=3, sticky='W') Label(frame5, text=' Seuil de Réussite:').grid(row=17, column=0, sticky='NE') self.Entry_R_Seuil = IntVar() Entry(frame5, textvariable=self.Entry_R_Seuil, justify='right', width=3, state='disabled')\ .grid(row=17, column=1, sticky='W', padx="10") Label(frame5, text='Tirage:').grid(row=17, column=2, sticky='NE') self.Entry_R_Tirage = IntVar() Entry(frame5, textvariable=self.Entry_R_Tirage, justify='right', width=3, state='disabled') \ .grid(row=17, column=3, sticky='W', padx="10") Label(frame5, text='Résultat Spécial:').grid(row=18, column=0, sticky='NE') self.Entry_R_Special = StringVar() Entry(frame5, textvariable=self.Entry_R_Special, justify='left', width=30, state='disabled') \ .grid(row=18, column=1, columnspan=2, sticky='W', padx="10") Label(frame5, text=' ').grid(row=19, column=4, sticky='NE') # Bouton pour le lancer de Dés Button(frame5, text="Lancer les Dés", command=self.lancer) \ .grid(row=18, column=3, columnspan=3, sticky='W', padx="10") # La mascote # On la fait déborder sur le frame4 pour gagner en largeur totale self.dragon = PhotoImage(file='./dragon3.gif') logo = Canvas(root, width=200, height=181, bd=1, relief='ridge') logo.grid(row=3, column=1, columnspan=2, sticky='SE', padx="10", pady="3") logo.create_image(0, 0, image=self.dragon, anchor='nw') # L'ecran étant initialisé, on peut créér un premier personnage par défaut self.creer() return # Fonction de recopie de la sélection depuis la Listbox des caractéristiques # Met à jour les 2 champs points et nom de caractéristique pour le calcul de résolution def sel_liste1(self, event): if self.liste1.curselection() != (): index = self.liste1.curselection()[0] self.Entry_R_C_Name.set(self.liste1.get(index)) self.Entry_R_C_Val.set(self.Entry_C[index].get()) return # Fonction de recopie de la sélection depuis la Listbox des compétences # Met à jour les 2 champs points et nom de compétence pour le calcul de résolution def sel_liste2(self, event): if self.liste2.curselection() != (): index = self.liste2.curselection()[0] self.Entry_R_A_Name.set(self.liste2.get(index)) self.Entry_R_A_Val.set(self.Entry_A[index].get()) return # Fonction de changement d'etat haut-rêvant def sel_revant(self): if self.Entry_HRevant.get() != 1: for i in range(0, 4): self.Entry_A[i + 42].set(-11) self.Draconic[i].configure(state='disabled') else: for i in range(0, 4): self.Draconic[i].configure(state='normal') return # Nouveau jeu # Il faut préalablement fermer le jeu en cours def nouveau(self): if self.fermer(): self.jeu.nouveau() return # Ouvrir jeu # Il faut préalablement fermer le jeu en cours # On reçoit le nom du jeu suivi d'une liste de personnages ou None si rien d'ouvert par le jeu def ouvrir(self): if self.fermer(): names = self.jeu.ouvrir() if names != None: numero = -1 for person in names: # index 0 : nom du fichier jeu if numero < 0: self.root.title('Rêve de Dragon - ' + person) # autres index : personnages # index vaudra le nombre de personnages reçus else: self.viewmenu.add_command(label=person, command=lambda index=numero: self.selectionner(index)) numero += 1 # On affiche le premier personnage if numero > 0: self.selectionner(0) return # Fermer le jeu en cours # On efface tous les personnages # on cree un nouveau personnage vide pour obtenir un affichage vierge def fermer(self): if not self.saved: self.saved = askyesno( 'Fermer', 'Voulez-vous vraiment fermer ce Jeu ?\nLes données non enregistrées seront perdues' ) if self.saved: last = self.viewmenu.index("end") if last is not None: for i in range(last + 1): self.viewmenu.delete(0) self.root.title('Rêve de Dragon') self.jeu.fermer() self.creer() return self.saved # Quitter le programme # onh détruit la fenêtre et on quitte def quitter(self): if askyesno('Quitter', 'Voulez-vous vraiment quitter le programme ?'): self.root.destroy() self.root.quit() return # Fonction interne d'affichage des données d'un personnage # Copie toutes les données du dictionnaire local dans les variables associées aux champs de saisie def affiche(self): self.Entry_Nom.set(self.pod["Fiche"]["Nom"]) self.Entry_Age.set(self.pod["Fiche"]["Age"]) self.Entry_Heure.set(self.pod["Fiche"]["Heure_Naissance"]) self.Entry_Taille.set(self.pod["Fiche"]["Taille"]) self.Entry_Poids.set(self.pod["Fiche"]["Poids"]) self.Entry_Sexe.set(self.pod["Fiche"]["Sexe"]) self.Entry_Cheveux.set(self.pod["Fiche"]["Cheveux"]) self.Entry_Yeux.set(self.pod["Fiche"]["Yeux"]) self.Entry_Beaute.set(self.pod["Fiche"]["Beaute"]) self.Entry_Ambidextre.set(self.pod["Fiche"]["Ambidextre"]) self.Entry_HRevant.set(self.pod["Fiche"]["Haut_Revant"]) self.Entry_SignesP.set(self.pod["Fiche"]["Signes_Particulier"]) for i in range(0, 18): self.Entry_C[i].set( self.pod["Caracteristique"][personnage.caracteristique(i, 0)]) for i in range(0, 7): self.Entry_P[i].set(self.pod["Point"][personnage.point(i, 0)]) for i in range(0, 66): self.Entry_A[i].set(self.pod["Competence"][personnage.competence( i, 0)][personnage.competence(i, 1)]) if self.Entry_HRevant.get() != 1: for i in range(0, 4): self.Draconic[i].configure(state='disabled') return # Création d'un nouveau personnage # On demande au jeu de créer un nouveau personnage dans la liste # On initialise toutes les variables de saisie aux valeur reçues def creer(self): self.pod = self.jeu.creer() self.affiche() return # Validation des données du personnage # On reconstitue le dictionnaire qui est envoyé au jeu pour vérification # Le jeu répond avec un dictionnaire contenant # - l'index du personnage # - Le nom de personnage # - Un message d'erreur ou d'acceptation def valider(self): if len(self.Entry_Nom.get()) < 1: return self.pod["Fiche"]["Nom"] = self.Entry_Nom.get() self.pod["Fiche"]["Age"] = self.Entry_Age.get() self.pod["Fiche"]["Heure_Naissance"] = self.Entry_Heure.get() self.pod["Fiche"]["Taille"] = self.Entry_Taille.get() self.pod["Fiche"]["Poids"] = self.Entry_Poids.get() self.pod["Fiche"]["Sexe"] = self.Entry_Sexe.get() self.pod["Fiche"]["Cheveux"] = self.Entry_Cheveux.get() self.pod["Fiche"]["Yeux"] = self.Entry_Yeux.get() self.pod["Fiche"]["Beaute"] = self.Entry_Beaute.get() self.pod["Fiche"]["Ambidextre"] = self.Entry_Ambidextre.get() self.pod["Fiche"]["Haut_Revant"] = self.Entry_HRevant.get() self.pod["Fiche"]["Signes_Particulier"] = self.Entry_SignesP.get() for i in range(0, 18): self.pod["Caracteristique"][personnage.caracteristique( i, 0)] = self.Entry_C[i].get() for i in range(0, 7): self.pod["Point"][personnage.point(i, 0)] = self.Entry_P[i].get() for i in range(0, 65): self.pod["Competence"][personnage.competence( i, 0)][personnage.competence(i, 1)] = self.Entry_A[i].get() retour = self.jeu.valider(self.pod) index = retour["index"] # On a bien un index valide : alors on met à jour le menu et on va chercher les données du personnage if index is not None: if self.viewmenu.entrycget(index, 'label') != self.Old_Nom: self.viewmenu.entryconfigure(index, label=retour["nom"]) elif self.viewmenu.entrycget(index, 'label') != retour["nom"]: self.viewmenu.add_command( label=retour["nom"], command=lambda index=index: self.selectionner(index)) self.pod = self.jeu.selectionner(index) self.affiche() # En cas d'erreur ou retour ok, il y a un message du jeu if len(retour["message"]): showerror("Validation", retour["message"]) self.saved = False return # Sélection d'un personnage depuis le menu # On envoie au jeu l'index du menu qui correspond à l'index de la liste du jeu # Les données du personnage reçu sont ensuite affichées def selectionner(self, code): self.pod = self.jeu.selectionner(code) self.affiche() return # Nouvelle partie # On demande au Jeu de changer de partie # Le jeu renvoie le contenu du personnage courant def partie(self): if askyesno( 'Nouvelle Partie', 'Voulez-vous vraiment terminer cette partie ?\nLes données non enregistrées seront perdues' ): self.pod = self.jeu.partie() self.affiche() return # Lancer de dé # Calcul de résolution puis lancer des dés # On passe au jeu les valeurs de caractéristiques et compétences sélectionnées # Le résultat sera récupéré en retour et affiché def lancer(self): resultat = self.jeu.lancer(self.Entry_R_C_Val.get(), self.Entry_R_A_Val.get()) self.Entry_R_Seuil.set(resultat["seuil"]) self.Entry_R_Tirage.set(resultat["tirage"]) self.Entry_R_Special.set(resultat["special"]) self.saved = False return # Affichage de la boite de dialogue A propos # Le texte est dans le fichier A_PROPOS.TXT def a_propos(self): fp = open("A_PROPOS.TXT", "r") texte_a_propos = fp.read() fp.close() showinfo("A Propos de...", texte_a_propos) return # Dialogue d'aide pour connaitre les règles du jeu # Le texte est dans le fichier REGLES.TXT def regles(self): file = "REGLE.TXT" titre = "Règles du Jeu Rêve de Dragon." self.aide(file, titre) return # Le texte est dans le fichier AIDE.TXT def utilise(self): file = "AIDE.TXT" titre = "Utilisation de Rêve de Dragon." self.aide(file, titre) return # Aide du jeu # Affiche une boite de dialogue avec un widget texte contenant l'aide def aide(self, file, titre): # On ouvre une fenêtre fille de celle du jeu self.wdw = Toplevel() self.wdw.geometry('+400+100') self.wdw.title(titre) # Le texte de l'aide est stocké dans un fichier Atexte fp = open(file, "r") texte_aide = fp.read() fp.close() # On l'affiche dans un widget Text avec une barre de défilement # Ne fonctionne que si on utilise grid pour placer les Widgets # Il faut mettre le widget en état disabled pour éviter que l'on y entre du texte self.S = Scrollbar(self.wdw, orient='vertical') self.T = Text(self.wdw, height=50, width=100, font=('TkDefaultFont', 10)) self.T.grid(row=0, column=0, sticky='NW') self.S.configure(command=self.T.yview) self.T.configure(yscrollcommand=self.S.set) self.S.grid(row=0, column=1, sticky='SN') self.T.configure(state='normal') self.T.insert('end', texte_aide) self.T.configure(state='disabled') # La nouvelle fenêtre est ouverte en modal # Il faudra la fermer pour reprendre le contrôle de la fenêtre principale self.wdw.transient(self.root) self.wdw.grab_set() self.root.wait_window(self.wdw) return # fonction qui ne fait rien (pour les tests) def void(self): return
class PlaylistHandlerSet(Frame): def __init__(self, w: PlaylistControl, *args, **kwargs): self.EMPTY_MENUBTN = _("Select Playlist") #After defined _ by gettext self.playlist = w.playlist self.playlist_control = w super().__init__(w, *args, **kwargs) #___ self.menubtn = Menubutton(self, direction="above", width=13, text=config.general["playlist"]) self.menubtn.pack() self.menu = Menu(self.menubtn, bg=config.colors["BG"], activebackground=config.colors["TV_BG_HOVER"], activeforeground=config.colors["FG"], tearoff=False) self.menu.add_command(label=_("Import Playlist"), command=self._newPlaylist) self.menu.add_separator() for playlist in config.user_config["Playlists"]: #https://stackoverflow.com/questions/11723217/python-lambda-doesnt-remember-argument-in-for-loop func_get_set_playlist = lambda playlist=playlist: self.setPlaylist( playlist) self.menu.add_command(label=playlist, command=func_get_set_playlist) self.menubtn.configure(menu=self.menu) #__________________________________________________ def _newPlaylist(self): folder: str = filedialog.askdirectory( title=_("Select folder for new playlist")) if folder == "": return playlist = os.path.basename(folder) if playlist not in config.user_config["Playlists"]: config.user_config["Playlists"][playlist] = { "path": os.path.normcase(folder), "orderby": ["title", 1], "filter": "" } self.menu.add_command(label=playlist, command=lambda: self.setPlaylist(playlist)) self.setPlaylist(playlist) def setPlaylist(self, playlist: str): ''' There will be no playlist when starting the application if the playlist had been destroyed with "self.delPlaylist()" and closed without selecting one playlist ''' if playlist == "": self.menubtn["text"] = self.EMPTY_MENUBTN return playlist_path = config.user_config["Playlists"][playlist]["path"] if not os.path.exists(playlist_path): messagebox.showerror(_("Load failed"), _("The folder does not to exist")) self.delPlaylist(playlist) return config.general["playlist"] = playlist config.playlist = config.user_config["Playlists"][playlist] self.menubtn["text"] = playlist self.playlist.setPlaylist(playlist_path) self.playlist_control.sortPlaylistForced(config.playlist["orderby"][0], config.playlist["orderby"][1]) self.playlist_control.setSearch(config.playlist["filter"]) def delPlaylist(self, playlist: str, in_tv: bool = True): config.user_config["Playlists"].pop(playlist) self.menu.delete(playlist) if in_tv: config.general["playlist"] = "" self.menubtn["text"] = self.EMPTY_MENUBTN self.playlist.delPlaylist() def renamePlaylist(self, playlist_new: str, playlist_new_path: str): playlist_old = config.general["playlist"] config.general["playlist"] = playlist_new config.user_config["Playlists"][playlist_new] = config.user_config[ "Playlists"].pop(playlist_old) config.playlist = config.user_config["Playlists"][playlist_new] config.playlist["path"] = playlist_new_path self.menu.entryconfig(self.menu.index(playlist_old), label=playlist_new, command=lambda: self.setPlaylist(playlist_new)) self.menubtn["text"] = playlist_new #Change the path of each song in the playlist for song in self.playlist.getAllSongs(): song.path = os.path.join(playlist_new_path, song.name) + song.extension