Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
    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'))
Exemple #4
0
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)
Exemple #5
0
    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)
Exemple #7
0
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()
Exemple #8
0
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
Exemple #10
0
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