class MainWindow(object): def __init__(self, root, options): ''' ----------------------------------------------------- | main button toolbar | ----------------------------------------------------- | < ma | in content area > | | | | | File list | File name | | | | ----------------------------------------------------- | status bar area | ----------------------------------------------------- ''' # Obtain and expand the current working directory. if options.path: base_path = os.path.abspath(options.path) else: base_path = os.path.abspath(os.getcwd()) self.base_path = os.path.normcase(base_path) # Create a filename normalizer based on the CWD. self.filename_normalizer = filename_normalizer(self.base_path) # Set up dummy coverage data self.coverage_data = {'lines': {}, 'total_coverage': None} # Root window self.root = root self.root.title('Duvet') self.root.geometry('1024x768') # Prevent the menus from having the empty tearoff entry self.root.option_add('*tearOff', tk.FALSE) # Catch the close button self.root.protocol("WM_DELETE_WINDOW", self.cmd_quit) # Catch the "quit" event. self.root.createcommand('exit', self.cmd_quit) # Setup the menu self._setup_menubar() # Set up the main content for the window. self._setup_button_toolbar() self._setup_main_content() self._setup_status_bar() # Now configure the weights for the root frame self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=0) self.root.rowconfigure(1, weight=1) self.root.rowconfigure(2, weight=0) ###################################################### # Internal GUI layout methods. ###################################################### def _setup_menubar(self): # Menubar self.menubar = tk.Menu(self.root) # self.menu_Apple = Menu(self.menubar, name='Apple') # self.menubar.add_cascade(menu=self.menu_Apple) self.menu_file = tk.Menu(self.menubar) self.menubar.add_cascade(menu=self.menu_file, label='File') self.menu_help = tk.Menu(self.menubar) self.menubar.add_cascade(menu=self.menu_help, label='Help') # self.menu_Apple.add_command(label='Test', command=self.cmd_dummy) # self.menu_file.add_command(label='New', command=self.cmd_dummy, accelerator="Command-N") # self.menu_file.add_command(label='Close', command=self.cmd_dummy) self.menu_help.add_command(label='Open Documentation', command=self.cmd_duvet_docs) self.menu_help.add_command(label='Open Duvet project page', command=self.cmd_duvet_page) self.menu_help.add_command(label='Open Duvet on GitHub', command=self.cmd_duvet_github) self.menu_help.add_command(label='Open BeeWare project page', command=self.cmd_beeware_page) # last step - configure the menubar self.root['menu'] = self.menubar def _setup_button_toolbar(self): ''' The button toolbar runs as a horizontal area at the top of the GUI. It is a persistent GUI component ''' # Main toolbar self.toolbar = tk.Frame(self.root) self.toolbar.grid(column=0, row=0, sticky=(tk.W, tk.E)) # Buttons on the toolbar self.refresh_button = tk.Button(self.toolbar, text='Refresh', command=self.cmd_refresh) self.refresh_button.grid(column=0, row=0) # Coverage summary for currently selected file. self.coverage_total_summary = tk.StringVar() self.coverage_total_summary_label = Label( self.toolbar, textvariable=self.coverage_total_summary, anchor=tk.E, padding=(5, 0, 5, 0), font=('Helvetica','20') ) self.coverage_total_summary_label.grid(column=1, row=0, sticky=(tk.W, tk.E)) self.toolbar.columnconfigure(0, weight=0) self.toolbar.columnconfigure(1, weight=1) self.toolbar.rowconfigure(0, weight=0) def _setup_main_content(self): ''' Sets up the main content area. It is a persistent GUI component ''' # Main content area self.content = PanedWindow(self.root, orient=tk.HORIZONTAL) self.content.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) # Create the tree/control area on the left frame self._setup_left_frame() self._setup_project_file_tree() self._setup_global_file_tree() # Create the output/viewer area on the right frame self._setup_code_area() # Set up weights for the left frame's content self.content.columnconfigure(0, weight=1) self.content.rowconfigure(0, weight=1) self.content.pane(0, weight=1) self.content.pane(1, weight=4) def _setup_left_frame(self): ''' The left frame mostly consists of the tree widget ''' # The left-hand side frame on the main content area # The tabs for the two trees self.tree_notebook = Notebook( self.content, padding=(0, 5, 0, 5) ) self.content.add(self.tree_notebook) def _setup_project_file_tree(self): self.project_file_tree_frame = tk.Frame(self.content) self.project_file_tree_frame.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) self.tree_notebook.add(self.project_file_tree_frame, text='Project') self.project_file_tree = FileView(self.project_file_tree_frame, normalizer=self.filename_normalizer, root=self.base_path) self.project_file_tree.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # # The tree's vertical scrollbar self.project_file_tree_scrollbar = tk.Scrollbar(self.project_file_tree_frame, orient=tk.VERTICAL) self.project_file_tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S)) # # Tie the scrollbar to the text views, and the text views # # to each other. self.project_file_tree.config(yscrollcommand=self.project_file_tree_scrollbar.set) self.project_file_tree_scrollbar.config(command=self.project_file_tree.yview) # Setup weights for the "project_file_tree" tree self.project_file_tree_frame.columnconfigure(0, weight=1) self.project_file_tree_frame.columnconfigure(1, weight=0) self.project_file_tree_frame.rowconfigure(0, weight=1) # Handlers for GUI events self.project_file_tree.bind('<<TreeviewSelect>>', self.on_file_selected) def _setup_global_file_tree(self): self.global_file_tree_frame = tk.Frame(self.content) self.global_file_tree_frame.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) self.tree_notebook.add(self.global_file_tree_frame, text='Global') self.global_file_tree = FileView(self.global_file_tree_frame, normalizer=self.filename_normalizer) self.global_file_tree.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # # The tree's vertical scrollbar self.global_file_tree_scrollbar = tk.Scrollbar(self.global_file_tree_frame, orient=tk.VERTICAL) self.global_file_tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S)) # # Tie the scrollbar to the text views, and the text views # # to each other. self.global_file_tree.config(yscrollcommand=self.global_file_tree_scrollbar.set) self.global_file_tree_scrollbar.config(command=self.global_file_tree.yview) # Setup weights for the "global_file_tree" tree self.global_file_tree_frame.columnconfigure(0, weight=1) self.global_file_tree_frame.columnconfigure(1, weight=0) self.global_file_tree_frame.rowconfigure(0, weight=1) # Handlers for GUI events self.global_file_tree.bind('<<TreeviewSelect>>', self.on_file_selected) def _setup_code_area(self): self.code_frame = tk.Frame(self.content) self.code_frame.grid(column=1, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # Label for current file self.current_file = tk.StringVar() self.current_file_label = Label(self.code_frame, textvariable=self.current_file) self.current_file_label.grid(column=0, row=0, sticky=(tk.W, tk.E)) # Code display area self.code = CodeView(self.code_frame) self.code.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) # Set up weights for the code frame's content self.code_frame.columnconfigure(0, weight=1) self.code_frame.rowconfigure(0, weight=0) self.code_frame.rowconfigure(1, weight=1) self.content.add(self.code_frame) def _setup_status_bar(self): # Status bar self.statusbar = tk.Frame(self.root) self.statusbar.grid(column=0, row=2, sticky=(tk.W, tk.E)) # Coverage summary for currently selected file. self.coverage_file_summary = tk.StringVar() self.coverage_file_summary_label = Label(self.statusbar, textvariable=self.coverage_file_summary) self.coverage_file_summary_label.grid(column=0, row=0, sticky=(tk.W, tk.E)) self.coverage_file_summary.set('No file selected') # Main window resize handle self.grip = Sizegrip(self.statusbar) self.grip.grid(column=1, row=0, sticky=(tk.S, tk.E)) # Set up weights for status bar frame self.statusbar.columnconfigure(0, weight=1) self.statusbar.columnconfigure(1, weight=0) self.statusbar.rowconfigure(0, weight=0) ###################################################### # Utility methods for controlling content ###################################################### def show_file(self, filename, line=None, breakpoints=None): """Show the content of the nominated file. If specified, line is the current line number to highlight. If the line isn't currently visible, the window will be scrolled until it is. breakpoints is a list of line numbers that have current breakpoints. If refresh is true, the file will be reloaded and redrawn. """ # Set the filename label for the current file self.current_file.set(self.filename_normalizer(filename)) # Update the code view; this means changing the displayed file # if necessary, and updating the current line. if filename != self.code.filename: self.code.filename = filename missing = self.coverage_data['missing'].get(os.path.normcase(filename), []) executed = self.coverage_data['lines'].get(os.path.normcase(filename), []) n_executed = len(executed) n_missing = len(missing) self.code.highlight_missing(missing) self.coverage_file_summary.set('%s/%s lines executed' % (n_executed, n_executed + n_missing)) self.code.line = line def load_coverage(self): "Load and display coverage data" # Store the old list of files that have coverage data. # We do this so we can identify stale data on the tree. old_files = set(self.coverage_data['lines'].keys()) old_total_coverage = self.coverage_data['total_coverage'] loaded = False retry = True while not loaded and retry: try: # Load the new coverage data cov = coverage.coverage() cov.load() # Override precision for coverage reporting. coverage.results.Numbers.set_precision(1) if cov.data.measured_files(): self.coverage_data = { 'lines': {}, 'missing': {}, } totals = coverage.results.Numbers() # Update the coverage display of every file mentioned in the file. for filename in cov.data.measured_files(): filename = os.path.normcase(filename) node = nodify(filename) dirname, basename = os.path.split(filename) # If the normalized version of the filename is the same as the # filename, then the file *isn't* under the project root. if filename == self.filename_normalizer(filename): file_tree = self.global_file_tree else: file_tree = self.project_file_tree try: # # Make sure the file exists on the tree. file_tree.insert_filename(dirname, basename) # Compute the coverage percentage analysis = cov._analyze(filename) self.coverage_data['lines'][filename] = analysis.statements self.coverage_data['missing'][filename] = analysis.missing file_coverage = analysis.numbers.pc_covered totals = totals + analysis.numbers file_tree.set(node, 'coverage', analysis.numbers.pc_covered_str) # file_tree.set(node, 'branch_coverage', str(len(lines))) # Set the color of the tree node based on coverage if file_coverage < 70.0: file_tree.item(node, tags=['file', 'code', 'bad']) elif file_coverage < 80.0: file_tree.item(node, tags=['file', 'code', 'poor']) elif file_coverage < 90.0: file_tree.item(node, tags=['file', 'code', 'ok']) elif file_coverage < 99.9: file_tree.item(node, tags=['file', 'code', 'good']) else: file_tree.item(node, tags=['file', 'code', 'perfect']) except coverage.misc.NoSource: # could mean the file was deleted after running coverage file_tree.item(node, tags=['bad']) # We've updated the file, so we know it isn't stale. try: old_files.remove(filename) except KeyError: # File wasn't loaded before; ignore this. pass # Clear out any stale coverage data for filename in old_files: node = nodify(filename) if file_tree.exists(node): file_tree.set(node, 'coverage', '') file_tree.item(node, tags=['file', 'code']) # Compute the overall coverage total_coverage = totals.pc_covered self.coverage_data['total_coverage'] = total_coverage coverage_text = u'%.1f%%' % total_coverage # Update the text with up/down arrows to reflect change if old_total_coverage is not None: if total_coverage > old_total_coverage: coverage_text = coverage_text + u' ⬆' elif total_coverage < old_total_coverage: coverage_text = coverage_text + u' ⬇' self.coverage_total_summary.set(coverage_text) # Set the color based on coverage level. if total_coverage < 70.0: self.coverage_total_summary_label.configure(foreground='red') elif total_coverage < 80.0: self.coverage_total_summary_label.configure(foreground='orange') elif total_coverage < 90.0: self.coverage_total_summary_label.configure(foreground='blue') elif total_coverage < 99.9: self.coverage_total_summary_label.configure(foreground='cyan') else: self.coverage_total_summary_label.configure(foreground='green') # Refresh the file display current_file = self.code._filename if current_file: self.code._filename = None self.show_file(current_file) loaded = True else: retry = tkMessageBox.askretrycancel( message="Couldn't find coverage data file. Have you generated coverage data? Is the .coverage in your current working directory", title='No coverage data found' ) except Exception as e: retry = tkMessageBox.askretrycancel( message="Couldn't load coverage data -- data file may be corrupted (Error was: %s)" % e, title='Problem loading coverage data' ) return loaded ###################################################### # TK Main loop ###################################################### def mainloop(self): self.root.mainloop() ###################################################### # TK Command handlers ###################################################### def cmd_quit(self): "Quit the program" self.root.quit() def cmd_refresh(self, event=None): "Refresh the coverage data" self.load_coverage() def cmd_duvet_page(self): "Show the Duvet project page" webbrowser.open_new('http://pybee.org/duvet') def cmd_duvet_github(self): "Show the Duvet GitHub repo" webbrowser.open_new('http://github.com/pybee/duvet') def cmd_duvet_docs(self): "Show the Duvet documentation" # If this is a formal release, show the docs for that # version. otherwise, just show the head docs. if len(NUM_VERSION) == 3: webbrowser.open_new('https://duvet.readthedocs.io/en/v%s/' % VERSION) else: webbrowser.open_new('https://duvet.readthedocs.io/') def cmd_beeware_page(self): "Show the BeeWare project page" webbrowser.open_new('http://pybee.org/') ###################################################### # Handlers for GUI actions ###################################################### def on_file_selected(self, event): "When a file is selected, highlight the file and line" if event.widget.selection(): filename = event.widget.selection()[0] # Display the file in the code view if os.path.isfile(filename): self.show_file(filename=filename) else: self.code.filename = None
def __init__(self, master, a_copier, a_supp, a_supp_avant_cp, original, sauvegarde, show_size): Toplevel.__init__(self, master) self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.title(_("Confirmation")) self.a_copier = a_copier self.a_supp = a_supp self.a_supp_avant_cp = a_supp_avant_cp h = max(len(a_supp), len(a_copier)) style = Style(self) style.configure("text.TFrame", background="white", relief="sunken") Label(self, text=_("Synchronization from %(original)s to %(backup)s") % { "original": original, "backup": sauvegarde }).grid(row=0, columnspan=2, padx=10, pady=10) paned = PanedWindow(self, orient='horizontal') paned.grid(row=1, columnspan=2, sticky="eswn", padx=(10, 4), pady=4) paned.columnconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.rowconfigure(1, weight=1) # --- copy frame_copie = Frame(paned) frame_copie.columnconfigure(0, weight=1) frame_copie.rowconfigure(1, weight=1) paned.add(frame_copie, weight=1) Label(frame_copie, text=_("To copy:")).grid(row=0, columnspan=2, padx=(10, 4), pady=4) f_copie = Frame(frame_copie, style="text.TFrame", borderwidth=1) f_copie.columnconfigure(0, weight=1) f_copie.rowconfigure(0, weight=1) f_copie.grid(row=1, column=0, sticky="ewsn") txt_copie = Text(f_copie, height=h, wrap="none", highlightthickness=0, relief="flat") txt_copie.grid(row=0, column=0, sticky="eswn") scrollx_copie = Scrollbar(frame_copie, orient="horizontal", command=txt_copie.xview) scrolly_copie = Scrollbar(frame_copie, orient="vertical", command=txt_copie.yview) scrollx_copie.grid(row=2, column=0, sticky="ew") scrolly_copie.grid(row=1, column=1, sticky="ns") txt_copie.configure(yscrollcommand=scrolly_copie.set, xscrollcommand=scrollx_copie.set) txt_copie.insert("1.0", "\n".join(a_copier)) txt_copie.configure(state="disabled") self._size_copy = Label(frame_copie) self._size_copy.grid(row=3, column=0) # --- deletion frame_supp = Frame(paned) frame_supp.columnconfigure(0, weight=1) frame_supp.rowconfigure(1, weight=1) paned.add(frame_supp, weight=1) Label(frame_supp, text=_("To remove:")).grid(row=0, columnspan=2, padx=(4, 10), pady=4) f_supp = Frame(frame_supp, style="text.TFrame", borderwidth=1) f_supp.columnconfigure(0, weight=1) f_supp.rowconfigure(0, weight=1) f_supp.grid(row=1, column=0, sticky="ewsn") txt_supp = Text(f_supp, height=h, wrap="none", highlightthickness=0, relief="flat") txt_supp.grid(row=0, column=0, sticky="eswn") scrollx_supp = Scrollbar(frame_supp, orient="horizontal", command=txt_supp.xview) scrolly_supp = Scrollbar(frame_supp, orient="vertical", command=txt_supp.yview) scrollx_supp.grid(row=2, column=0, sticky="ew") scrolly_supp.grid(row=1, column=1, sticky="ns") txt_supp.configure(yscrollcommand=scrolly_supp.set, xscrollcommand=scrollx_supp.set) txt_supp.insert("1.0", "\n".join(a_supp)) txt_supp.configure(state="disabled") self._size_supp = Label(frame_supp) self._size_supp.grid(row=3, column=0) Button(self, command=self.ok, text="Ok").grid(row=3, column=0, sticky="e", padx=(10, 4), pady=(4, 10)) Button(self, text=_("Cancel"), command=self.destroy).grid(row=3, column=1, sticky="w", padx=(4, 10), pady=(4, 10)) self.wait_visibility() self.grab_set() self.transient(self.master) if show_size: self.compute_size()
def __init__(self): Tk.__init__(self, className='FolderSync') self.title("FolderSync") self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.protocol("WM_DELETE_WINDOW", self.quitter) self.icon = PhotoImage(master=self, file=IM_ICON) self.iconphoto(True, self.icon) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) # --- icons self.img_about = PhotoImage(master=self, file=IM_ABOUT) self.img_open = PhotoImage(master=self, file=IM_OPEN) self.img_plus = PhotoImage(master=self, file=IM_PLUS) self.img_moins = PhotoImage(master=self, file=IM_MOINS) self.img_sync = PhotoImage(master=self, file=IM_SYNC) self.img_prev = PhotoImage(master=self, file=IM_PREV) self.img_expand = PhotoImage(master=self, file=IM_EXPAND) self.img_collapse = PhotoImage(master=self, file=IM_COLLAPSE) self.original = "" self.sauvegarde = "" # list of files / folders to delete before starting the copy because # they are not of the same type on the original and the backup self.pb_chemins = [] self.err_copie = False self.err_supp = False # --- init log files l = [f for f in listdir(PATH) if match(r"foldersync[0-9]+.pid", f)] nbs = [] for f in l: with open(join(PATH, f)) as fich: old_pid = fich.read().strip() if exists("/proc/%s" % old_pid): nbs.append(int(search(r"[0-9]+", f).group())) else: remove(join(PATH, f)) if not nbs: i = 0 else: nbs.sort() i = 0 while i in nbs: i += 1 self.pidfile = PID_FILE % i open(self.pidfile, 'w').write(str(getpid())) self.log_copie = LOG_COPIE % i self.log_supp = LOG_SUPP % i self.logger_copie = setup_logger("copie", self.log_copie) self.logger_supp = setup_logger("supp", self.log_supp) date = datetime.now().strftime('%d/%m/%Y %H:%M') self.logger_copie.info("\n### %s ###\n" % date) self.logger_supp.info("\n### %s ###\n" % date) # --- filenames and extensions that will not be copied exclude_list = split(r'(?<!\\) ', CONFIG.get("Defaults", "exclude_copie")) self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[1:]) else: self.exclude_names.append(elt.replace("\ ", " ")) # --- paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] # while "" in self.exclude_path_supp: # self.exclude_path_supp.remove("") self.q_copie = Queue() self.q_supp = Queue() # True if a copy / deletion is running self.is_running_copie = False self.is_running_supp = False self.style = Style(self) self.style.theme_use("clam") self.style.configure("TProgressbar", troughcolor='lightgray', background='#387EF5', lightcolor="#5D95F5", darkcolor="#2758AB") self.style.map("TProgressbar", lightcolor=[("disabled", "white")], darkcolor=[("disabled", "gray")]) self.style.configure("folder.TButton", padding=0) # --- menu self.menu = Menu(self, tearoff=False) self.configure(menu=self.menu) # -------- recents self.menu_recent = Menu(self.menu, tearoff=False) if RECENT: for ch_o, ch_s in RECENT: self.menu_recent.add_command( label="%s -> %s" % (ch_o, ch_s), command=lambda o=ch_o, s=ch_s: self.open(o, s)) else: self.menu.entryconfigure(0, state="disabled") # -------- favorites self.menu_fav = Menu(self.menu, tearoff=False) self.menu_fav_del = Menu(self.menu_fav, tearoff=False) self.menu_fav.add_command(label=_("Add"), image=self.img_plus, compound="left", command=self.add_fav) self.menu_fav.add_cascade(label=_("Remove"), image=self.img_moins, compound="left", menu=self.menu_fav_del) for ch_o, ch_s in FAVORIS: label = "%s -> %s" % (ch_o, ch_s) self.menu_fav.add_command( label=label, command=lambda o=ch_o, s=ch_s: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") # -------- log files menu_log = Menu(self.menu, tearoff=False) menu_log.add_command(label=_("Copy"), command=self.open_log_copie) menu_log.add_command(label=_("Removal"), command=self.open_log_suppression) # -------- settings menu_params = Menu(self.menu, tearoff=False) self.copy_links = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "copy_links")) self.show_size = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "show_size")) menu_params.add_checkbutton(label=_("Copy links"), variable=self.copy_links, command=self.toggle_copy_links) menu_params.add_checkbutton(label=_("Show total size"), variable=self.show_size, command=self.toggle_show_size) self.langue = StringVar(self, CONFIG.get("Defaults", "language")) menu_lang = Menu(menu_params, tearoff=False) menu_lang.add_radiobutton(label="English", value="en", variable=self.langue, command=self.change_language) menu_lang.add_radiobutton(label="Français", value="fr", variable=self.langue, command=self.change_language) menu_params.add_cascade(label=_("Language"), menu=menu_lang) menu_params.add_command(label=_("Exclude from copy"), command=self.exclusion_copie) menu_params.add_command(label=_("Exclude from removal"), command=self.exclusion_supp) self.menu.add_cascade(label=_("Recents"), menu=self.menu_recent) self.menu.add_cascade(label=_("Favorites"), menu=self.menu_fav) self.menu.add_cascade(label=_("Log"), menu=menu_log) self.menu.add_cascade(label=_("Settings"), menu=menu_params) self.menu.add_command(image=self.img_prev, compound="center", command=self.list_files_to_sync) self.menu.add_command(image=self.img_sync, compound="center", state="disabled", command=self.synchronise) self.menu.add_command(image=self.img_about, compound="center", command=lambda: About(self)) # --- tooltips wrapper = TooltipMenuWrapper(self.menu) wrapper.add_tooltip(4, _('Preview')) wrapper.add_tooltip(5, _('Sync')) wrapper.add_tooltip(6, _('About')) # --- path selection frame_paths = Frame(self) frame_paths.grid(row=0, sticky="ew", pady=(10, 0)) frame_paths.columnconfigure(0, weight=1) frame_paths.columnconfigure(1, weight=1) f1 = Frame(frame_paths, height=26) f2 = Frame(frame_paths, height=26) f1.grid(row=0, column=0, sticky="ew") f2.grid(row=0, column=1, sticky="ew") f1.grid_propagate(False) f2.grid_propagate(False) f1.columnconfigure(1, weight=1) f2.columnconfigure(1, weight=1) # -------- path to original Label(f1, text=_("Original")).grid(row=0, column=0, padx=(10, 4)) f11 = Frame(f1) f11.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_orig = Entry(f11) self.entry_orig.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_orig = Button(f1, image=self.img_open, style="folder.TButton", command=self.open_orig) self.b_open_orig.grid(row=0, column=2, padx=(0, 7)) # -------- path to backup Label(f2, text=_("Backup")).grid(row=0, column=0, padx=(8, 4)) f22 = Frame(f2) f22.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_sauve = Entry(f22) self.entry_sauve.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_sauve = Button(f2, image=self.img_open, width=2, style="folder.TButton", command=self.open_sauve) self.b_open_sauve.grid(row=0, column=5, padx=(0, 10)) paned = PanedWindow(self, orient='horizontal') paned.grid(row=2, sticky="eswn") paned.rowconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.columnconfigure(0, weight=1) # --- left side frame_left = Frame(paned) paned.add(frame_left, weight=1) frame_left.rowconfigure(3, weight=1) frame_left.columnconfigure(0, weight=1) # -------- files to copy f_left = Frame(frame_left) f_left.columnconfigure(2, weight=1) f_left.grid(row=2, columnspan=2, pady=(4, 2), padx=(10, 4), sticky="ew") Label(f_left, text=_("To copy")).grid(row=0, column=2) frame_copie = Frame(frame_left) frame_copie.rowconfigure(0, weight=1) frame_copie.columnconfigure(0, weight=1) frame_copie.grid(row=3, column=0, sticky="eswn", columnspan=2, pady=(2, 4), padx=(10, 4)) self.tree_copie = CheckboxTreeview(frame_copie, selectmode='none', show='tree') self.b_expand_copie = Button(f_left, image=self.img_expand, style="folder.TButton", command=self.tree_copie.expand_all) TooltipWrapper(self.b_expand_copie, text=_("Expand all")) self.b_expand_copie.grid(row=0, column=0) self.b_expand_copie.state(("disabled", )) self.b_collapse_copie = Button(f_left, image=self.img_collapse, style="folder.TButton", command=self.tree_copie.collapse_all) TooltipWrapper(self.b_collapse_copie, text=_("Collapse all")) self.b_collapse_copie.grid(row=0, column=1, padx=4) self.b_collapse_copie.state(("disabled", )) self.tree_copie.tag_configure("warning", foreground="red") self.tree_copie.tag_configure("link", font="tkDefaultFont 9 italic", foreground="blue") self.tree_copie.tag_bind("warning", "<Button-1>", self.show_warning) self.tree_copie.grid(row=0, column=0, sticky="eswn") self.scroll_y_copie = Scrollbar(frame_copie, orient="vertical", command=self.tree_copie.yview) self.scroll_y_copie.grid(row=0, column=1, sticky="ns") self.scroll_x_copie = Scrollbar(frame_copie, orient="horizontal", command=self.tree_copie.xview) self.scroll_x_copie.grid(row=1, column=0, sticky="ew") self.tree_copie.configure(yscrollcommand=self.scroll_y_copie.set, xscrollcommand=self.scroll_x_copie.set) self.pbar_copie = Progressbar(frame_left, orient="horizontal", mode="determinate") self.pbar_copie.grid(row=4, columnspan=2, sticky="ew", padx=(10, 4), pady=4) self.pbar_copie.state(("disabled", )) # --- right side frame_right = Frame(paned) paned.add(frame_right, weight=1) frame_right.rowconfigure(3, weight=1) frame_right.columnconfigure(0, weight=1) # -------- files to delete f_right = Frame(frame_right) f_right.columnconfigure(2, weight=1) f_right.grid(row=2, columnspan=2, pady=(4, 2), padx=(4, 10), sticky="ew") Label(f_right, text=_("To remove")).grid(row=0, column=2) frame_supp = Frame(frame_right) frame_supp.rowconfigure(0, weight=1) frame_supp.columnconfigure(0, weight=1) frame_supp.grid(row=3, columnspan=2, sticky="eswn", pady=(2, 4), padx=(4, 10)) self.tree_supp = CheckboxTreeview(frame_supp, selectmode='none', show='tree') self.b_expand_supp = Button(f_right, image=self.img_expand, style="folder.TButton", command=self.tree_supp.expand_all) TooltipWrapper(self.b_expand_supp, text=_("Expand all")) self.b_expand_supp.grid(row=0, column=0) self.b_expand_supp.state(("disabled", )) self.b_collapse_supp = Button(f_right, image=self.img_collapse, style="folder.TButton", command=self.tree_supp.collapse_all) TooltipWrapper(self.b_collapse_supp, text=_("Collapse all")) self.b_collapse_supp.grid(row=0, column=1, padx=4) self.b_collapse_supp.state(("disabled", )) self.tree_supp.grid(row=0, column=0, sticky="eswn") self.scroll_y_supp = Scrollbar(frame_supp, orient="vertical", command=self.tree_supp.yview) self.scroll_y_supp.grid(row=0, column=1, sticky="ns") self.scroll_x_supp = Scrollbar(frame_supp, orient="horizontal", command=self.tree_supp.xview) self.scroll_x_supp.grid(row=1, column=0, sticky="ew") self.tree_supp.configure(yscrollcommand=self.scroll_y_supp.set, xscrollcommand=self.scroll_x_supp.set) self.pbar_supp = Progressbar(frame_right, orient="horizontal", mode="determinate") self.pbar_supp.grid(row=4, columnspan=2, sticky="ew", padx=(4, 10), pady=4) self.pbar_supp.state(("disabled", )) # --- bindings self.entry_orig.bind("<Key-Return>", self.list_files_to_sync) self.entry_sauve.bind("<Key-Return>", self.list_files_to_sync)
def __init__(self): Tk.__init__(self) self.title("FolderSync") self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.protocol("WM_DELETE_WINDOW", self.quitter) self.icon = PhotoImage(master=self, file=IM_ICON) self.iconphoto(True, self.icon) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) # --- icons self.img_about = PhotoImage(master=self, file=IM_ABOUT) self.img_open = PhotoImage(master=self, file=IM_OPEN) self.img_plus = PhotoImage(master=self, file=IM_PLUS) self.img_moins = PhotoImage(master=self, file=IM_MOINS) self.img_sync = PhotoImage(master=self, file=IM_SYNC) self.img_prev = PhotoImage(master=self, file=IM_PREV) self.img_expand = PhotoImage(master=self, file=IM_EXPAND) self.img_collapse = PhotoImage(master=self, file=IM_COLLAPSE) self.original = "" self.sauvegarde = "" # liste des fichiers/dossiers à supprimer avant de lancer la copie car # ils sont de nature différente sur l'original et la sauvegarde self.pb_chemins = [] self.err_copie = False self.err_supp = False # --- init log files l = [ f for f in listdir('/tmp') if re.match(r"foldersync[0-9]+.pid", f) ] nbs = [] for f in l: with open(join('/tmp', f)) as fich: old_pid = fich.read().strip() if exists("/proc/%s" % old_pid): nbs.append(int(re.search(r"[0-9]+", f).group())) else: remove(f) if not nbs: i = 0 else: nbs.sort() i = 0 while i in nbs: i += 1 self.pidfile = PID_FILE % i open(self.pidfile, 'w').write(str(getpid())) self.log_copie = LOG_COPIE % i self.log_supp = LOG_SUPP % i self.logger_copie = setup_logger("copie", self.log_copie) self.logger_supp = setup_logger("supp", self.log_supp) date = datetime.now().strftime('%d/%m/%Y %H:%M') self.logger_copie.info("\n### %s ###\n" % date) self.logger_supp.info("\n### %s ###\n" % date) # --- filenames and extensions that will not be copied exclude_list = split(r'(?<!\\) ', CONFIG.get("Defaults", "exclude_copie")) self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[1:]) else: self.exclude_names.append(elt.replace("\ ", " ")) # --- paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] # while "" in self.exclude_path_supp: # self.exclude_path_supp.remove("") self.q_copie = Queue() self.q_supp = Queue() # True si une copie / suppression est en cours self.is_running_copie = False self.is_running_supp = False self.style = Style(self) self.style.theme_use("clam") self.style.configure("TProgressbar", troughcolor='lightgray', background='#387EF5', lightcolor="#5D95F5", darkcolor="#2758AB") self.style.map("TProgressbar", lightcolor=[("disabled", "white")], darkcolor=[("disabled", "gray")]) self.style.configure("folder.TButton", padding=1) # --- menu self.menu = Menu(self, tearoff=False) self.configure(menu=self.menu) # emplacements récents self.menu_recent = Menu(self.menu, tearoff=False) if RECENT: for ch_o, ch_s in RECENT: self.menu_recent.add_command( label="%s -> %s" % (ch_o, ch_s), command=lambda o=ch_o, s=ch_s: self.open(o, s)) else: self.menu.entryconfigure(0, state="disabled") # emplacements favoris self.menu_fav = Menu(self.menu, tearoff=False) self.menu_fav_del = Menu(self.menu_fav, tearoff=False) self.menu_fav.add_command(label="Ajouter", image=self.img_plus, compound="left", command=self.add_fav) self.menu_fav.add_cascade(label="Supprimer", image=self.img_moins, compound="left", menu=self.menu_fav_del) for ch_o, ch_s in FAVORIS: label = "%s -> %s" % (ch_o, ch_s) self.menu_fav.add_command( label=label, command=lambda o=ch_o, s=ch_s: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") # accès aux fichiers log menu_log = Menu(self.menu, tearoff=False) menu_log.add_command(label="Copie", command=self.open_log_copie) menu_log.add_command(label="Suppression", command=self.open_log_suppression) # paramètres, préférences menu_params = Menu(self.menu, tearoff=False) self.copy_links = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "copy_links")) menu_params.add_checkbutton(label="Copier les liens", variable=self.copy_links, command=self.toggle_copy_links) menu_params.add_command(label="Exclusions copie", command=self.exclusion_copie) menu_params.add_command(label="Exclusions supp", command=self.exclusion_supp) self.menu.add_cascade(label="Récents", menu=self.menu_recent) self.menu.add_cascade(label="Favoris", menu=self.menu_fav) self.menu.add_cascade(label="Log", menu=menu_log) self.menu.add_cascade(label="Paramètres", menu=menu_params) self.menu.add_command(image=self.img_prev, compound="center", command=self.list_files_to_sync) self.menu.add_command(image=self.img_sync, compound="center", state="disabled", command=self.synchronise) self.menu.add_command(image=self.img_about, compound="center", command=lambda: About(self)) # sélection chemins frame_paths = Frame(self) frame_paths.grid(row=0, sticky="ew", pady=(10, 0)) frame_paths.columnconfigure(0, weight=1) frame_paths.columnconfigure(1, weight=1) f1 = Frame(frame_paths, height=26) f2 = Frame(frame_paths, height=26) f1.grid(row=0, column=0, sticky="ew") f2.grid(row=0, column=1, sticky="ew") f1.grid_propagate(False) f2.grid_propagate(False) f1.columnconfigure(1, weight=1) f2.columnconfigure(1, weight=1) ## chemin vers original Label(f1, text="Original").grid(row=0, column=0, padx=(10, 4)) self.entry_orig = Entry(f1) self.entry_orig.grid(row=0, column=1, sticky="ew", padx=(4, 2)) self.b_open_orig = Button(f1, image=self.img_open, style="folder.TButton", command=self.open_orig) self.b_open_orig.grid(row=0, column=2, padx=(1, 8)) ## chemin vers sauvegarde Label(f2, text="Sauvegarde").grid(row=0, column=0, padx=(8, 4)) self.entry_sauve = Entry(f2) self.entry_sauve.grid(row=0, column=1, sticky="ew", padx=(4, 2)) self.b_open_sauve = Button(f2, image=self.img_open, width=2, style="folder.TButton", command=self.open_sauve) self.b_open_sauve.grid(row=0, column=5, padx=(1, 10)) # self.b_prev = Button(frame_paths, image=self.img_prev, # command=self.list_files_to_sync) # self.b_prev.grid(row=1, column=4, padx=4) # # self.b_sync = Button(frame_paths, image=self.img_sync, # command=self.synchronise) # self.b_sync.grid(row=1, column=5, padx=(4, 10)) # self.b_sync.state(("disabled", )) # self.b_prev = Button(self, image=self.img_prev, # command=self.list_files_to_sync) # self.b_prev.grid(row=1, sticky="ew", pady=(4, 10), padx=10) paned = PanedWindow(self, orient='horizontal') paned.grid(row=2, sticky="eswn") paned.rowconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.columnconfigure(0, weight=1) # --- côté gauche frame_left = Frame(paned) paned.add(frame_left, weight=1) frame_left.rowconfigure(3, weight=1) frame_left.columnconfigure(0, weight=1) # fichiers à copier f_left = Frame(frame_left) f_left.columnconfigure(2, weight=1) f_left.grid(row=2, columnspan=2, pady=(4, 2), padx=(10, 4), sticky="ew") Label(f_left, text="À copier").grid(row=0, column=2) frame_copie = Frame(frame_left) frame_copie.rowconfigure(0, weight=1) frame_copie.columnconfigure(0, weight=1) frame_copie.grid(row=3, column=0, sticky="eswn", columnspan=2, pady=(2, 4), padx=(10, 4)) self.tree_copie = CheckboxTreeview(frame_copie, selectmode='none', show='tree') self.b_expand_copie = Button(f_left, image=self.img_expand, style="folder.TButton", command=self.tree_copie.expand_all) self.b_expand_copie.grid(row=0, column=0) self.b_expand_copie.state(("disabled", )) self.b_collapse_copie = Button(f_left, image=self.img_collapse, style="folder.TButton", command=self.tree_copie.collapse_all) self.b_collapse_copie.grid(row=0, column=1, padx=4) self.b_collapse_copie.state(("disabled", )) self.tree_copie.tag_configure("warning", foreground="red") self.tree_copie.tag_configure("link", font="tkDefaultFont 9 italic", foreground="blue") self.tree_copie.tag_bind("warning", "<Button-1>", self.show_warning) self.tree_copie.grid(row=0, column=0, sticky="eswn") self.scroll_y_copie = Scrollbar(frame_copie, orient="vertical", command=self.tree_copie.yview) self.scroll_y_copie.grid(row=0, column=1, sticky="ns") self.scroll_x_copie = Scrollbar(frame_copie, orient="horizontal", command=self.tree_copie.xview) self.scroll_x_copie.grid(row=1, column=0, sticky="ew") self.tree_copie.configure(yscrollcommand=self.scroll_y_copie.set, xscrollcommand=self.scroll_x_copie.set) self.pbar_copie = Progressbar(frame_left, orient="horizontal", mode="determinate") self.pbar_copie.grid(row=4, columnspan=2, sticky="ew", padx=(10, 4), pady=4) self.pbar_copie.state(("disabled", )) # --- côté droit frame_right = Frame(paned) paned.add(frame_right, weight=1) frame_right.rowconfigure(3, weight=1) frame_right.columnconfigure(0, weight=1) # fichiers à supprimer f_right = Frame(frame_right) f_right.columnconfigure(2, weight=1) f_right.grid(row=2, columnspan=2, pady=(4, 2), padx=(4, 10), sticky="ew") Label(f_right, text="À supprimer").grid(row=0, column=2) frame_supp = Frame(frame_right) frame_supp.rowconfigure(0, weight=1) frame_supp.columnconfigure(0, weight=1) frame_supp.grid(row=3, columnspan=2, sticky="eswn", pady=(2, 4), padx=(4, 10)) self.tree_supp = CheckboxTreeview(frame_supp, selectmode='none', show='tree') self.b_expand_supp = Button(f_right, image=self.img_expand, style="folder.TButton", command=self.tree_supp.expand_all) self.b_expand_supp.grid(row=0, column=0) self.b_expand_supp.state(("disabled", )) self.b_collapse_supp = Button(f_right, image=self.img_collapse, style="folder.TButton", command=self.tree_supp.collapse_all) self.b_collapse_supp.grid(row=0, column=1, padx=4) self.b_collapse_supp.state(("disabled", )) self.tree_supp.grid(row=0, column=0, sticky="eswn") self.scroll_y_supp = Scrollbar(frame_supp, orient="vertical", command=self.tree_supp.yview) self.scroll_y_supp.grid(row=0, column=1, sticky="ns") self.scroll_x_supp = Scrollbar(frame_supp, orient="horizontal", command=self.tree_supp.xview) self.scroll_x_supp.grid(row=1, column=0, sticky="ew") self.tree_supp.configure(yscrollcommand=self.scroll_y_supp.set, xscrollcommand=self.scroll_x_supp.set) self.pbar_supp = Progressbar(frame_right, orient="horizontal", mode="determinate") self.pbar_supp.grid(row=4, columnspan=2, sticky="ew", padx=(4, 10), pady=4) self.pbar_supp.state(("disabled", )) # # lancer synchronisation # self.b_sync = Button(self, image=self.img_sync, # command=self.synchronise) # self.b_sync.grid(row=3, sticky="ew", pady=(4, 10), padx=10) # self.b_sync.state(("disabled", )) # bindings self.entry_orig.bind("<Key-Return>", self.list_files_to_sync) self.entry_sauve.bind("<Key-Return>", self.list_files_to_sync)
class MainWindow(object): def __init__(self, root, options): ''' ----------------------------------------------------- | main button toolbar | ----------------------------------------------------- | < ma | in content area > | | | | | File list | File name | | | | ----------------------------------------------------- | status bar area | ----------------------------------------------------- ''' # Obtain and expand the current working directory. if options.path: base_path = os.path.abspath(options.path) else: base_path = os.path.abspath(os.getcwd()) self.base_path = os.path.normcase(base_path) # Create a filename normalizer based on the CWD. self.filename_normalizer = filename_normalizer(self.base_path) # Set up dummy coverage data self.coverage_data = {'lines': {}, 'total_coverage': None} # Root window self.root = root self.root.title('Duvet') self.root.geometry('1024x768') # Prevent the menus from having the empty tearoff entry self.root.option_add('*tearOff', tk.FALSE) # Catch the close button self.root.protocol("WM_DELETE_WINDOW", self.cmd_quit) # Catch the "quit" event. self.root.createcommand('exit', self.cmd_quit) # Setup the menu self._setup_menubar() # Set up the main content for the window. self._setup_button_toolbar() self._setup_main_content() self._setup_status_bar() # Now configure the weights for the root frame self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=0) self.root.rowconfigure(1, weight=1) self.root.rowconfigure(2, weight=0) ###################################################### # Internal GUI layout methods. ###################################################### def _setup_menubar(self): # Menubar self.menubar = tk.Menu(self.root) # self.menu_Apple = Menu(self.menubar, name='Apple') # self.menubar.add_cascade(menu=self.menu_Apple) self.menu_file = tk.Menu(self.menubar) self.menubar.add_cascade(menu=self.menu_file, label='File') self.menu_help = tk.Menu(self.menubar) self.menubar.add_cascade(menu=self.menu_help, label='Help') # self.menu_Apple.add_command(label='Test', command=self.cmd_dummy) # self.menu_file.add_command(label='New', command=self.cmd_dummy, accelerator="Command-N") # self.menu_file.add_command(label='Close', command=self.cmd_dummy) self.menu_help.add_command(label='Open Documentation', command=self.cmd_duvet_docs) self.menu_help.add_command(label='Open Duvet project page', command=self.cmd_duvet_page) self.menu_help.add_command(label='Open Duvet on GitHub', command=self.cmd_duvet_github) self.menu_help.add_command(label='Open BeeWare project page', command=self.cmd_beeware_page) # last step - configure the menubar self.root['menu'] = self.menubar def _setup_button_toolbar(self): ''' The button toolbar runs as a horizontal area at the top of the GUI. It is a persistent GUI component ''' # Main toolbar self.toolbar = tk.Frame(self.root) self.toolbar.grid(column=0, row=0, sticky=(tk.W, tk.E)) # Buttons on the toolbar self.refresh_button = tk.Button(self.toolbar, text='Refresh', command=self.cmd_refresh) self.refresh_button.grid(column=0, row=0) # Coverage summary for currently selected file. self.coverage_total_summary = tk.StringVar() self.coverage_total_summary_label = Label( self.toolbar, textvariable=self.coverage_total_summary, anchor=tk.E, padding=(5, 0, 5, 0), font=('Helvetica', '20')) self.coverage_total_summary_label.grid(column=1, row=0, sticky=(tk.W, tk.E)) self.toolbar.columnconfigure(0, weight=0) self.toolbar.columnconfigure(1, weight=1) self.toolbar.rowconfigure(0, weight=0) def _setup_main_content(self): ''' Sets up the main content area. It is a persistent GUI component ''' # Main content area self.content = PanedWindow(self.root, orient=tk.HORIZONTAL) self.content.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) # Create the tree/control area on the left frame self._setup_left_frame() self._setup_project_file_tree() self._setup_global_file_tree() # Create the output/viewer area on the right frame self._setup_code_area() # Set up weights for the left frame's content self.content.columnconfigure(0, weight=1) self.content.rowconfigure(0, weight=1) self.content.pane(0, weight=1) self.content.pane(1, weight=4) def _setup_left_frame(self): ''' The left frame mostly consists of the tree widget ''' # The left-hand side frame on the main content area # The tabs for the two trees self.tree_notebook = Notebook(self.content, padding=(0, 5, 0, 5)) self.content.add(self.tree_notebook) def _setup_project_file_tree(self): self.project_file_tree_frame = tk.Frame(self.content) self.project_file_tree_frame.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) self.tree_notebook.add(self.project_file_tree_frame, text='Project') self.project_file_tree = FileView(self.project_file_tree_frame, normalizer=self.filename_normalizer, root=self.base_path) self.project_file_tree.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # # The tree's vertical scrollbar self.project_file_tree_scrollbar = tk.Scrollbar( self.project_file_tree_frame, orient=tk.VERTICAL) self.project_file_tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S)) # # Tie the scrollbar to the text views, and the text views # # to each other. self.project_file_tree.config( yscrollcommand=self.project_file_tree_scrollbar.set) self.project_file_tree_scrollbar.config( command=self.project_file_tree.yview) # Setup weights for the "project_file_tree" tree self.project_file_tree_frame.columnconfigure(0, weight=1) self.project_file_tree_frame.columnconfigure(1, weight=0) self.project_file_tree_frame.rowconfigure(0, weight=1) # Handlers for GUI events self.project_file_tree.bind('<<TreeviewSelect>>', self.on_file_selected) def _setup_global_file_tree(self): self.global_file_tree_frame = tk.Frame(self.content) self.global_file_tree_frame.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) self.tree_notebook.add(self.global_file_tree_frame, text='Global') self.global_file_tree = FileView(self.global_file_tree_frame, normalizer=self.filename_normalizer) self.global_file_tree.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # # The tree's vertical scrollbar self.global_file_tree_scrollbar = tk.Scrollbar( self.global_file_tree_frame, orient=tk.VERTICAL) self.global_file_tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S)) # # Tie the scrollbar to the text views, and the text views # # to each other. self.global_file_tree.config( yscrollcommand=self.global_file_tree_scrollbar.set) self.global_file_tree_scrollbar.config( command=self.global_file_tree.yview) # Setup weights for the "global_file_tree" tree self.global_file_tree_frame.columnconfigure(0, weight=1) self.global_file_tree_frame.columnconfigure(1, weight=0) self.global_file_tree_frame.rowconfigure(0, weight=1) # Handlers for GUI events self.global_file_tree.bind('<<TreeviewSelect>>', self.on_file_selected) def _setup_code_area(self): self.code_frame = tk.Frame(self.content) self.code_frame.grid(column=1, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) # Label for current file self.current_file = tk.StringVar() self.current_file_label = Label(self.code_frame, textvariable=self.current_file) self.current_file_label.grid(column=0, row=0, sticky=(tk.W, tk.E)) # Code display area self.code = CodeView(self.code_frame) self.code.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) # Set up weights for the code frame's content self.code_frame.columnconfigure(0, weight=1) self.code_frame.rowconfigure(0, weight=0) self.code_frame.rowconfigure(1, weight=1) self.content.add(self.code_frame) def _setup_status_bar(self): # Status bar self.statusbar = tk.Frame(self.root) self.statusbar.grid(column=0, row=2, sticky=(tk.W, tk.E)) # Coverage summary for currently selected file. self.coverage_file_summary = tk.StringVar() self.coverage_file_summary_label = Label( self.statusbar, textvariable=self.coverage_file_summary) self.coverage_file_summary_label.grid(column=0, row=0, sticky=(tk.W, tk.E)) self.coverage_file_summary.set('No file selected') # Main window resize handle self.grip = Sizegrip(self.statusbar) self.grip.grid(column=1, row=0, sticky=(tk.S, tk.E)) # Set up weights for status bar frame self.statusbar.columnconfigure(0, weight=1) self.statusbar.columnconfigure(1, weight=0) self.statusbar.rowconfigure(0, weight=0) ###################################################### # Utility methods for controlling content ###################################################### def show_file(self, filename, line=None, breakpoints=None): """Show the content of the nominated file. If specified, line is the current line number to highlight. If the line isn't currently visible, the window will be scrolled until it is. breakpoints is a list of line numbers that have current breakpoints. If refresh is true, the file will be reloaded and redrawn. """ # Set the filename label for the current file self.current_file.set(self.filename_normalizer(filename)) # Update the code view; this means changing the displayed file # if necessary, and updating the current line. if filename != self.code.filename: self.code.filename = filename missing = self.coverage_data['missing'].get( os.path.normcase(filename), []) executed = self.coverage_data['lines'].get( os.path.normcase(filename), []) n_executed = len(executed) n_missing = len(missing) self.code.highlight_missing(missing) self.coverage_file_summary.set( '%s/%s lines executed' % (n_executed, n_executed + n_missing)) self.code.line = line def load_coverage(self): "Load and display coverage data" # Store the old list of files that have coverage data. # We do this so we can identify stale data on the tree. old_files = set(self.coverage_data['lines'].keys()) old_total_coverage = self.coverage_data['total_coverage'] loaded = False retry = True while not loaded and retry: try: # Load the new coverage data cov = coverage.coverage() cov.load() # Override precision for coverage reporting. coverage.results.Numbers.set_precision(1) if cov.data.measured_files(): self.coverage_data = { 'lines': {}, 'missing': {}, } totals = coverage.results.Numbers() # Update the coverage display of every file mentioned in the file. for filename in cov.data.measured_files(): filename = os.path.normcase(filename) node = nodify(filename) dirname, basename = os.path.split(filename) # If the normalized version of the filename is the same as the # filename, then the file *isn't* under the project root. if filename == self.filename_normalizer(filename): file_tree = self.global_file_tree else: file_tree = self.project_file_tree try: # # Make sure the file exists on the tree. file_tree.insert_filename(dirname, basename) # Compute the coverage percentage analysis = cov._analyze(filename) self.coverage_data['lines'][ filename] = analysis.statements self.coverage_data['missing'][ filename] = analysis.missing file_coverage = analysis.numbers.pc_covered totals = totals + analysis.numbers file_tree.set(node, 'coverage', analysis.numbers.pc_covered_str) # file_tree.set(node, 'branch_coverage', str(len(lines))) # Set the color of the tree node based on coverage if file_coverage < 70.0: file_tree.item(node, tags=['file', 'code', 'bad']) elif file_coverage < 80.0: file_tree.item(node, tags=['file', 'code', 'poor']) elif file_coverage < 90.0: file_tree.item(node, tags=['file', 'code', 'ok']) elif file_coverage < 99.9: file_tree.item(node, tags=['file', 'code', 'good']) else: file_tree.item( node, tags=['file', 'code', 'perfect']) except coverage.misc.NoSource: # could mean the file was deleted after running coverage file_tree.item(node, tags=['bad']) # We've updated the file, so we know it isn't stale. try: old_files.remove(filename) except KeyError: # File wasn't loaded before; ignore this. pass # Clear out any stale coverage data for filename in old_files: node = nodify(filename) if file_tree.exists(node): file_tree.set(node, 'coverage', '') file_tree.item(node, tags=['file', 'code']) # Compute the overall coverage total_coverage = totals.pc_covered self.coverage_data['total_coverage'] = total_coverage coverage_text = u'%.1f%%' % total_coverage # Update the text with up/down arrows to reflect change if old_total_coverage is not None: if total_coverage > old_total_coverage: coverage_text = coverage_text + u' ⬆' elif total_coverage < old_total_coverage: coverage_text = coverage_text + u' ⬇' self.coverage_total_summary.set(coverage_text) # Set the color based on coverage level. if total_coverage < 70.0: self.coverage_total_summary_label.configure( foreground='red') elif total_coverage < 80.0: self.coverage_total_summary_label.configure( foreground='orange') elif total_coverage < 90.0: self.coverage_total_summary_label.configure( foreground='blue') elif total_coverage < 99.9: self.coverage_total_summary_label.configure( foreground='cyan') else: self.coverage_total_summary_label.configure( foreground='green') # Refresh the file display current_file = self.code._filename if current_file: self.code._filename = None self.show_file(current_file) loaded = True else: retry = tkMessageBox.askretrycancel( message= "Couldn't find coverage data file. Have you generated coverage data? Is the .coverage in your current working directory", title='No coverage data found') except Exception as e: retry = tkMessageBox.askretrycancel( message= "Couldn't load coverage data -- data file may be corrupted (Error was: %s)" % e, title='Problem loading coverage data') return loaded ###################################################### # TK Main loop ###################################################### def mainloop(self): self.root.mainloop() ###################################################### # TK Command handlers ###################################################### def cmd_quit(self): "Quit the program" self.root.quit() def cmd_refresh(self, event=None): "Refresh the coverage data" self.load_coverage() def cmd_duvet_page(self): "Show the Duvet project page" webbrowser.open_new('http://pybee.org/duvet') def cmd_duvet_github(self): "Show the Duvet GitHub repo" webbrowser.open_new('http://github.com/pybee/duvet') def cmd_duvet_docs(self): "Show the Duvet documentation" # If this is a formal release, show the docs for that # version. otherwise, just show the head docs. if len(NUM_VERSION) == 3: webbrowser.open_new('https://duvet.readthedocs.io/en/v%s/' % VERSION) else: webbrowser.open_new('https://duvet.readthedocs.io/') def cmd_beeware_page(self): "Show the BeeWare project page" webbrowser.open_new('http://pybee.org/') ###################################################### # Handlers for GUI actions ###################################################### def on_file_selected(self, event): "When a file is selected, highlight the file and line" if event.widget.selection(): filename = event.widget.selection()[0] # Display the file in the code view if os.path.isfile(filename): self.show_file(filename=filename) else: self.code.filename = None
def __init__(self, master, a_copier, a_supp, a_supp_avant_cp, original, sauvegarde): Toplevel.__init__(self, master) self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.title("Confirmation") self.a_copier = a_copier self.a_supp = a_supp self.a_supp_avant_cp = a_supp_avant_cp h = max(len(a_supp), len(a_copier)) style = Style(self) style.configure("text.TFrame", background="white", relief="sunken") Label(self, text="Synchronisation de %s vers %s" % (original, sauvegarde)).grid(row=0, columnspan=2, padx=10, pady=10) paned = PanedWindow(self, orient='horizontal') paned.grid(row=1, columnspan=2, sticky="eswn", padx=(10, 4), pady=4) paned.columnconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.rowconfigure(1, weight=1) frame_copie = Frame(paned) frame_copie.columnconfigure(0, weight=1) frame_copie.rowconfigure(1, weight=1) paned.add(frame_copie, weight=1) Label(frame_copie, text="À copier :").grid(row=0, columnspan=2, padx=(10, 4), pady=4) f_copie = Frame(frame_copie, style="text.TFrame", borderwidth=1) f_copie.columnconfigure(0, weight=1) f_copie.rowconfigure(0, weight=1) f_copie.grid(row=1, column=0, sticky="ewsn") txt_copie = Text(f_copie, height=h, wrap="none", highlightthickness=0, relief="flat") txt_copie.grid(row=0, column=0, sticky="eswn") scrollx_copie = Scrollbar(frame_copie, orient="horizontal", command=txt_copie.xview) scrolly_copie = Scrollbar(frame_copie, orient="vertical", command=txt_copie.yview) scrollx_copie.grid(row=2, column=0, sticky="ew") scrolly_copie.grid(row=1, column=1, sticky="ns") txt_copie.configure(yscrollcommand=scrolly_copie.set, xscrollcommand=scrollx_copie.set) txt_copie.insert("1.0", "\n".join(a_copier)) txt_copie.configure(state="disabled") frame_supp = Frame(paned) frame_supp.columnconfigure(0, weight=1) frame_supp.rowconfigure(1, weight=1) paned.add(frame_supp, weight=1) Label(frame_supp, text="À supprimer :").grid(row=0, columnspan=2, padx=(4, 10), pady=4) f_supp = Frame(frame_supp, style="text.TFrame", borderwidth=1) f_supp.columnconfigure(0, weight=1) f_supp.rowconfigure(0, weight=1) f_supp.grid(row=1, column=0, sticky="ewsn") txt_supp = Text(f_supp, height=h, wrap="none", highlightthickness=0, relief="flat") txt_supp.grid(row=0, column=0, sticky="eswn") scrollx_supp = Scrollbar(frame_supp, orient="horizontal", command=txt_supp.xview) scrolly_supp = Scrollbar(frame_supp, orient="vertical", command=txt_supp.yview) scrollx_supp.grid(row=2, column=0, sticky="ew") scrolly_supp.grid(row=1, column=1, sticky="ns") txt_supp.configure(yscrollcommand=scrolly_supp.set, xscrollcommand=scrollx_supp.set) txt_supp.insert("1.0", "\n".join(a_supp)) txt_supp.configure(state="disabled") Button(self, command=self.ok, text="Ok").grid(row=3, column=0, sticky="e", padx=(10, 4), pady=(4, 10)) Button(self, text="Annuler", command=self.destroy).grid(row=3, column=1, sticky="w", padx=(4, 10), pady=(4, 10)) self.grab_set()