class SettingString(SettingItem): popup = ObjectProperty(None, allownone=True) textinput = ObjectProperty(None) def on_panel(self, instance, value): if value is None: return self.fbind('on_release', self._create_popup) def dismiss(self, *largs): if self.popup: self.popup.dismiss() app = App.get_running_app() if app.popup: app.popup = None self.popup = None def _validate(self, instance, answer): value = self.popup.content.ids['input'].text.strip() self.dismiss() if answer == 'yes': self.value = value def _create_popup(self, instance): content = InputPopup(text='', input_text=self.value) app = App.get_running_app() content.bind(on_answer=self._validate) self.popup = NormalPopup(title=self.title, content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=True) app = App.get_running_app() app.popup = self.popup self.popup.open()
def new_item(self,photoListRecyclerView): """Open new tag popup""" content = InputPopupTag(hint='Tag Name', text='Enter A Tag:') app = App.get_running_app() content.bind(on_answer=self.new_item_answer) self.photoListRecyclerView = photoListRecyclerView self.popup = NormalPopup(title='Create Tag', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=False) self.popup.open()
class TreeViewItemTags(TreeViewItem): indent = 0 can_new_folder = True def visit(self, visitor): super(TreeViewItemTags, self).visit() if self.expanded: visitor.data = self.deleteChild(visitor.data, self) self.expanded = False else: index = self.getItemIndex(visitor.data, self) for tag in self.item.all(): index += 1 tag_item = TreeViewItemTag(self.owner, tag, self.height, self) visitor.data.insert(index, tag_item) self.expanded = True def new_item(self, photoListRecyclerView): """Open new tag popup""" content = InputPopupTag(hint='Tag Name', text='Enter A Tag:') app = App.get_running_app() content.bind(on_answer=self.new_item_answer) self.photoListRecyclerView = photoListRecyclerView self.popup = NormalPopup(title='Create Tag', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=False) self.popup.open() def new_item_answer(self, instance=None, answer="yes"): """Adds the current input tag to the app tags.""" if answer == "yes": if instance is not None: tag_name = instance.ids['input'].text.lower().strip(' ') if not tag_name: self.dismiss_popup() return else: tag_input = self.ids['newTag'] tag_name = tag_input.text.lower().strip(' ') tag_input.text = '' tag = Tag(name=tag_name) app = App.get_running_app() app.session.add(tag) app.session.commit() self.popup.dismiss() # refresh tag treeview if self.expanded: self.visit(self.photoListRecyclerView) self.visit(self.photoListRecyclerView)
def load_theme(self): content = FileBrowser(ok_text='Load', directory_select=False, file_editable=True, export_mode=False, file='theme.txt') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.load_theme_finish) self.popup = NormalPopup(title="Select Theme To Load", content=content, size_hint=(0.9, 0.9)) self.popup.open()
def save_theme(self): content = FileBrowser(ok_text='Save', directory_select=False, file_editable=True, export_mode=True, file='theme.txt') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.save_theme_check) self.popup = NormalPopup(title="Select File To Save Theme To", content=content, size_hint=(0.9, 0.9)) self.popup.open()
def filechooser_popup(self): content = FileBrowser(ok_text='Export', directory_select=False, file_editable=True, export_mode=True, file='collage.jpg') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.export_check) self.popup = NormalPopup(title="Select File To Export To", content=content, size_hint=(0.9, 0.9)) self.popup.open()
def _create_popup(self, instance): content = InputPopup(text='', input_text=self.value) app = App.get_running_app() content.bind(on_answer=self._validate) self.popup = NormalPopup(title=self.title, content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=True) app = App.get_running_app() app.popup = self.popup self.popup.open()
def add_folder(self): """Begin the add folder process, create an input popup.""" content = InputPopup(hint='Folder Name', text='Enter A Folder Name:') app = App.get_running_app() content.bind(on_answer=self.add_folder_answer) self.popup = NormalPopup(title='Create Folder', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open()
def rename_item(self, photoListRecyclerView): """Starts the folder renaming process, creates an input text popup.""" content = InputPopup(hint=self.item.name, text='Rename To:') app = App.get_running_app() self.photoListRecyclerView = photoListRecyclerView content.bind(on_answer=self.rename_item_answer) self.photoListRecyclerView = photoListRecyclerView self.popup = NormalPopup(title='Rename Tag', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=False) self.popup.open()
def export_finish(self): app = App.get_running_app() content = MessagePopup(text='Exporting Collage') if len(self.images) > 8: message = 'Exporting Collage, This May Take Several Minutes, Please Wait...' else: message = 'Exporting Collage, Please Wait...' self.popup = NormalPopup(title=message, content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.popup.open() #Wait a cycle so the popup can display Clock.schedule_once(self.export_process)
def delete_item(self, photoListRecyclerView): """Starts the delete folder process, creates the confirmation popup.""" text = "Delete tag " + self.item.name + "\nThe Contained Files Will Not Be Deleted." content = ConfirmPopup(text=text, yes_text='Delete', no_text="Don't Delete", warn_yes=True) app = App.get_running_app() self.photoListRecyclerView = photoListRecyclerView content.bind(on_answer=self.delete_item_answer) self.popup = NormalPopup(title='Confirm Delete', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open()
def filechooser_popup(self): content = FileBrowser(ok_text='Add', directory_select=True) content.bind(on_cancel=self.filepopup_dismiss) content.bind(on_ok=self.add_directory) self.filepopup = filepopup = NormalPopup(title=self.title, content=content, size_hint=(0.9, 0.9)) filepopup.open()
def add_folder(self): content = FileBrowser(ok_text='Add', directory_select=True) content.bind(on_cancel=self.owner.owner.dismiss_popup) content.bind(on_ok=self.add_folder_confirm) self.owner.owner.popup = filepopup = NormalPopup( title='Select A Folder To Import From', content=content, size_hint=(0.9, 0.9)) filepopup.open()
def select_command(self): """Opens a popup filebrowser to select a program to run.""" content = FileBrowser(ok_text='Select', filters=['*']) content.bind(on_cancel=lambda x: self.owner.owner.dismiss_popup()) content.bind(on_ok=self.select_command_confirm) self.owner.owner.popup = filepopup = NormalPopup( title='Select A Program', content=content, size_hint=(0.9, 0.9)) filepopup.open()
def delete_folder(self): """Starts the delete folder process, creates the confirmation popup.""" app = App.get_running_app() if not os.listdir(self.path): text = "Delete The Selected Folder?" content = ConfirmPopup(text=text, yes_text='Delete', no_text="Don't Delete", warn_yes=True) content.bind(on_answer=self.delete_folder_answer) self.popup = NormalPopup(title='Confirm Delete', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() else: app.popup_message(text='Could not delete, Folder is not empty', title='Warning')
def select_watermark(self): """Open a filebrowser to select the watermark image.""" content = FileBrowser(ok_text='Select', filters=['*.png']) content.bind(on_cancel=self.owner.owner.dismiss_popup) content.bind(on_ok=self.select_watermark_confirm) self.owner.owner.popup = filepopup = NormalPopup( title='Select Watermark PNG Image', content=content, size_hint=(0.9, 0.9)) filepopup.open()
def select_export(self): """Activates a popup folder browser dialog to select the export folder.""" content = FileBrowser(ok_text='Select', directory_select=True) content.bind(on_cancel=self.owner.owner.dismiss_popup) content.bind(on_ok=self.select_export_confirm) self.owner.owner.popup = filepopup = NormalPopup( title='Select An Export Folder', content=content, size_hint=(0.9, 0.9)) filepopup.open()
def save_theme_check(self, *_): path = self.popup.content.path file = self.popup.content.file self.dismiss_popup() self.filename = os.path.join(path, file) if os.path.isfile(self.filename): app = App.get_running_app() content = ConfirmPopup(text='Overwrite the file "' + self.filename + '"?', yes_text='Overwrite', no_text="Cancel", warn_yes=True) content.bind(on_answer=self.save_theme_finish) self.popup = NormalPopup(title='Confirm Overwrite', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() else: self.save_theme_finish()
def on_release(self): app = App.get_running_app() content = ConfirmPopup(text='Delete The Tag "' + self.to_remove + '"?', yes_text='Delete', no_text="Don't Delete", warn_yes=True) content.bind(on_answer=self.on_answer) self.owner.popup = NormalPopup(title='Confirm Delete', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.owner.popup.open()
def _create_popup(self, *_): app = App.get_running_app() if app.database_scanning: return content = BoxLayout(orientation='vertical') popup_width = min(0.95 * Window.width, dp(500)) self.popup = popup = NormalPopup(title=self.title, content=content, size_hint=(None, 0.9), width=popup_width) if not self.value: content.add_widget( ShortLabel( height=app.button_scale * 3, text= "You must set at least one screenDatabase directory.\n\nThis is a folder where your photos are stored.\nNew photos will be imported to a screenDatabase folder." )) content.add_widget(BoxLayout()) else: folders = filter(None, self.value.split(';')) folderdata = [] for folder in folders: folderdata.append({'text': folder}) self.folderlist = folderlist = FolderSettingsList(size_hint=(1, .8), id='folderlist') folderlist.data = folderdata content.add_widget(folderlist) buttons = BoxLayout(orientation='horizontal', size_hint=(1, None), height=app.button_scale) addbutton = NormalButton(text='+') addbutton.bind(on_release=self.add_path) removebutton = NormalButton(text='-') removebutton.bind(on_release=self.remove_path) okbutton = WideButton(text='OK') okbutton.bind(on_release=self._dismiss) buttons.add_widget(addbutton) buttons.add_widget(removebutton) buttons.add_widget(okbutton) content.add_widget(buttons) popup.open()
class ScreenImporting(Screen): """Screen layout for photo importing process. Displays photos from directories and lets you select which ones to import. """ type = StringProperty('') selected = StringProperty('') import_to = StringProperty('') naming_method = StringProperty('') delete_originals = BooleanProperty(False) single_folder = BooleanProperty(False) import_from = ListProperty() popup = None import_photos = [] duplicates = [] photos = [] folders = {} unsorted = [] removed = [] total_size = 0 cancel_scanning = BooleanProperty( False ) # The importing process thread checks this and will stop if set to True. scanningpopup = None # Popup dialog showing the importing process progress. scanningthread = None # Importing files thread. popup_update_thread = None # Updates the percentage and time index on scanning popup percent_completed = NumericProperty() start_time = NumericProperty() import_scanning = BooleanProperty(False) def get_selected_photos(self, fullpath=False): photos = self.ids['photos'] selected_indexes = photos.selected_nodes photos_container = self.ids['photosContainer'] selected_photos = [] for selected in selected_indexes: if fullpath: selected_photos.append( photos_container.data[selected]['fullpath']) else: selected_photos.append( photos_container.data[selected]['photoinfo']) return selected_photos def dismiss_extra(self): """Cancels the import process if it is running""" if self.import_scanning: self.cancel_import() return True else: return False def date_to_folder(self, date): """Generates a string from a date in the format YYYYMMDD.""" date_info = datetime.datetime.fromtimestamp(date) return str(date_info.year) + str(date_info.month).zfill(2) + str( date_info.day).zfill(2) def on_enter(self): """Called when the screen is entered. Sets up variables, and scans the import folders.""" app = App.get_running_app() self.ids['leftpanel'].width = app.left_panel_width() self.duplicates = [] self.import_photos = [] self.folders = {} self.unsorted = [] self.removed = [] self.total_size = 0 self.import_scanning = False # Display message that folder scanning is in progress self.cancel_scanning = False self.scanningpopup = ScanningPopup(title='Scanning Import Folders...', auto_dismiss=False, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.scanningpopup.open() scanning_button = self.scanningpopup.ids['scanningButton'] scanning_button.bind(on_release=self.cancel_import) self.percent_completed = 0 self.scanningthread = threading.Thread(target=self.scan_folders) self.import_scanning = True self.scanningthread.start() self.start_time = time.time() def scan_folders(self, *_): """Function that scans the import folders for valid files to import and now really import files without using the interface to select file. Will import all files in directories. """ app = App.get_running_app() engine = create_engine(app.database_path, echo=True, connect_args={'check_same_thread': False}) Session = sessionmaker(bind=engine) session = Session() # TODO: put this code in a secret file #gmaps = googlemaps.Client(key='xxx-LLtQzk0U') current_timestamp = time.time() # Scan the folders for folder in self.import_from: if os.path.isdir(folder): files = list_files(folder) print(folder) size = len(files) for file_info in files: # update popup if self.cancel_scanning: self.scanning_canceled() return self.percent_completed = self.percent_completed + 1 if self.percent_completed > 100: self.percent_completed = 0 self.scanningpopup.scanning_percentage = self.percent_completed extension = os.path.splitext(file_info[0])[1].lower() if extension in imagetypes or extension in movietypes: photo = Photo() photo.from_file_info(self.import_to, file_info) self.import_photos.append(photo) nb_photo = len(self.import_photos) current_photo_index = 0 for photo in self.import_photos: session.commit() self.scanningpopup.scanning_percentage = current_photo_index / nb_photo * 100 current_photo_index += 1 photo_in_db = session.query(Photo).filter_by( full_path=photo.full_path).first() if photo_in_db is None: folder = session.query(Folder).filter_by( name=photo.folder_name()).first() if folder is None: local_folder_path = os.path.join(self.import_to, photo.folder_name()) if not os.path.isdir(local_folder_path): os.makedirs(local_folder_path) folder = Folder(name=photo.folder_name()) session.add(folder) session.commit() photo.update_thumbnail() session.add(photo) session.commit() folder.photos.append(photo) session.commit() if self.delete_originals: move(photo.old_full_filename(), photo.new_full_filename()) else: copy2(photo.old_full_filename(), photo.new_full_filename()) # localize_photo(gmaps, session, photo) else: # photo is already in database if self.delete_originals: try: os.remove(photo.old_full_filename()) except Exception as e: print(e) for folder in self.import_from: while (removeEmptyFolders(folder, True)): pass self.scanningpopup.dismiss() self.scanningpopup = None self.scanningpopup = None self.import_scanning = False self.update_treeview() self.update_photolist() def scanning_canceled(self): app = App.get_running_app() app.message("Canceled import scanning.") self.scanningpopup.dismiss() self.scanningpopup = None self.import_scanning = False Clock.schedule_once(lambda *dt: app.show_database()) def cancel_import(self, unknown=False): """Cancel the import process.""" self.cancel_scanning = True def finalize_import(self): raise ValueError( "should not call this function anymore because the load is done on preset selection" ) """Begin the final stage of the import - copying files.""" app = App.get_running_app() # Create popup to show importing progress self.cancel_scanning = False self.scanningpopup = ScanningPopup(title='Importing Files', auto_dismiss=False, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.scanningpopup.open() scanning_button = self.scanningpopup.ids['scanningButton'] scanning_button.bind(on_release=self.cancel_import) # Start importing thread self.percent_completed = 0 self.scanningthread = threading.Thread(target=self.importing_process) self.import_scanning = True self.scanningthread.start() self.start_time = time.time() def importing_process(self): """Function that actually imports the files.""" raise ValueError( "should not call this function anymore because the load is done on preset selection" ) app = App.get_running_app() folders = self.folders import_to = self.import_to total_size = self.total_size imported_size = 0 self.scanningpopup.scanning_text = "Importing " + format_size( total_size) + ' 0%' imported_folders = [] imported_files = 0 failed_files = 0 if disk_usage: free_space = disk_usage(import_to)[2] if total_size > free_space: self.scanningpopup.dismiss() self.scanningpopup = None app.message("Not enough free drive space! Cancelled import.") Clock.schedule_once(lambda *dt: app.show_import()) # Scan folders for folder_path in folders: if self.cancel_scanning: break folder = folders[folder_path] folder_name = folder['name'] if folder['photos']: if folder['naming']: folder_name = naming(self.naming_method, title=folder['title'], year=folder['year'], month=folder['month'], day=folder['day']) photos = folder['photos'] parent = folder['parent'] if parent: path_string = [] while parent: newfolder = folders[parent] newfolder_name = newfolder['name'] if newfolder['naming']: newfolder_name = naming(self.naming_method, title=newfolder['title'], year=newfolder['year'], month=newfolder['month'], day=newfolder['day']) path_string.append(newfolder_name) parent = newfolder['parent'] for path in path_string: folder_name = os.path.join(path, folder_name) folderinfo = [ folder_name, folder['title'], folder['description'] ] path = os.path.join(import_to, folder_name) if not os.path.isdir(path): os.makedirs(path) if not app.Folder.exist(folderinfo[0]): app.Folder.insert(folderinfo) else: if folderinfo[1]: app.Folder.update_title(folderinfo[0], folderinfo[1]).commit() if folderinfo[2]: app.Folder.update_description(path, description)( folderinfo[0], folderinfo[2]) # Scan and import photos in folder for photo in photos: if self.cancel_scanning: break completed = (imported_size / total_size) remaining = 1 - completed self.percent_completed = 100 * completed self.scanningpopup.scanning_percentage = self.percent_completed seconds_elapsed = time.time() - self.start_time time_elapsed = ' Time: ' + str( datetime.timedelta(seconds=int(seconds_elapsed))) if self.percent_completed > 0: seconds_remain = (seconds_elapsed * remaining) / completed time_remain = ' Remaining: ' + str( datetime.timedelta(seconds=int(seconds_remain))) else: time_remain = '' self.scanningpopup.scanning_text = "Importing " + format_size( total_size) + ' ' + str( int(self.percent_completed )) + '% ' + time_elapsed + time_remain old_full_filename = os.path.join(photo[2], photo[0]) new_photo_fullpath = os.path.join(folder_name, photo[10]) new_full_filename = os.path.join(import_to, new_photo_fullpath) thumbnail_data = app.Photo.thumbnail(photo[2], temporary=True) if not app.Photo.exist(new_photo_fullpath): photo[0] = new_photo_fullpath photo[1] = folder_name photo[2] = import_to photo[6] = int(time.time()) try: copy2(old_full_filename, new_full_filename) except: failed_files = failed_files + 1 imported_size = imported_size + photo[4] else: if self.delete_originals: if os.path.isfile(new_full_filename): if os.path.getsize(new_full_filename ) == os.path.getsize( old_full_filename): os.remove(old_full_filename) app.Photo.add(photo) # app.database_imported_add(photo[0], photo[10], photo[3]) if thumbnail_data: thumbnail = thumbnail_data[2] app.Photo.thumbnail_write( photo[0], int(time.time()), thumbnail, photo[13]) imported_size = imported_size + photo[4] imported_files = imported_files + 1 else: failed_files = failed_files + 1 imported_size = imported_size + photo[4] """ imported_folders.append(folder_name) """ raise ValueError('muste complete import') app.update_photoinfo(folders=imported_folders) self.scanningpopup.dismiss() if failed_files: failed = ' Could not import ' + str(failed_files) + ' files.' else: failed = '' if not self.cancel_scanning: if imported_files: app.message("Finished importing " + str(imported_files) + " files." + failed) else: if imported_files: app.message("Canceled importing, " + str(imported_files) + " files were imported." + failed) else: app.message("Canceled importing, no files were imported.") self.scanningpopup = None self.import_scanning = False Clock.schedule_once(lambda *dt: app.show_database()) def set_delete_originals(self, state): """Enable the 'Delete Originals' option.""" if state == 'down': self.delete_originals = True else: self.delete_originals = False def previous_album(self): """Switch to the previous album in the list.""" database = self.ids['folders'] selected_album = database.selected_node if selected_album: nodes = list(database.iterate_all_nodes()) index = nodes.index(selected_album) if index <= 1: index = len(nodes) new_selected_album = nodes[index - 1] database.select_node(new_selected_album) new_selected_album.on_press() database_container = self.ids['foldersContainer'] database_container.scroll_to(new_selected_album) def next_album(self): """Switch to the next item on the list.""" database = self.ids['folders'] selected_album = database.selected_node if selected_album: nodes = list(database.iterate_all_nodes()) index = nodes.index(selected_album) if index >= len(nodes) - 1: index = 0 new_selected_album = nodes[index + 1] database.select_node(new_selected_album) new_selected_album.on_press() database_container = self.ids['foldersContainer'] database_container.scroll_to(new_selected_album) def delete(self): """Remove selected files and place them in the unsorted folder.""" if self.type == 'folder' or (self.type == 'extra' and self.selected == 'unsorted'): selected_files = self.get_selected_photos() for photo in selected_files: if self.selected != 'unsorted': self.folders[self.selected]['photos'].remove(photo) self.unsorted.append(photo) self.update_treeview() self.update_photolist() def add_folder(self): """Begin the add folder process, create an input popup.""" content = InputPopup(hint='Folder Name', text='Enter A Folder Name:') app = App.get_running_app() content.bind(on_answer=self.add_folder_answer) self.popup = NormalPopup(title='Create Folder', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() def add_folder_answer(self, instance, answer): """Confirm adding the folder. Arguments: instance: Dialog that called this function. answer: String, if set to 'yes', folder is created. """ app = App.get_running_app() if answer == 'yes': text = instance.ids['input'].text.strip(' ') if text: if self.type == 'extra': root = '' else: root = self.selected path = os.path.join(root, text) if path not in self.folders: self.folders[path] = { 'name': text, 'parent': root, 'naming': False, 'title': '', 'description': '', 'year': 0, 'month': 0, 'day': 0, 'photos': [] } else: app.message("Folder already exists.") self.dismiss_popup() self.update_treeview() def delete_folder(self): """Delete the selected import folder and move photos to the unsorted folder.""" if self.type == 'folder' and self.selected: folder_info = self.folders[self.selected] photos = folder_info['photos'] for photo in photos: self.unsorted.append(photo) del self.folders[self.selected] self.selected = '' self.type = 'None' self.update_treeview() self.update_photolist() def toggle_select(self): """Toggles the selection of photos in the current album.""" photos = self.ids['photos'] photos.toggle_select() self.update_selected() def select_none(self): """Deselects all photos.""" photos = self.ids['photos'] photos.clear_selection() self.update_selected() def update_treeview(self): """Clears and repopulates the left-side folder list.""" folder_list = self.ids['folders'] # Clear the treeview list nodes = list(folder_list.iterate_all_nodes()) for node in nodes: folder_list.remove_node(node) selected_node = None # folder_item = TreeViewButton(target='removed', type='extra', owner=self, view_album=False) # folder_item.folder_name = 'Removed (Never Scan Again)' # total_photos = len(self.removed) # folder_item.total_photos_numeric = total_photos # if total_photos > 0: # folder_item.total_photos = '('+str(total_photos)+')' # folder_list.add_node(folder_item) # if self.selected == 'removed' and self.type == 'extra': # selected_node = folder_item # Populate the 'Already Imported' folder folder_item = TreeViewButton(target='duplicates', type='extra', owner=self, view_album=False) folder_item.folder_name = 'Already Imported (Never Import Again)' total_photos = len(self.duplicates) folder_item.total_photos_numeric = total_photos if total_photos > 0: folder_item.total_photos = '(' + str(total_photos) + ')' folder_list.add_node(folder_item) if self.selected == 'duplicates' and self.type == 'extra': selected_node = folder_item # Populate the 'Unsorted' folder folder_item = TreeViewButton(target='unsorted', type='extra', owner=self, view_album=False) folder_item.folder_name = 'Unsorted (Not Imported This Time)' total_photos = len(self.unsorted) folder_item.total_photos_numeric = total_photos if total_photos > 0: folder_item.total_photos = '(' + str(total_photos) + ')' folder_list.add_node(folder_item) if self.selected == 'unsorted' and self.type == 'extra': selected_node = folder_item # Populate the importing folders sorted_folders = sorted(self.folders) self.total_size = 0 to_parent = [] added_nodes = {} for folder_date in sorted_folders: folder_info = self.folders[folder_date] target = folder_date folder_item = TreeViewButton(is_open=True, fullpath=target, dragable=True, target=target, type='folder', owner=self, view_album=False) if folder_info['naming']: folder_item.folder_name = naming(self.naming_method, title=folder_info['title'], year=folder_info['year'], month=folder_info['month'], day=folder_info['day']) else: folder_item.folder_name = folder_info['name'] added_nodes[folder_date] = folder_item photos = folder_info['photos'] for photo in photos: self.total_size = self.total_size + photo[4] total_photos = len(photos) folder_item.total_photos_numeric = total_photos if total_photos > 0: folder_item.total_photos = '(' + str(total_photos) + ')' if folder_info['parent']: to_parent.append([folder_item, folder_info['parent']]) else: folder_list.add_node(folder_item) if self.selected == target and self.type == 'folder': selected_node = folder_item for item in to_parent: node = item[0] parent_name = item[1] if parent_name in added_nodes.keys(): folder_list.add_node(node, parent=added_nodes[parent_name]) else: folder_list.add_node(node) if selected_node: folder_list.select_node(selected_node) size_display = self.ids['totalSize'] size_display.text = 'Total Size: ' + format_size(self.total_size) def on_selected(self, instance, value): """Called when a photo is selected. Activate the delete button, and update photo view.""" delete_button = self.ids['deleteButton'] delete_button.disabled = True self.update_photolist() def new_description(self, description_editor): """Called when the description field of the currently selected folder is edited. Update internal variables to match. Argument: description_editor: The input box that has been edited. """ description = description_editor.text if self.type == 'folder': self.folders[self.selected]['description'] = description def new_title(self, title_editor): """Called when the title field of the currently selected folder is edited. Update internal variables to match. Argument: title_editor: The input box that has been edited. """ title = title_editor.text if self.type == 'folder': self.folders[self.selected]['title'] = title folder_info = self.folders[self.selected] self.update_treeview() folder_name = self.ids['folderName'] folder_name.text = naming(self.naming_method, title=folder_info['title'], year=folder_info['year'], month=folder_info['month'], day=folder_info['day']) def update_photolist(self): """Redraw the photo list view for the currently selected folder.""" folder_name = self.ids['folderName'] photos = [] name = '' title_editor = self.ids['folderTitle'] description_editor = self.ids['folderDescription'] dragable = True # Viewing an input folder. if self.type == 'folder': if self.selected in self.folders: folder_info = self.folders[self.selected] title_editor.text = folder_info['title'] title_editor.disabled = False description_editor.text = folder_info['description'] description_editor.disabled = False photos = folder_info['photos'] if folder_info['naming']: name = naming(self.naming_method, title=folder_info['title'], year=folder_info['year'], month=folder_info['month'], day=folder_info['day']) else: name = self.selected # Viewing a special sorting folder. else: title_editor.text = '' title_editor.disabled = True description_editor.text = '' description_editor.disabled = True if self.selected == 'unsorted': photos = self.unsorted name = 'Unsorted (Not Imported This Time)' elif self.selected == 'removed': photos = self.removed name = 'Removed (Never Scanned Again)' elif self.selected == 'duplicates': dragable = False photos = self.duplicates name = 'Already Imported (Never Import Again)' folder_name.text = name # Populate photo view photos_container = self.ids['photosContainer'] datas = [] for photo in photos: full_filename = os.path.join(photo.database_folder, photo.fullpath) fullpath = photo.fullpath database_folder = photo.database_folder video = os.path.splitext(full_filename)[1].lower() in movietypes data = { 'fullpath': fullpath, 'temporary': True, 'photo': photo, 'folder': self.selected, 'database_folder': database_folder, 'filename': full_filename, 'target': self.selected, 'type': self.type, 'owner': self, 'video': video, 'photo_orientation': photo.orientation, 'source': full_filename, 'title': photo.owner, 'selected': False, 'selectable': True, 'dragable': dragable } datas.append(data) photos_container.data = datas self.select_none() def find_photo(self, photo_path, photo_list): """Searches through a list of photoinfo objects to find the specified photo. Arguments: photo_path: The screenDatabase-relative path to the photo to search for. photo_list: The list of photo info objects to look through. Returns: False if nothing found Photo info list if match found. """ for photo in photo_list: if photo[0] == photo_path: return photo return False def drop_widget(self, fullpath, position, dropped_type='file', aspect=1): """Called when a widget is dropped after being dragged. Determines what to do with the widget based on where it is dropped. Arguments: fullpath: String, file location of the object being dragged. position: List of X,Y window coordinates that the widget is dropped on. dropped_type: String, describes the object being dropped. May be: 'folder' or 'file' """ app = App.get_running_app() folder_list = self.ids['folders'] folder_container = self.ids['foldersContainer'] if folder_container.collide_point(position[0], position[1]): offset_x, offset_y = folder_list.to_widget(position[0], position[1]) for widget in folder_list.children: if widget.collide_point( position[0], offset_y ) and widget.type != 'None' and self.type != 'None' and not ( widget.target == 'duplicates' and widget.type == 'extra'): if dropped_type == 'folder': # Dropped a folder dropped_data = self.folders[fullpath] new_path = os.path.join(widget.fullpath, dropped_data['name']) if widget.fullpath != fullpath: # this was actually a drag and not a long click if new_path not in self.folders: # this folder can be dropped here old_parent = fullpath dropped_data['parent'] = widget.fullpath self.folders[new_path] = dropped_data del self.folders[fullpath] new_folders = {} # rename child folders for folder in self.folders: folder_info = self.folders[folder] parent = folder_info['parent'] if old_parent and folder.startswith( old_parent): new_folder_path = new_path + folder[ len(old_parent):] new_parent = new_path + parent[ len(old_parent):] folder_info['parent'] = new_parent new_folders[ new_folder_path] = folder_info else: new_folders[folder] = folder_info self.folders = new_folders self.update_treeview() self.update_photolist() else: app.message("Invalid folder location.") elif dropped_type == 'file': # Dropped a file photo_list = self.get_selected_photos(fullpath=True) if fullpath not in photo_list: photo_list.append(fullpath) for photo_path in photo_list: photo_info = False if self.type == 'folder': photo_info = self.find_photo( photo_path, self.folders[self.selected]['photos']) if photo_info: self.folders[self.selected][ 'photos'].remove(photo_info) else: if self.selected == 'unsorted': photo_info = self.find_photo( photo_path, self.unsorted) if photo_info: self.unsorted.remove(photo_info) elif self.selected == 'removed': photo_info = self.find_photo( photo_path, self.removed) if photo_info: self.removed.remove(photo_info) if photo_info: if widget.type == 'folder': self.folders[widget.target][ 'photos'].append(photo_info) else: if widget.target == 'unsorted': self.unsorted.append(photo_info) elif widget.target == 'removed': self.removed.append(photo_info) self.type = widget.type self.selected = widget.target self.update_treeview() self.select_none() break def update_selected(self): """Updates the delete button when files are selected or unselected. Disables button if nothing is selected.""" if self.type == 'folder' or (self.type == 'extra' and self.selected == 'unsorted'): photos = self.ids['photos'] if photos.selected_nodes: selected = True else: selected = False delete_button = self.ids['deleteButton'] if self.type != 'extra' and self.selected != 'unsorted': delete_button.disabled = not selected def has_popup(self): """Detects if the current screen has a popup active. Returns: True or False """ if self.popup: if self.popup.open: return True return False def dismiss_popup(self, *_): """Close a currently open popup for this screen.""" if self.popup: self.popup.dismiss() self.popup = None def text_input_active(self): """Detects if any text input fields are currently active (being typed in). Returns: True or False """ input_active = False for widget in self.walk(restrict=True): if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput': if widget.focus: input_active = True break return input_active def key(self, key): """Handles keyboard shortcuts, performs the actions needed. Argument: key: The name of the key command to perform. """ if self.text_input_active(): pass else: if not self.popup or (not self.popup.open): if key == 'left' or key == 'up': self.previous_album() if key == 'right' or key == 'down': self.next_album() if key == 'delete': self.delete() if key == 'a': self.toggle_select() elif self.popup and self.popup.open: if key == 'enter': self.popup.content.dispatch('on_answer', 'yes')
class TreeViewItemTag(TreeViewItem): indent = 1 can_rename_folder = True can_delete_folder = True def visit(self, screenDatabase): super(TreeViewItemTag, self).visit() screenDatabase = self.owner datas = [] for photo in self.item.photos: datas.append(photo.data_item(screenDatabase)) screenDatabase.data = datas screenDatabase.update_can_browse() screenDatabase.update_selected() def visit_drop(self, elements): self.owner.add_to_tag(self.item, elements) self.target = 'allo' def rename_item(self, photoListRecyclerView): """Starts the folder renaming process, creates an input text popup.""" content = InputPopup(hint=self.item.name, text='Rename To:') app = App.get_running_app() self.photoListRecyclerView = photoListRecyclerView content.bind(on_answer=self.rename_item_answer) self.photoListRecyclerView = photoListRecyclerView self.popup = NormalPopup(title='Rename Tag', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=False) self.popup.open() def rename_item_answer(self, instance, answer): """Tells the app to rename the folder if the dialog is confirmed. Arguments: instance: The dialog that called this function. answer: String, if 'yes', the folder will be renamed, all other answers will just close the dialog. """ if answer == 'yes': text = instance.ids['input'].text.strip(' ') app = App.get_running_app() self.item.name = text app.session.commit() self.popup.dismiss() self.__refresh_tree_view() def delete_item(self, photoListRecyclerView): """Starts the delete folder process, creates the confirmation popup.""" text = "Delete tag " + self.item.name + "\nThe Contained Files Will Not Be Deleted." content = ConfirmPopup(text=text, yes_text='Delete', no_text="Don't Delete", warn_yes=True) app = App.get_running_app() self.photoListRecyclerView = photoListRecyclerView content.bind(on_answer=self.delete_item_answer) self.popup = NormalPopup(title='Confirm Delete', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() def delete_item_answer(self, instance, answer): """Tells the app to delete the folder if the dialog is confirmed. Arguments: instance: The dialog that called this function. answer: String, if 'yes', the folder will be deleted, all other answers will just close the dialog. """ del instance if answer == 'yes': self.item.delete() #self.previous_album() self.popup.dismiss() self.__refresh_tree_view() def __refresh_tree_view(self): if self.treeViewItemParent.expanded: self.treeViewItemParent.visit(self.photoListRecyclerView) self.treeViewItemParent.visit(self.photoListRecyclerView)
class ScreenCollage(Screen): sort_reverse_button = StringProperty('normal') images = [] collage_background = ListProperty([0, 0, 0, 1]) resolution = StringProperty('Medium') aspect = NumericProperty(1.3333) aspect_text = StringProperty('4:3') filename = StringProperty('') export_scale = 1 #Widget holder variables sort_dropdown = ObjectProperty() #Holder for the sort method dropdown menu popup = None #Holder for the screen's popup dialog resolution_select = ObjectProperty() color_select = ObjectProperty() aspect_select = ObjectProperty() add_remove = ObjectProperty() #Variables relating to the photo list view on the left selected = StringProperty( '') #The current folder/album/tag being displayed type = StringProperty('None') #'Folder', 'Album', 'Tag' target = StringProperty( ) #The identifier of the album/folder/tag that is being viewed photos = [] #Photoinfo of all photos in the album sort_method = StringProperty('Name') #Current album sort method sort_reverse = BooleanProperty(False) def deselect_images(self): collage = self.ids['collageHolder'] for image in collage.children: image.selected = False def delete_selected(self): collage = self.ids['collageHolder'] for image in collage.children: if image.selected: collage.remove_widget(image) def clear_collage(self): collage = self.ids['collageHolder'] collage.clear_widgets() self.images = [] self.collage_background = [0, 0, 0, 1] def add_all(self): #adds all photos to the collage using a fimonacci spiral pattern collage = self.ids['collageHolder'] size = (1 / (len(self.photos)**0.5)) #average scale of photo photos = list(self.photos) random.shuffle(photos) tau = (1 + 5**0.5) / 2 inc = (2 - tau) * 2 * math.pi theta = 0 max_x = 0 max_y = 0 coords = [] offset_scale = .5 app = App.get_running_app() app.message("Added " + str(len(photos)) + " images.") #Generate basis coordinates and determine min/max for index in range(0, len(photos)): offset = (random.random() * offset_scale) - ( .5 * offset_scale) #random angle variation r = index**0.5 theta = theta + inc + offset pos_x = 0.5 + r * math.cos(theta) if abs(pos_x) > max_x: max_x = abs(pos_x) pos_y = 0.5 + r * math.sin(theta) if abs(pos_y) > max_y: max_y = abs(pos_y) coords.append((pos_x, pos_y)) #add photos to collage for index, photo in enumerate(photos): rand_angle = random.randint(-33, 33) pos_x, pos_y = coords[index] #scale points down by max size pos_x = pos_x / max_x pos_y = pos_y / max_y #scale points down to prevent photos overlapping edges pos_x = pos_x * (1 - (size / 2)) pos_y = pos_y * (1 - (size / 2)) #convert to kivy's coordinate system pos_x = (pos_x + 1) / 2 pos_y = (pos_y + 1) / 2 #offset points to correct for photo size pos_x = pos_x - (size / 2) pos_y = pos_y - (size / 2) position = (collage.width * pos_x, collage.height * pos_y) #forces lowmem mode if more than a certain number of photos if len(photos) > 8: lowmem = True else: lowmem = False self.add_collage_image(collage, photo[0], position, size=size, angle=rand_angle, lowmem=lowmem) def export(self): self.deselect_images() self.filechooser_popup() def filechooser_popup(self): content = FileBrowser(ok_text='Export', directory_select=False, file_editable=True, export_mode=True, file='collage.jpg') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.export_check) self.popup = NormalPopup(title="Select File To Export To", content=content, size_hint=(0.9, 0.9)) self.popup.open() def export_check(self, *_): path = self.popup.content.path file = self.popup.content.file self.dismiss_popup() if not file.lower().endswith('.jpg'): file = file + '.jpg' self.filename = os.path.join(path, file) if os.path.isfile(self.filename): app = App.get_running_app() content = ConfirmPopup(text='Overwrite the file "' + self.filename + '"?', yes_text='Overwrite', no_text="Cancel", warn_yes=True) content.bind(on_answer=self.export_overwrite_answer) self.popup = NormalPopup(title='Confirm Overwrite', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() else: self.export_finish() def export_overwrite_answer(self, instance, answer): del instance if answer == 'yes': self.dismiss_popup() self.export_finish() def export_finish(self): app = App.get_running_app() content = MessagePopup(text='Exporting Collage') if len(self.images) > 8: message = 'Exporting Collage, This May Take Several Minutes, Please Wait...' else: message = 'Exporting Collage, Please Wait...' self.popup = NormalPopup(title=message, content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.popup.open() #Wait a cycle so the popup can display Clock.schedule_once(self.export_process) def export_process(self, *_): if self.resolution == 'High': self.export_scale = 4 elif self.resolution == 'Low': self.export_scale = 1 else: self.export_scale = 2 #wait for full sized images to load check_images = [] if self.export_scale > 1: for image in self.images: async_image = image.children[0].children[0] if not async_image.is_full_size: async_image.loadfullsize = True async_image._load_fullsize() check_images.append(async_image) while check_images: for image in check_images: if image.is_full_size: check_images.remove(image) #wait a cycle so kivy can finish displaying the textures Clock.schedule_once(self.export_collage_as_image) def export_collage_as_image(self, *_): collage = self.ids['collageHolder'] exported = self.export_scaled_jpg(collage, self.filename, image_scale=self.export_scale) app = App.get_running_app() self.dismiss_popup() if exported is True: app.message("Exported " + self.filename) else: app.message('Export error: ' + exported) def export_scaled_jpg(self, widget, filename, image_scale=1): from kivy.graphics import (Translate, Fbo, ClearColor, ClearBuffers, Scale) re_size = (widget.width * image_scale, widget.height * image_scale) if widget.parent is not None: canvas_parent_index = widget.parent.canvas.indexof(widget.canvas) if canvas_parent_index > -1: widget.parent.canvas.remove(widget.canvas) try: fbo = Fbo(size=re_size, with_stencilbuffer=True) with fbo: ClearColor(0, 0, 0, 0) ClearBuffers() Scale(image_scale, -image_scale, image_scale) Translate(-widget.x, -widget.y - widget.height, 0) fbo.add(widget.canvas) fbo.draw() from io import BytesIO image_bytes = BytesIO() fbo.texture.save(image_bytes, flipped=False, fmt='png') image_bytes.seek(0) from PIL import Image image = Image.open(image_bytes) image = image.convert('RGB') image.save(filename) exported = True except Exception as ex: exported = str(ex) try: fbo.remove(widget.canvas) except: pass if widget.parent is not None and canvas_parent_index > -1: widget.parent.canvas.insert(canvas_parent_index, widget.canvas) return exported def change_transform(self, transform_mode): for container in self.images: if transform_mode == 'rotscale': container.do_rotation = True container.do_scale = True container.do_translation = False elif transform_mode == 'rotate': container.do_rotation = True container.do_scale = False container.do_translation = False elif transform_mode == 'scale': container.do_rotation = False container.do_scale = True container.do_translation = False else: container.do_rotation = True container.do_scale = True container.do_translation = True def on_sort_reverse(self, *_): """Updates the sort reverse button's state variable, since kivy doesnt just use True/False for button states.""" app = App.get_running_app() self.sort_reverse_button = 'down' if to_bool( app.config.get('Sorting', 'album_sort_reverse')) else 'normal' def drop_widget(self, fullpath, position, dropped_type='file', aspect=1): """Called when a widget is dropped. Determine photo dragged, and where it needs to go.""" collage = self.ids['collageHolder'] position = collage.to_widget(*position) self.add_collage_image(collage, fullpath, position, aspect=aspect) def add_collage_image(self, collage, fullpath, position, size=0.5, angle=0, lowmem=False, aspect=1): if not lowmem: if len(self.images) > 8: lowmem = True if collage.collide_point(*position): self.deselect_images() app = App.get_running_app() photoinfo = app.Photo.exist(fullpath) file = os.path.join(photoinfo[2], photoinfo[0]) orientation = photoinfo[13] if orientation == 3 or orientation == 4: angle_offset = 180 elif orientation == 5 or orientation == 6: angle_offset = 270 elif orientation == 7 or orientation == 8: angle_offset = 90 else: angle_offset = 0 if orientation in [2, 4, 5, 7]: mirror = True else: mirror = False width = collage.width image_holder = ScatterImage(owner=self, source=file, rotation=angle + angle_offset, mirror=mirror, image_angle=0, photoinfo=photoinfo, lowmem=lowmem, aspect=aspect) image_holder.scale = size image_holder.selected = True if aspect < 1: image_holder.width = width * aspect image_holder.height = width else: image_holder.width = width image_holder.height = width / aspect self.images.append(image_holder) image_holder.pos = (position[0] - (width * size / 2), position[1] - (width * size / 2)) collage.add_widget(image_holder) def show_selected(self): """Scrolls the treeview to the currently selected folder""" database = self.ids['albumContainer'] database_interior = self.ids['album'] selected = self.selected data = database.data current_folder = None for i, node in enumerate(data): if node['target'] == selected and node['type'] == self.type: current_folder = node break if current_folder is not None: database_interior.selected = current_folder database.scroll_to_selected() def text_input_active(self): """Detects if any text input fields are currently active (being typed in). Returns: True or False """ input_active = False for widget in self.walk(restrict=True): if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput': if widget.focus: input_active = True break return input_active def has_popup(self): """Detects if the current screen has a popup active. Returns: True or False """ if self.popup: if self.popup.open: return True return False def dismiss_popup(self, *_): """Close a currently open popup for this screen.""" if self.popup: self.popup.dismiss() self.popup = None def dismiss_extra(self): """Deactivates running processes if applicable. Returns: True if something was deactivated, False if not. """ return False def key(self, key): """Handles keyboard shortcuts, performs the actions needed. Argument: key: The name of the key command to perform. """ if self.text_input_active(): pass else: if not self.popup or (not self.popup.open): #normal keypresses pass elif self.popup and self.popup.open: if key == 'enter': self.popup.content.dispatch('on_answer', 'yes') def scroll_photolist(self): """Scroll the right-side photo list to the current active photo.""" photolist = self.ids['albumContainer'] self.show_selected() photolist.scroll_to_selected() def refresh_all(self): self.refresh_photolist() self.refresh_photoview() def update_selected(self): pass def refresh_photolist(self): """Reloads and sorts the photo list""" app = App.get_running_app() #Get photo list self.photos = [] if self.type == 'Album': for albuminfo in app.albums: if albuminfo['name'] == self.target: photo_paths = albuminfo['photos'] for fullpath in photo_paths: photoinfo = app.Photo.exist(fullpath) if photoinfo: self.photos.append(photoinfo) elif self.type == 'Tag': self.photos = app.Tag.photos(self.target) else: self.photos = app.Photo.by_folder(self.target) #Remove video files temp_photos = [] for photo in self.photos: source = os.path.join(photo[2], photo[0]) isvideo = os.path.splitext(source)[1].lower() in movietypes if not isvideo: temp_photos.append(photo) self.photos = temp_photos #Sort photos if self.sort_method == 'Imported': sorted_photos = sorted(self.photos, key=lambda x: x[6], reverse=self.sort_reverse) elif self.sort_method == 'Modified': sorted_photos = sorted(self.photos, key=lambda x: x[7], reverse=self.sort_reverse) elif self.sort_method == 'Owner': sorted_photos = sorted(self.photos, key=lambda x: x[11], reverse=self.sort_reverse) elif self.sort_method == 'Name': sorted_photos = sorted(self.photos, key=lambda x: os.path.basename(x[0]), reverse=self.sort_reverse) else: sorted_photos = sorted(self.photos, key=lambda x: x[0], reverse=self.sort_reverse) self.photos = sorted_photos def refresh_photoview(self): #refresh recycleview photolist = self.ids['albumContainer'] photodatas = [] for photo in self.photos: source = os.path.join(photo[2], photo[0]) filename = os.path.basename(photo[0]) photodata = { 'text': filename, 'fullpath': photo[0], 'temporary': True, 'photoinfo': photo, 'folder': self.selected, 'database_folder': photo[2], 'filename': filename, 'target': self.selected, 'type': self.type, 'owner': self, 'video': False, 'photo_orientation': photo[13], 'source': source, 'title': photo[10], 'selected': False, 'selectable': True, 'dragable': True } photodatas.append(photodata) photolist.data = photodatas def clear_photolist(self): photolist = self.ids['albumContainer'] photolist.data = [] def resort_method(self, method): """Sets the album sort method. Argument: method: String, the sort method to use """ self.sort_method = method app = App.get_running_app() app.config.set('Sorting', 'album_sort', method) self.refresh_all() Clock.schedule_once(lambda *dt: self.scroll_photolist()) def resort_reverse(self, reverse): """Sets the album sort reverse. Argument: reverse: String, if 'down', reverse will be enabled, disabled on any other string. """ app = App.get_running_app() sort_reverse = True if reverse == 'down' else False app.config.set('Sorting', 'album_sort_reverse', sort_reverse) self.sort_reverse = sort_reverse self.refresh_all() Clock.schedule_once(lambda *dt: self.scroll_photolist()) def on_leave(self): """Called when the screen is left. Clean up some things.""" self.clear_collage() self.clear_photolist() def on_enter(self): """Called when the screen is entered. Set up variables and widgets, and prepare to view images.""" app = App.get_running_app() self.ids['leftpanel'].width = app.left_panel_width() self.ids['moveButton'].state = 'down' self.ids['rotateButton'].state = 'normal' self.color_select = ColorDropDown(owner=self) self.resolution_select = ResolutionDropDown(owner=self) self.aspect_select = ExportAspectRatioDropDown(owner=self) self.add_remove = AddRemoveDropDown(owner=self) #import variables self.target = app.target self.type = app.type #set up sort buttons self.sort_dropdown = AlbumSortDropDown() self.sort_dropdown.bind( on_select=lambda instance, x: self.resort_method(x)) self.sort_method = app.config.get('Sorting', 'album_sort') self.sort_reverse = to_bool( app.config.get('Sorting', 'album_sort_reverse')) #refresh views self.refresh_photolist() self.refresh_photoview()
class FileBrowser(BoxLayout): __events__ = ('on_cancel', 'on_ok') path = StringProperty() file = StringProperty() filename = StringProperty() root = StringProperty() popup = ObjectProperty(None, allownone=True) allow_new = BooleanProperty(True) allow_delete = BooleanProperty(True) new_folder = StringProperty('') start_in = StringProperty() directory_select = BooleanProperty(False) file_editable = BooleanProperty(False) filters = ListProperty() target_selected = BooleanProperty(False) export_mode = BooleanProperty(False) header_text = StringProperty('Select A File') cancel_text = StringProperty('Cancel') ok_text = StringProperty('OK') def __init__(self, **kwargs): if not self.start_in: self.start_in = '/' Clock.schedule_once(self.refresh_locations) super(FileBrowser, self).__init__(**kwargs) def dismiss_popup(self, *_): """If this dialog has a popup, closes it and removes it.""" if self.popup: self.popup.dismiss() self.popup = None def add_folder(self): """Starts the add folder process, creates an input text popup.""" content = InputPopup(hint='Folder Name', text='Enter A Folder Name:') app = App.get_running_app() content.bind(on_answer=self.add_folder_answer) self.popup = NormalPopup(title='Create Folder', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 5), auto_dismiss=False) self.popup.open() def add_folder_answer(self, instance, answer): """Tells the app to rename the folder if the dialog is confirmed. Arguments: instance: The dialog that called this function. answer: String, if 'yes', the folder will be created, all other answers will just close the dialog. """ if answer == 'yes': text = instance.ids['input'].text.strip(' ') if text: app = App.get_running_app() folder = os.path.join(self.path, text) created = False try: if not os.path.isdir(folder): os.makedirs(folder) created = True except: pass if created: app.message("Created the folder '" + folder + "'") self.path = folder self.refresh_folder() else: app.message("Could Not Create Folder.") self.dismiss_popup() def delete_folder(self): """Starts the delete folder process, creates the confirmation popup.""" app = App.get_running_app() if not os.listdir(self.path): text = "Delete The Selected Folder?" content = ConfirmPopup(text=text, yes_text='Delete', no_text="Don't Delete", warn_yes=True) content.bind(on_answer=self.delete_folder_answer) self.popup = NormalPopup(title='Confirm Delete', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() else: app.popup_message(text='Could not delete, Folder is not empty', title='Warning') def delete_folder_answer(self, instance, answer): """Tells the app to delete the folder if the dialog is confirmed. Arguments: instance: The dialog that called this function. answer: String, if 'yes', the folder will be deleted, all other answers will just close the dialog. """ del instance if answer == 'yes': app = App.get_running_app() try: os.rmdir(self.path) app.message("Deleted Folder: \"" + self.path + "\"") self.go_up() except: app.message("Could Not Delete Folder...") self.dismiss_popup() def refresh_locations(self, *_): locations_list = self.ids['locationsList'] locations = get_drives() self.root = locations[0][0] data = [] for location in locations: data.append({ 'text': location[1], 'fullpath': location[0], 'path': location[0], 'type': 'folder', 'owner': self }) locations_list.data = data if not self.path: self.path = locations[0][0] self.refresh_folder() def refresh_folder(self, *_): file_list = self.ids['fileList'] data = [] files = [] dirs = [] try: directory_elements = os.listdir(self.path) except: directory_elements = [] for file in directory_elements: fullpath = os.path.join(self.path, file) if os.path.isfile(fullpath): files.append(file) elif os.path.isdir(fullpath): dirs.append(file) dirs = sorted(dirs, key=lambda s: s.lower()) for directory in dirs: fullpath = os.path.join(self.path, directory) data.append({ 'text': directory, 'fullpath': fullpath, 'path': fullpath + os.path.sep, 'type': 'folder', 'owner': self, 'selected': False }) if not self.directory_select: if self.filters: filtered_files = [] for item in self.filters: filtered_files += fnmatch.filter(files, item) files = filtered_files files = sorted(files, key=lambda s: s.lower()) for file in files: data.append({ 'text': file, 'fullpath': os.path.join(self.path, file), 'path': self.path, 'type': file, 'file': file, 'owner': self, 'selected': False }) file_list.data = data if not self.directory_select: if self.export_mode: if not self.file: self.target_selected = False else: self.target_selected = True else: self.file = '' self.target_selected = False else: self.filename = self.path self.target_selected = True def go_up(self, *_): up_path = os.path.realpath(os.path.join(self.path, '..')) if not up_path.endswith(os.path.sep): up_path += os.path.sep if up_path == self.path: up_path = self.root self.path = up_path self.refresh_folder() def select(self, button): if button.type == 'folder': self.path = button.path self.refresh_folder() if self.directory_select: self.filename = button.fullpath self.target_selected = True elif self.export_mode: self.target_selected = True else: self.filename = '' self.target_selected = False else: self.filename = button.fullpath self.file = button.file self.target_selected = True def on_cancel(self): pass def on_ok(self): pass
class ScreenTheme(Screen): """Screen layout of the album viewer.""" popup = None #Holder for the screen's popup dialog theme_backup = {} filename = '' preset_drop = ObjectProperty() def drop_widget(self, fullpath, position, dropped_type='file', aspect=1): """Dummy function. Here because the app can possibly call this function for any screen.""" pass def text_input_active(self): """Detects if any text input fields are currently active (being typed in). Returns: True or False """ input_active = False for widget in self.walk(restrict=True): if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput': if widget.focus: input_active = True break return input_active def has_popup(self): """Detects if the current screen has a popup active. Returns: True or False """ if self.popup: if self.popup.open: return True return False def dismiss_popup(self, *_): """Close a currently open popup for this screen.""" if self.popup: self.popup.dismiss() self.popup = None def dismiss_extra(self): """Close any running processes.""" return False def key(self, key): """Handles keyboard shortcuts, performs the actions needed. Argument: key: The name of the key command to perform. """ if self.text_input_active(): pass else: if not self.popup or (not self.popup.open): pass elif self.popup and self.popup.open: if key == 'enter': self.popup.content.dispatch('on_answer', 'yes') def on_leave(self): """Called when the screen is left. Clean up some things.""" app = App.get_running_app() def on_enter(self): """Called when the screen is entered. Set up variables and widgets.""" app = App.get_running_app() self.ids['leftpanel'].width = app.left_panel_width() #back up theme self.theme_backup = app.theme.theme_to_data(app.theme) #Set up preset menu self.preset_drop = NormalDropDown() for preset in themes: menu_button = MenuButton(text=preset['name']) menu_button.bind(on_release=self.set_preset) self.preset_drop.add_widget(menu_button) def set_preset(self, instance): """Sets the current dialog preset settings to one of the presets stored in the app. Argument: index: Integer, the index of the preset to set. """ self.preset_drop.dismiss() app = App.get_running_app() for preset in themes: if preset['name'] == instance.text: app.theme.data_to_theme(preset) self.reload_colors() app.message('Reset Theme To: ' + instance.text) break def load_theme(self): content = FileBrowser(ok_text='Load', directory_select=False, file_editable=True, export_mode=False, file='theme.txt') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.load_theme_finish) self.popup = NormalPopup(title="Select Theme To Load", content=content, size_hint=(0.9, 0.9)) self.popup.open() def load_theme_finish(self, *_): path = self.popup.content.path file = self.popup.content.file self.dismiss_popup() self.filename = os.path.join(path, file) app = App.get_running_app() theme_file = self.filename loaded, data = app.load_theme_data(theme_file) if not loaded: app.message('Could Not Load Theme: ' + str(data)) return else: app.message('Loaded Theme: ' + theme_file) self.theme_backup = data app.theme.data_to_theme(data) self.reload_colors() def save_theme(self): content = FileBrowser(ok_text='Save', directory_select=False, file_editable=True, export_mode=True, file='theme.txt') content.bind(on_cancel=self.dismiss_popup) content.bind(on_ok=self.save_theme_check) self.popup = NormalPopup(title="Select File To Save Theme To", content=content, size_hint=(0.9, 0.9)) self.popup.open() def save_theme_check(self, *_): path = self.popup.content.path file = self.popup.content.file self.dismiss_popup() self.filename = os.path.join(path, file) if os.path.isfile(self.filename): app = App.get_running_app() content = ConfirmPopup(text='Overwrite the file "' + self.filename + '"?', yes_text='Overwrite', no_text="Cancel", warn_yes=True) content.bind(on_answer=self.save_theme_finish) self.popup = NormalPopup(title='Confirm Overwrite', content=content, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4), auto_dismiss=False) self.popup.open() else: self.save_theme_finish() def save_theme_finish(self, instance=None, answer='yes'): self.dismiss_popup() if answer != 'yes': return app = App.get_running_app() theme_file = self.filename data = app.theme_to_data(app.theme) saved = app.save_theme_data(theme_file, data) if saved is True: app.message('Saved Theme') else: app.message('Could Not Save Theme: ' + str(saved)) def reset_theme(self): app = App.get_running_app() app.theme.data_to_theme(self.theme_backup) self.reload_colors() def reload_colors(self): color_element_holder = self.ids['colorElementHolder'] for widget in color_element_holder.children: try: widget.on_color_property() if widget.expanded: widget.toggle_expanded() except: pass def theme_default(self, *_): app = App.get_running_app() app.theme_default() self.reload_colors()