示例#1
0
    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()
示例#2
0
    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()
示例#3
0
 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
示例#4
0
 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")
示例#5
0
    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()
示例#6
0
    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()
示例#7
0
    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()
示例#8
0
    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
示例#9
0
    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")
示例#10
0
    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")
示例#11
0
    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()
示例#12
0
    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
示例#13
0
    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()
示例#14
0
    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()