def export_finish(self): app = App.get_running_app() if len(self.collage.images) > 8: message = 'Exporting Collage, This May Take Several Minutes, Please Wait...' else: message = 'Exporting Collage, Please Wait...' self.popup = ScanningPopup(title=message, auto_dismiss=False, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.popup.scanning_text = '' self.popup.button_text = 'Ok' self.popup.open() self.exportthread = threading.Thread(target=self.export_process) self.exportthread.start()
def filechooser_popup(self): app = App.get_running_app() content = FileBrowser(ok_text='Export', path=app.last_browse_folder, 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 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()
class CollageScreen(Screen): #Display variables 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 collage_type = StringProperty('Pile') sort_reverse_button = StringProperty('normal') resolution = StringProperty('Medium') aspect = NumericProperty(1.3333) aspect_text = StringProperty('4:3') filename = StringProperty('') export_scale = 1 collage_background = ListProperty([0, 0, 0, 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() collage_type_select = ObjectProperty() add_remove = ObjectProperty() collage = ObjectProperty() exportthread = ObjectProperty() #Variables relating to the photo list view on the left sort_method = StringProperty('Name') #Current album sort method sort_reverse = BooleanProperty(False) def on_collage_background(self, *_): self.collage.collage_background = self.collage_background def deselect_images(self): self.collage.deselect_images() def delete_selected(self): self.collage.delete_selected() def clear_collage(self): self.collage.clear() def add_all(self): self.collage.add_photos(list(self.photos)) 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() if len(self.collage.images) > 8: message = 'Exporting Collage, This May Take Several Minutes, Please Wait...' else: message = 'Exporting Collage, Please Wait...' self.popup = ScanningPopup(title=message, auto_dismiss=False, size_hint=(None, None), size=(app.popup_x, app.button_scale * 4)) self.popup.scanning_text = '' self.popup.button_text = 'Ok' self.popup.open() self.exportthread = threading.Thread(target=self.export_process) self.exportthread.start() def export_process(self, *_): scanning = 0 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.collage.images: scanning = scanning + 10 if scanning > 100: scanning = 0 self.popup.scanning_percentage = scanning 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: scanning = scanning + 10 if scanning > 100: scanning = 0 self.popup.scanning_percentage = scanning if image.is_full_size: check_images.remove(image) self.collage.show_guides(False) #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.collage exported = self.export_scaled_jpg(collage, self.filename, image_scale=self.export_scale) app = App.get_running_app() self.popup.dismiss() self.popup = None if exported is True: app.message("Exported "+self.filename) else: app.message('Export error: '+exported) self.collage.show_guides(True) 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): self.collage.transform_mode = transform_mode 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.""" position = self.collage.to_widget(*position) self.collage.drop_image(fullpath, position, aspect=aspect) 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: if self.popup.title.startswith('Exporting Collage'): return False 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.database_exists(fullpath) if photoinfo: self.photos.append(photoinfo) elif self.type == 'Tag': self.photos = app.database_get_tag(self.target) else: self.photos = app.database_get_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_collage_type(self, *_): self.set_collage() def set_collage(self): if self.collage: self.collage.clear() collage_holder = self.ids['collageHolder'] collage_holder.clear_widgets() if self.collage_type == 'Pile': self.collage = ScatterCollage() elif self.collage_type == '3': self.collage = GridCollage3() elif self.collage_type == '2x2': self.collage = GridCollage2x2() elif self.collage_type == '5': self.collage = GridCollage5() elif self.collage_type == '2x3': self.collage = GridCollage2x3() elif self.collage_type == '3x2': self.collage = GridCollage3x2() elif self.collage_type == '3x3': self.collage = GridCollage3x3() self.collage.collage_background = self.collage_background collage_holder.add_widget(self.collage) def on_leave(self): """Called when the screen is left. Clean up some things.""" app = App.get_running_app() app.clear_drags() self.clear_collage() collage_holder = self.ids['collageHolder'] collage_holder.clear_widgets() 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.set_collage() 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) self.collage_type_select = CollageTypeDropDown(owner=self) #import variables self.target = app.export_target self.type = app.export_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()