class LoadPanel( BoxLayout): ''' high-level load panel, contains FileSection widgets and big load button ''' file_sections_box = ObjectProperty(None) load_btn = ObjectProperty(None) opts_btn = ObjectProperty(None) def __init__( self, **kwargs): super( LoadPanel, self).__init__( **kwargs) self.file_section1 = FileSection( text='Populate to:', default_dataset_name='File1') self.file_section2 = FileSection( text='Populate from:', default_dataset_name='File2') self.file_sections_box.add_widget( self.file_section1) self.file_sections_box.add_widget( self.file_section2) self.load_btn.bind( on_release=self.load_callback) self.opts_btn.bind( on_release=self.opts_callback) # set focus to the first textinput self.file_section1.set_focus() def opts_callback( self, *args): App.get_running_app().nav_to( 'searchconfig_screen', 'right') def show_help( self): help_text = '- In SQL terminology, this software implements a "left join" on one or more inexact keys.\n\n- Use the [b]"Browse"[/b] buttons to select the two spreadsheets you want to match. When prompted, give each sheet a name.\n\n- The [b]"Populate to"[/b] sheet is the one you want to populate with matches. In SQL terminology it is the "left" table. The match output will contain one (or optionally more) match result for each row in this sheet. [b]This is typically the sheet with the fewest rows.[/b]\n\n- The [b]"Populate from"[/b] sheet should contain the potential matches for the rows in the first sheet. In SQL terminology this is the "right" table. [u]Note:[/u] not every row in this sheet will necessarily populate into the first sheet, and rows from this sheet can populate more than once into the first one.\n' self._popup = HelpMsg( help_text, title='Help', size_hint=(0.92,0.92)) self._popup.open() def show_about( self): about_text = 'This GUI and related code is written by Steve Suway. The GUI is built using Kivy. Matching computations are performed using string_grouper, which is written by Chris van den Berg. string_grouper\'s matching functionality builds upon sparse_dot_topn, which is an open-source project by ING Bank. ASCII transliteration is done using Unidecode, written by Tomaz Solc. Other packages this code relies on include NumPy, SciPy, and pandas.' self._popup = HelpMsg( about_text, title='About', size_hint=(0.75,0.60)) self._popup.open() def load_callback( self, btn=None): file1 = self.file_section1.get_path() file2 = self.file_section2.get_path() if (file1 == '') | (file2 == ''): return self._popup = Popup( size=('425dp','150dp'), size_hint=(None,None), auto_dismiss=False, title='Please wait') self._popup.content = Label( text='Loading files... this can take a long time for large files, and \nthis window will stop responding until finished.') self._popup.open() # wait for popup to actually open before starting to load Clock.schedule_once( self.do_load, 0.1) def do_load( self, obj): app = App.get_running_app() app.backend.labels[0] = self.file_section1.dataset_name app.backend.labels[1] = self.file_section2.dataset_name if app.backend.labels[0] == app.backend.labels[1]: app.backend.labels[1] += '(2)' file1 = self.file_section1.get_path() file2 = self.file_section2.get_path() sep1 = self.file_section1.sep sep2 = self.file_section2.sep encoding1 = self.file_section1.encoding encoding2 = self.file_section2.encoding load_successful = app.backend.init_fast_match( file1, file2, sep1, sep2, encoding1, encoding2) self._popup.dismiss() if load_successful: app.panels['narrowby_screen'].populate_dropdowns() app.panels['alsocompare_screen'].reset_panel() app.panels['append_screen'].populate() app.nav_to( 'narrowby_screen', 'left') else: error_type = app.backend.grouper_helper.error_type error_msg = app.backend.grouper_helper.error_msg if app.backend.grouper_helper.file1_load_successful: problem_file = app.backend.labels[1] else: problem_file = app.backend.labels[0] errtxt = 'Error loading {}.\n\n{}: {}'.format(problem_file, error_type, error_msg) if error_type == 'UnicodeDecodeError': errtxt += '\n\nThis probably means you selected the wrong encoding. Try re-loading your plaintext file and select a different encoding when prompted.' self._popup = ErrorMsg( error_text=errtxt) self._popup.open()
class ExportMatchesPanel(BoxLayout): check_clip = ObjectProperty(None) check_file = ObjectProperty(None) check_space = ObjectProperty(None) check_sort = ObjectProperty(None) txt_nmatch = ObjectProperty(None) def __init__(self, **kwargs): super(ExportMatchesPanel, self).__init__(**kwargs) self.check_clip.bind(active=self.export_opts_callback) self.check_file.bind(active=self.export_opts_callback) self.sep = None self.encoding = None def nmatch_up(self): max = App.get_running_app().backend.max_n_matches if int(self.txt_nmatch.text) >= max: return self.txt_nmatch.text = str(int(self.txt_nmatch.text) + 1) self.check_space.disabled = False def nmatch_down(self): if self.txt_nmatch.text == '1': return self.txt_nmatch.text = str(int(self.txt_nmatch.text) - 1) if self.txt_nmatch.text == '1': self.check_space.disabled = True def show_help(self): help_text = '''- [u]Keep the top [i]n[/i] matches[/u]: choose how many alternate matches you want to review. If you only want the top-scoring match for each row, select \'1\'.\n\n- [u]Add spacer between groups of matches[/u]: if you export multiple alternate matches, this option adds a blank spacer row between separate groups of matches, which helps guide the eye during manual review.\n\n- [u]Sort rows by similarity[/u]: if enabled, rows will be sorted by match score. If disabled, the original row order from your input spreadsheet will be preserved.\n\n- [u]Copy matches to clipboard[/u]: choose this option if you want to paste the matches into Excel or Google Sheets.\n\n- [u]Save matches to file[/u]: choose this option if you want to save the matches to a spreadsheet file (.xlsx, .csv, or .txt).\n''' self._popup = HelpMsg(help_text, title='Help', size_hint=(0.9, 0.9)) self._popup.open() def export_opts_callback(self, check, value): if (self.check_clip.active == False) & (self.check_file.active == False): check.active = True def match_opts_callback(self, check, value): if (self.check_all.active == False) & (self.check_best.active == False): check.active = True if self.check_all.active == True: self.check_space.disabled = False else: self.check_space.disabled = True def back_callback(self, ): app = App.get_running_app() app.backend.drop_appends() app.nav_to('append_screen', 'right') def prep_export(self, *args): backend = App.get_running_app().backend self.matches_for_export = backend.clean_matches_for_export( int(self.txt_nmatch.text), restore_row_order=(not self.check_sort.active), use_spacer=self.check_space.active) def export_callback(self): self._popup = Popup(size=('410dp', '150dp'), size_hint=(None, None), auto_dismiss=False, title='Please wait') self._popup.content = Label( text= 'Working... this can take a long time for large files, and \nthis window will stop responding until finished.' ) self._popup.open() # wait for popup to actually open before starting to load Clock.schedule_once(self.export_really, 0.1) def export_really(self, obj): backend = App.get_running_app().backend if self.check_clip.active: self.prep_export() self.matches_for_export.to_clipboard(index=False) self._popup.dismiss() else: with TkSaveDialog('matches.xlsx', [('Spreadsheet', '.xlsx .csv .txt')]) as dialog: filename = dialog.get_filename() self._popup.dismiss() if filename != '': self.save_export(filename) def save_export(self, filename): self.out_file = Path(filename) self.out_file = self.out_file.with_suffix(self.out_file.suffix.lower()) self.plaintext_opts() def plaintext_opts(self, obj=None): if (self.out_file.suffix == '.csv') | (self.out_file.suffix == '.txt'): self._popup.dismiss() self._popup = Popup(size=('300dp', '350dp'), size_hint=(None, None), title='Select plaintext delimiter', auto_dismiss=False) self._popup.content = PlaintextSepChooser(self._popup.dismiss, self.delim_opts_next) self._popup.open() else: self.save_really() def delim_opts_next(self, obj): delim_choice = self._popup.content.get_delim_choice() if delim_choice == '': return else: self.sep = delim_choice self._popup.dismiss() self._popup = Popup(size=('450dp', '200dp'), size_hint=(None, None), title='Select plaintext encoding', auto_dismiss=False) self._popup.content = PlaintextEncodingChooser( self._popup.dismiss, self.enc_opts_ok) self._popup.open() def enc_opts_ok(self, obj): enc_choice = self._popup.content.dropbtn.text if enc_choice == '[use default]': self.encoding = None else: self.encoding = enc_choice.split(' ')[0] self.save_really() def save_really(self): backend = App.get_running_app().backend try: if self.out_file.suffix == '.xlsx': self.prep_export() self.matches_for_export.to_excel(str(self.out_file), index=False) self._popup.dismiss() elif (self.out_file.suffix == '.csv') | (self.out_file.suffix == '.txt'): self.prep_export() self.matches_for_export.to_csv(str(self.out_file), index=False, sep=self.sep, encoding=self.encoding) self._popup.dismiss() else: raise Exception('Filetype must be .xlsx, .csv, or .txt') except Exception as error: self._popup.dismiss() error_type = str(type(error)).split('\'')[1] self._popup = ErrorMsg( error_text='Error saving {}.\n\n{}: {}'.format( self.out_file.parts[-1], error_type, error)) self._popup.open()