def end_of_file_shutdown(self): """ Reached End of file - close ancestry file handler, display Completion message to user and call shutdown() """ TKHelper.disable_buttons(button_list=self.w.review_buttons) # self.start_time = time.time() # self.logger.debug(f'COMPLETED time={int((time.time() - self.start_time) / 60)} minutes') self.w.status.text = "Done. Shutting Down..." self.w.original_entry.text = " " path = self.cfg.get("gedcom_path") self.ancestry_file_handler.close() self.update_statistics() self.w.root.update_idletasks() # Let GUI update if 'ramp' in self.out_suffix: # Gramps file is .csv messagebox.showinfo( "Info", f"Finished. Created file for Import to Ancestry software:\n\n {path}.csv" ) self.logger.info(f'DONE. Created output file {path}.csv') else: messagebox.showinfo( "Info", f"Finished. Created file for Import to Ancestry software:\n\n {path}.{self.out_suffix}" ) self.logger.info( f'DONE. Created output file {path}{self.out_suffix}') self.logger.info('End of file') self.shutdown()
def load_handler(self): """ The User pressed the LOAD button to load and review an Ancestry file. Switch app display to the Review layout Load in file name and call handle_place_entry() """ self.w.original_entry.text = "" self.w.remove_initialization_widgets() # Remove old widgets self.w.create_review_widgets( ) # Switch display from Initial widgets to review widgets self.load_data_files() ged_path = self.cfg.get( "gedcom_path") # Get saved config setting for file # Load appropriate ancestry file handler based on file type (Gramps XML or GEDCOM) if ged_path is not None: if '.ged' in ged_path: # GEDCOM self.out_suffix = "import.ged" self.ancestry_file_handler = Gedcom.Gedcom( in_path=ged_path, out_suffix=temp_suffix, cache_d=self.cache_dir, progress=None) # Routines to open and parse GEDCOM file elif '.gramps' in ged_path: # GRAMPS self.out_suffix = "import.gramps" # self.out_suffix = "csv" self.ancestry_file_handler = GrampsXml.GrampsXml( in_path=ged_path, out_suffix=temp_suffix, cache_d=self.cache_dir, progress=None, geodata=self.geodata ) # Routines to open and parse Gramps file else: self.out_suffix = 'unk.new.ged' messagebox.showwarning( f'UNKNOWN File type. Not .gramps and not .ged. \n\n{ged_path}') self.out_diag_file = open(ged_path + '.output.txt', 'wt') self.in_diag_file = open(ged_path + '.input.txt', 'wt') miss_diag_fname = ged_path + '.miss.txt' self.geodata.open_diag_file(miss_diag_fname) if self.ancestry_file_handler.error: TKHelper.fatal_error(f"File {ged_path} not found.") self.w.root.update() self.place: Loc.Loc = Loc.Loc( ) # Create an object to store info for the current Place # Add filename to Title path_parts = os.path.split(ged_path) # Extract filename from full path self.w.title.text = f'GEO FINDER v{__version__.__version__} - {path_parts[1]}' # Read file, find each place entry and handle it. self.w.user_entry.text = "Scanning to previous position..." self.process_place_entries()
def display_country_note(self) -> int: """ Display warning if only a small number of countries are enabled #Returns: Number of supported countries """ country_list, supported_countries = self.geodata.geo_files.get_supported_countries( ) self.w.root.update() if supported_countries == 0: TKHelper.fatal_error( "No countries enabled.\n\nUse Config Country Tab to change country list\n" ) return supported_countries
def filename_handler(self): """ Display file open selector dialog """ fname = filedialog.askopenfilename(initialdir=self.directory, title=f"Select {file_types} file", filetypes=[ ("GEDCOM files", "*.ged"), ("Gramps files", "*.gramps"), ("all files", "*.*") ]) if len(fname) > 1: self.cfg.set("gedcom_path", fname) # Add filename to dict self.cfg.write() # Write out config file self.w.status.text = f"Click Open to load {file_types} file" self.w.original_entry.text = self.cfg.get("gedcom_path") TKHelper.set_preferred_button(self.w.load_button, self.w.initialization_buttons, "Preferred.TButton")
def quit_handler(self): """ User clicked Quit. Set flag for shutdown. Process all global replaces and exit """ self.skip_count += 1 path = self.cfg.get("gedcom_path") if self.w.prog.shutdown_requested: # Already in shutdown and user quit again - do immediate shutdown self.logger.info('Shutdown') self.shutdown() self.w.prog.shutdown_requested = True if messagebox.askyesno( 'Generate Import File?', f'All updates saved.\n\nDo you want to generate a file for import' f' to Gedcom/Gramps?\n\n', default='no'): # Write file for importing back messagebox.showinfo( "Generate Import File", "Reminder - make sure the ancestry export file you are working on is up to date before " "generating a file to import back!\n\nThe file will take about 10 minutes per 1000 places" ) TKHelper.disable_buttons(button_list=self.w.review_buttons) self.w.quit_button.config(state="disabled") self.w.prog.full_update = True self.w.statistics_text.configure(style="Good.TLabel") self.w.user_entry.text = "Creating Import File..." self.start_time = time.time() # Write out the item we are on self.ancestry_file_handler.write_asis(self.w.original_entry.text) # We will still continue to go through file, but only handle global replaces self.process_place_entries() else: # Immediate exit self.logger.info('Shutdown') self.shutdown()
def __init__(self, frame, title, dir_name, cache_filename): self.logger = logging.getLogger(__name__) # Add these in addition to the standard widgets we inherit from ListBoxFrame self.add_button = ttk.Button(frame, text="add", command=self.add_handler, width=UtilListboxFrame.BUTTON_WIDTH) self.add_label = Widge.CLabel(frame, text="Enter replacements below and click on Add", style='Info.TLabel') self.add_label2 = Widge.CLabel(frame, text="Original:", style='Info.TLabel') self.add_entry: Widge.CEntry = Widge.CEntry(frame, text=" orig ", width=15) # , style='Info.TLabel') self.add_replace: Widge.CEntry = Widge.CEntry(frame, text=" rr ", width=15) # , style='Info.TLabel') self.add_label3 = Widge.CLabel(frame, text="Replacement:", style='Info.TLabel') super().__init__(frame, title, dir_name, cache_filename) self.tree.heading("#0", text="Original", anchor=tk.W) self.tree.heading("pre", text="Replacement", anchor=tk.W) # If dictionary is empty, load in defaults if len(self.dict) == 0: self.logger.error('Output filter list is empty. loading defaults') self.set_default(default) self.load_defaults() super().add_handler()
def __init__(self, frame, title, dir_name, cache_filename): self.logger = logging.getLogger(__name__) # Add these widgets in addition to the standard widgets we inherit from ListBoxFrame self.add_button = ttk.Button(frame, text="add", command=self.add_handler, width=UtilListboxFrame.BUTTON_WIDTH) self.add_label = TkHelp.CLabel( frame, text="Enter Geoname Feature below and click on Add button to add", style='Info.TLabel') self.add_entry: TkHelp.CEntry = TkHelp.CEntry( frame, text=" ", width=15) # , style='Info.TLabel') super().__init__(frame, title, dir_name, cache_filename) # If dictionary is empty, load in defaults if len(self.dict) == 0: self.logger.error('Feature list is empty. loading defaults') self.set_default(default) self.load_defaults() super().add_handler()
def __init__(self, frame, title: str, dir_name: str, cache_filename: str): # Initialize GEO database self.geodb = GeoDB.GeoDB(db_path=os.path.join(dir_name, 'geodata.db'), spellcheck=None, show_message=True, exit_on_error=True, set_speed_pragmas=True, db_limit=150) # Read in dictionary listing output text replacements self.output_replace_cd = CachedDictionary.CachedDictionary(dir_name, "output_list.pkl") self.output_replace_cd.read() self.output_replace_dct: Dict[str, str] = self.output_replace_cd.dict # Add these widgets in addition to the standard widgets we inherit from ListBoxFrame self.update_button = ttk.Button(frame, text="update", command=self.update_handler, width=UtilListboxFrame.BUTTON_WIDTH) self.update_label = TkHelp.CLabel(frame, text="Edit prefix below and click Update", style='Info.TLabel') self.edit_entry: TkHelp.CEntry = TkHelp.CEntry(frame, text=" ", width=55) # , style='Info.TLabel') super().__init__(frame, title, dir_name, cache_filename) self.tree.heading("#0", text="Original", anchor=tk.W) self.tree.heading("pre", text="Replacement", anchor=tk.W) self.status.text = "Click to select items above. Then click Remove to remove item, or edit prefix below." self.key = '' self.val = '' self.item = None
def create_initialization_widgets(self): """ Create the widgets for display during initialization (File open) """ self.pad: TKHelper.CLabel = TKHelper.CLabel(self.root, text=" ", width=2, style='Light.TLabel') self.title: TKHelper.CLabel = TKHelper.CLabel(self.root, text="GEO FINDER", width=30, style='Large.TLabel') self.original_entry: TKHelper.CLabel = TKHelper.CLabel(self.root, text=" ", width=50, style='Info.TLabel') self.status: TKHelper.CLabel = TKHelper.CLabel(self.root, width=GFStyle.TXT_WID, style='Good.TLabel') self.prog: TKHelper.Progress = TKHelper.Progress(self.root, bar_color=GFStyle.HIGH_COLOR, trough_color=GFStyle.LT_GRAY, status=self.status, length=400) self.quit_button: ttk.Button = ttk.Button(self.root, text="quit", command=self.main.shutdown, width=GFStyle.BTN_WID_WD, image=self.images['exit'], compound="left") self.load_button: ttk.Button = ttk.Button(self.root, text="open", command=self.main.load_handler, width=GFStyle.BTN_WID_WD, style='Preferred.TButton', image=self.images['play'], compound="left") self.choose_button: ttk.Button = ttk.Button(self.root, text="choose", command=self.main.filename_handler, width=GFStyle.BTN_WID_WD, image=self.images['folder'], compound="left") self.config_button: ttk.Button = ttk.Button(self.root, text="config", command=self.main.config_handler, width=GFStyle.BTN_WID_WD, image=self.images['play'], compound="left") # Set grid layout for padding column widget - just pads out left column self.pad.grid(column=PAD_COL, row=0, padx=GFStyle.PAD_PADX, pady=0, sticky="EW") # Set grid for text widgets self.original_entry.grid(column=TXT_COL, row=2, padx=7, pady=5, sticky="EW") self.status.grid(column=TXT_COL, row=4, padx=7, pady=5, sticky="EW") self.title.grid(column=TXT_COL, row=0, padx=7, pady=12, sticky="") self.prog.bar.grid(column=TXT_COL, row=1, padx=7, pady=5, sticky="EW") # Set grid for button widgets self.load_button.grid(column=SCRL_COL, row=2, padx=20, pady=6, sticky="") self.choose_button.grid(column=SCRL_COL, row=3, padx=20, pady=6, sticky="") self.config_button.grid(column=SCRL_COL, row=4, padx=20, pady=6, sticky="") self.quit_button.grid(column=SCRL_COL, row=9, padx=20, pady=6, sticky="S") Tooltip.Tooltip(self.root, self.load_button, text="Open GEDCOM file") Tooltip.Tooltip(self.root, self.choose_button, text="Choose GEDCOM file") self.root.update() self.initialization_buttons: List[ttk.Button] = [self.quit_button, self.load_button, self.choose_button, self.config_button] # disable all buttons and the app will enable appropriate ones TKHelper.disable_buttons(button_list=self.initialization_buttons) self.config_button.config(state="normal")
def validate_directories(self): if not os.path.exists(self.cache_dir): # Create directories for GeoFinder if messagebox.askyesno( 'Geoname Data Cache Folder not found', f'Create Geoname Cache folder?\n\n{self.cache_dir} '): err = self.cfg.create_directories() if not os.path.exists(self.cache_dir): messagebox.showwarning( 'Geoname Data Cache Folder not found', f'Unable to create folder\n\n{self.cache_dir} ') self.shutdown() else: self.logger.debug(f'Created {self.cache_dir}') messagebox.showinfo( 'Geoname Data Cache Folder created', f'Created folder\n\n{self.cache_dir} ') else: self.shutdown() # Ensure GeoFinder directory structure is valid if self.cfg.valid_directories(): # Directories are valid. See if required Geonames files are present err = self.check_configuration() if err: # Missing files self.logger.warning('Missing files') self.w.status.text = "Click Config to set up Geo Finder" TKHelper.set_preferred_button(self.w.config_button, self.w.initialization_buttons, "Preferred.TButton") self.w.load_button.config(state="disabled") else: # No config errors # Read config settings (Ancestry file path) err = self.cfg.read() if err: self.logger.warning('error reading {} config.pkl'.format( self.cache_dir)) self.w.original_entry.text = self.cfg.get("gedcom_path") TKHelper.enable_buttons(self.w.initialization_buttons) if os.path.exists(self.cfg.get("gedcom_path")): # file is valid. Prompt user to click Open for file self.w.status.text = f"Click Open to load {file_types} file" TKHelper.set_preferred_button( self.w.load_button, self.w.initialization_buttons, "Preferred.TButton") else: # No file. prompt user to select a file - GEDCOM file name isn't valid self.w.status.text = f"Choose a {file_types} file" self.w.load_button.config(state="disabled") TKHelper.set_preferred_button( self.w.choose_button, self.w.initialization_buttons, "Preferred.TButton") else: # Missing directories self.logger.warning('Directories not found: {} '.format( self.cache_dir)) self.w.status.text = "Click Config to set up Geo Finder" self.w.load_button.config(state="disabled") TKHelper.set_preferred_button(self.w.config_button, self.w.initialization_buttons, "Preferred.TButton")
def display_result(self, place: Loc.Loc): """ Display result details for a town entry Enable buttons so user can either click Skip, or edit the item and Click Verify. Args: place: Returns: None """ place.set_types_as_string() place.status = f'{place.result_type_text} {self.result_text_list.get(place.result_type)} ' TKHelper.enable_buttons(self.w.review_buttons) # Enable action buttons based on type of result if place.result_type == GeoUtil.Result.MULTIPLE_MATCHES or \ place.result_type == GeoUtil.Result.NO_MATCH or \ place.result_type == GeoUtil.Result.NO_COUNTRY: # Result requires user action # Disable the Save & Map button until an item is found. Set Verify as preferred button self.set_save_button_allowed(False) TKHelper.set_preferred_button(self.w.verify_button, self.w.review_buttons, "Preferred.TButton") elif place.result_type == GeoUtil.Result.NOT_SUPPORTED: # Country Not supported - set Skip as preferred button TKHelper.set_preferred_button(self.w.skip_button, self.w.review_buttons, "Preferred.TButton") else: # Found a match - enable save. Set Save as preferred button self.set_save_button_allowed(True) # Enable save button TKHelper.set_preferred_button(self.w.save_button, self.w.review_buttons, "Preferred.TButton") # Display status and color based on success self.set_status_text(place.get_status()) if place.result_type in GeoUtil.successful_match: if place.place_type == Loc.PlaceType.CITY: self.w.status.configure(style="Good.TLabel") else: self.w.status.configure(style="GoodCounty.TLabel") else: self.w.status.configure(style="Error.TLabel") # If more than one result, set Verify as preferred button # TODO - clean this up so we don't have multiple mechanisms to set status if len(place.georow_list) > 1: TKHelper.set_preferred_button(self.w.verify_button, self.w.review_buttons, "Preferred.TButton") self.set_save_button_allowed(False) if len(place.georow_list) > 0: # Display matches in listbox self.w.tree.focus() # Set focus to listbox self.display_georow_list(place) else: # No matches self.w.user_entry.focus() # Set focus to text edit widget self.display_one_georow(place.status_detail, place.geoid, score=9999, feat='') # Display GEDCOM person and event that this location refers to self.w.ged_event_info.text = f'{self.ancestry_file_handler.get_name(self.ancestry_file_handler.id)}: ' \ f'{self.ancestry_file_handler.event_name} {self.ancestry_file_handler.date}' self.w.root.update_idletasks()
def load_data_files(self) -> bool: """ Load in data files required for GeoFinder: Load global_replace dictionary, Geodata files and geonames #Returns: Error - True if error occurred """ # Read in Skiplist, Replace list self.skiplist = CachedDictionary.CachedDictionary( self.cache_dir, "skiplist.pkl") self.skiplist.read() self.global_replace = CachedDictionary.CachedDictionary( self.cache_dir, "global_replace.pkl") self.global_replace.read() dict_copy = copy.copy(self.global_replace.dict) # Convert all global_replace items to lowercase for ky in dict_copy: val = self.global_replace.dict.pop(ky) new_key = Normalize.normalize(text=ky, remove_commas=False) self.global_replace.dict[new_key] = val # Read in dictionary listing Geoname features we should include self.feature_code_list_cd = CachedDictionary.CachedDictionary( self.cache_dir, "feature_list.pkl") self.feature_code_list_cd.read() feature_code_list_dct: Dict[str, str] = self.feature_code_list_cd.dict if len(feature_code_list_dct) < 3: self.logger.warning('Feature list is empty.') feature_code_list_dct.clear() feature_list = UtilFeatureFrame.default for feat in feature_list: feature_code_list_dct[feat] = '' self.feature_code_list_cd.write() # Read in dictionary containing countries (ISO2) we should include self.supported_countries_cd = CachedDictionary.CachedDictionary( self.cache_dir, "country_list.pkl") self.supported_countries_cd.read() supported_countries_dct: Dict[str, str] = self.supported_countries_cd.dict # Read in dictionary containing languages (ISO2) we should include self.languages_list_cd = CachedDictionary.CachedDictionary( self.cache_dir, "languages_list.pkl") self.languages_list_cd.read() languages_list_dct: Dict[str, str] = self.languages_list_cd.dict # Initialize geo data self.geodata = Geodata(directory_name=self.directory, progress_bar=self.w.prog, enable_spell_checker=self.enable_spell_checker, show_message=True, exit_on_error=True, languages_list_dct=languages_list_dct, feature_code_list_dct=feature_code_list_dct, supported_countries_dct=supported_countries_dct) # If the list of supported countries is unusually short, display note to user num = self.display_country_note() self.logger.info('{} countries will be loaded'.format(num)) # open Geoname Gazeteer DB - city names, lat/long, etc. error = self.geodata.open() if error: TKHelper.fatal_error(MISSING_FILES) self.w.root.update() self.w.prog.update_progress(100, " ") return error
def __init__(self, frame, title, dir_name, cache_filename, error): self.logger = logging.getLogger(__name__) self.file_error = True self.title = title self.frame = frame self.separator = ":" self.dirty_flag = False # Flag to track if data was modified self.error = error # Load in list from cache file self.directory = dir_name self.cache_dir = GeoUtil.get_cache_directory(dir_name) self.logger.debug( f'SetupStatusFrame dir={dir_name} sub_dir={self.cache_dir} file={cache_filename}' ) self.cache = CachedDictionary.CachedDictionary(self.cache_dir, cache_filename) self.cache.read() self.error_dict = {} # Keep a dictionary of errors self.supported_countries_cd = CachedDictionary.CachedDictionary( self.cache_dir, "country_list.pkl") self.supported_countries_cd.read() self.supported_countries_dct: Dict[ str, str] = self.supported_countries_cd.dict self.logger.debug( f'country list len={len(self.supported_countries_dct)}') self.grd = { "title_label": [0, 0, 5, 5, "W"], "scrollbar": [1, 2, 0, 5, "WNS"], "status": [0, 1, 5, 5, "W"], "add_button": [2, 4, 5, 5, "W"], "listbox": [0, 2, 5, 5, "E"], "unused": [2, 3, 5, 5, "W"], "add_entry": [0, 4, 5, 5, "W"], "load_button": [2, 1, 5, 5, "W"], "geoname_button": [2, 1, 5, 5, "E"], "add_label": [0, 3, 5, 5, "EW"] } self.title_label = Widge.CLabel(frame, text=self.title, width=80, style='Info.TLabel') self.status = Widge.CLabel(frame, text=" ", width=80, style='Highlight.TLabel') self.scrollbar = Scrollbar(frame) self.listbox = Listbox(frame, width=80, height=20, bg=AppStyle.LT_GRAY, selectmode=MULTIPLE, yscrollcommand=self.scrollbar.set) self.add_button = ttk.Button(frame, text="geonames.org", command=self.web_handler, width=12) # Configure buttons and widgets self.configure_widgets() #self.frame.columnconfigure(0, weight=5) #self.frame.columnconfigure(2, weight=2) #self.frame.rowconfigure(0, weight=2) #self.frame.rowconfigure(1, weight=2) # Display data self.load_handler()
def create_review_widgets(self): """ Create all the buttons and entry fields for normal running """ self.root.protocol("WM_DELETE_WINDOW", self.main.quit_handler) # Handle close window event self.pad: TKHelper.CLabel = TKHelper.CLabel(self.root, text=" ", width=2, style='Light.TLabel') self.title: TKHelper.CLabel = TKHelper.CLabel(self.root, text="GEO FINDER", width=40, style='Large.TLabel') self.original_entry: TKHelper.CLabel = TKHelper.CLabel(self.root, text=" ", width=GFStyle.TXT_WID, style='Light.TLabel') self.user_entry: TKHelper.CEntry = TKHelper.CEntry(self.root, text=" ", width=GFStyle.TXT_WID, font=(GFStyle.FNT_NAME, 14)) self.status: TKHelper.CLabel = TKHelper.CLabel(self.root, width=GFStyle.TXT_WID, style='Good.TLabel') self.prefix: TKHelper.CLabel = TKHelper.CLabel(self.root, width=GFStyle.TXT_WID, style='Highlight.TLabel') # User fix statistics self.statistics_text: TKHelper.CLabel = TKHelper.CLabel(self.root, text="", width=GFStyle.TXT_WID, style='Light.TLabel') # Treeview (list box) self.tree_scrollbar = ttk.Scrollbar(self.root) self.tree = TKHelper.CTreeView(root=self.root, styl="Plain.Treeview", mode="browse", odd_background=GFStyle.ODD_ROW_COLOR, even_background='white') self.tree["columns"] = ("pre","id","score", "feat") self.tree["displaycolumns"] = ("pre","score", "feat") self.tree.column("#0", width=300, minwidth=80, stretch=tk.YES, anchor=tk.E) self.tree.heading("#0", text=" Location", anchor=tk.W) self.tree.column("pre", width=90, minwidth=5, stretch=tk.YES, anchor=tk.E) self.tree.heading("pre", text=" Prefix", anchor=tk.E) self.tree.column("id", width=0, minwidth=0, stretch=tk.NO, anchor=tk.E) self.tree.heading("id", text=" ID", anchor=tk.E) self.tree.column("score", width=70, minwidth=0, stretch=tk.NO, anchor=tk.E) self.tree.heading("score", text=" Score", anchor=tk.E) self.tree.column("feat", width=60, minwidth=0, stretch=tk.NO, anchor=tk.E) self.tree.heading("feat", text=" Type", anchor=tk.E) self.tree.config(yscrollcommand=self.tree_scrollbar.set) self.tree_scrollbar.config(command=self.tree.yview) self.tree.bind("<Double-1>", self.main.doubleclick_handler) self.ged_event_info: TKHelper.CLabel = TKHelper.CLabel(self.root, text=" ", width=GFStyle.TXT_WID, style='Light.TLabel') self.footnote: TKHelper.CLabel = TKHelper.CLabel(self.root, text="Data is from GeoNames.org.", width=GFStyle.TXT_WID, style='Light.TLabel') self.prog: TKHelper.Progress = TKHelper.Progress(self.root, bar_color=GFStyle.HIGH_COLOR, trough_color=GFStyle.LT_GRAY, status=self.status, length=400) self.search_button: ttk.Button = ttk.Button(self.root, text="search", command=self.main.search_handler, width=GFStyle.BTN_WID, image=self.images['search'], compound="left") self.map_button: ttk.Button = ttk.Button(self.root, text="map", command=self.main.map_handler, width=GFStyle.BTN_WID, image=self.images['map'], compound="left") self.verify_button: ttk.Button = ttk.Button(self.root, text="verify", command=self.main.verify_handler, width=GFStyle.BTN_WID, image=self.images['verify'], compound="left") self.save_button: ttk.Button = ttk.Button(self.root, text="save", command=self.main.save_handler, width=GFStyle.BTN_WID, image=self.images['save'], compound="left") self.skip_button: ttk.Button = ttk.Button(self.root, text="skip", command=self.main.skip_handler, width=GFStyle.BTN_WID, image=self.images['skip'], compound="left") self.help_button: ttk.Button = ttk.Button(self.root, text="help", command=self.main.help_handler, width=GFStyle.BTN_WID, image=self.images['help'], compound="left") self.quit_button: ttk.Button = ttk.Button(self.root, text=" quit", command=self.main.quit_handler, width=GFStyle.BTN_WID, image=self.images['exit'], compound="left") # Set grid layout for padding column widget - just pads out left column self.pad.grid(column=PAD_COL, row=0, padx=GFStyle.PAD_PADX, pady=0, sticky="EW") # Set grid layout for column 0 (TXT_COL) Widgets # The first 8 are set span to 2 columns because the listbox has a scrollbar next to it self.title.grid(column=TXT_COL, row=0, padx=0, pady=12, sticky="N", columnspan=2) self.prog.bar.grid(column=TXT_COL, row=1, padx=0, pady=5, sticky="EW", columnspan=2) self.original_entry.grid(column=TXT_COL, row=2, padx=0, pady=5, sticky="EWS", columnspan=2) self.user_entry.grid(column=TXT_COL, row=3, padx=0, pady=0, sticky="EWN", columnspan=2) self.status.grid(column=TXT_COL, row=4, padx=0, pady=0, sticky="EW", columnspan=2) self.tree.grid(column=TXT_COL, row=5, padx=0, pady=5, sticky="EW") self.ged_event_info.grid(column=TXT_COL, row=7, padx=0, pady=6, sticky="W", columnspan=2) self.footnote.grid(column=TXT_COL, row=12, padx=0, pady=5, sticky="EW", columnspan=2) self.statistics_text.grid(column=TXT_COL, row=11, padx=0, pady=5, sticky='EW', columnspan=2) # Column 1 - just the scrollbar self.tree_scrollbar.grid(column=SCRL_COL, row=5, padx=0, pady=5, sticky='WNS') # Column 2 Widgets self.map_button.grid(column=BTN_COL, row=0, padx=GFStyle.BTN_PADX, pady=6, sticky="E") self.search_button.grid(column=BTN_COL, row=1, padx=GFStyle.BTN_PADX, pady=6, sticky="E") self.verify_button.grid(column=BTN_COL, row=2, padx=GFStyle.BTN_PADX, pady=6, sticky="E") self.save_button.grid(column=BTN_COL, row=4, padx=GFStyle.BTN_PADX, pady=6, sticky="E") self.skip_button.grid(column=BTN_COL, row=10, padx=GFStyle.BTN_PADX, pady=6, sticky="E") self.help_button.grid(column=BTN_COL, row=11, padx=GFStyle.BTN_PADX, pady=5, sticky="E") self.quit_button.grid(column=BTN_COL, row=12, padx=GFStyle.BTN_PADX, pady=6, sticky="SE") # Set accelerator keys for Verify, listbox, and Save self.user_entry.bind("<Return>", self.main.event_handler_for_return_key) self.user_entry.bind("<Control-s>", self.main.ctl_s_event_handler) # Track whether user is in Edit box or list box self.user_entry.bind("<FocusIn>", self.main.entry_focus_event_handler) self.tree.bind("<FocusIn>", self.main.list_focus_event_handler) # Tooltips footnote_text = 'This uses data from GeoNames.org. This work is licensed under a Creative Commons Attribution 4.0 License,\ see https://creativecommons.org/licenses/by/4.0/ The Data is provided "as is" without warranty or any representation of accuracy, \ timeliness or completeness.' Tooltip.Tooltip(self.root, self.footnote, text=footnote_text) Tooltip.Tooltip(self.root, self.ged_event_info, text="Person and Event in GEDCOM") Tooltip.Tooltip(self.root, self.original_entry, text="Original GEDCOM entry") Tooltip.Tooltip(self.root, self.verify_button, text="Verify new entry") Tooltip.Tooltip(self.root, self.search_button, text="Bring up search in browser") Tooltip.Tooltip(self.root, self.map_button, text="Bring up map in browser") Tooltip.Tooltip(self.root, self.save_button, text="Save this replacement") Tooltip.Tooltip(self.root, self.skip_button, text="Ignore this error and write out unmodified") self.review_buttons: List[ttk.Button] = [self.save_button, self.search_button, self.verify_button, self.skip_button, self.map_button, self.help_button] self.root.update()