class DownloadedMangaDisplay(ScrollView): def __init__(self, master, language, **kwargs): super().__init__(**kwargs) self.master = master self.effect_cls = "ScrollEffect" self.bar_width = "10dp" self.pos_hint = {"top": .9} self.do_scroll_y = True self.language_folder = self.master.japanese_manga_dir if language == "Japanese" else self.master.english_manga_dir self.manga_folders = [ resource_path(str(dir)) for dir in glob(os.path.join(self.language_folder, "*/")) if os.path.isdir(dir) ] self.manga_cover_imgs = [ resource_path(str(img_path)) for img_path in glob(os.path.join(self.language_folder, "*/*.jpg")) if os.path.isfile(img_path) ] self.manga_tile_data = list( zip(self.manga_folders, self.manga_cover_imgs)) # This grid acts as a container for the number of manga found and the table with the clickable tiles self.outer_gird = MDGridLayout(rows=2, adaptive_height=True, padding=("0dp", "20dp", "0dp", "20dp"), pos_hint={"top": .8}) self.outer_gird.add_widget( MDLabel(text=f"{len(self.manga_folders)} manga were found", halign="center", pos_hint={ "center_x": .5, "y": .9 })) self.grid = MDStackLayout(adaptive_height=True, orientation="lr-tb", spacing=("20dp", "20dp"), padding=("5dp", "30dp", "5dp", "30dp")) for i in self.manga_tile_data: title, manga_path = os.path.basename(os.path.realpath( i[0])), resource_path(i[0]) print("i: ", i, "title", title, "manga_path: ", manga_path) reload_func = lambda title=title, manga_path=manga_path: self.master.create_manga_reader_chapter_selection( title, manga_path) self.btn = MangaCoverTile(source=i[1], text=title, size_hint=(.25, .25), on_release=partial( kill_screen, "Manga Reader Chapter Selection", reload_func)) self.grid.add_widget(self.btn) self.outer_gird.add_widget(self.grid) self.add_widget(self.outer_gird)
def __init__(self, master, **kwargs): super().__init__(**kwargs) self.master = master self.manga_data = self.master.manga_data self.downloader_links_methods = { "manganelo": MangaNelo.download_manga, "kissmanga": KissManga.download_manga, "rawdevart": RawDevArt.download_manga, "senmanga": SenManga.download_manga } self.effect_cls = "ScrollEffect" self.bar_width = "10dp" self.pos_hint = {"top": .9} # This grid acts as a container for the number of manga found and the table with the clickable tiles self.outer_gird = MDGridLayout(rows=2, adaptive_height=True, padding=("0dp", "20dp", "0dp", "20dp"), pos_hint={"top": .8}) self.outer_gird.add_widget( MDLabel(text=f"{len(self.manga_data)} manga were found", halign="center", pos_hint={ "center_x": .5, "y": .9 })) # This grid acts a table to store all found manga self.grid = MDStackLayout(adaptive_height=True, orientation="lr-tb", spacing=("20dp", "20dp"), padding=("5dp", "30dp", "5dp", "30dp")) for title, links_tuple in self.manga_data.items(): self.btn = MangaCoverTile(source=links_tuple[1], text=title, on_release=partial( self.make_request, title), size_hint=(.25, .25)) self.grid.add_widget(self.btn) # Checks to see if any manga were found; An empty dict means no manga were found with the inputted text if self.manga_data == {}: self.grid.add_widget( MDLabel(text="No Manga found", halign="center", pos_hint={ "center_x": .5, "center_y": .5 })) self.outer_gird.add_widget(self.grid) self.add_widget(self.outer_gird)
class MangaReaderChapterSelection(ScrollView): def __init__(self, master, title, manga_path, **kwargs): super().__init__(**kwargs) self.master = master self.manga_path = resource_path(manga_path) self.manga_title = title self.effect_cls = "ScrollEffect" self.bar_width = "10dp" self.pos_hint = {"top": .9} self.padding = ( "20dp", "0dp", "20dp", "0dp" ) # padding: [padding_left, padding_top, padding_right, padding_bottom] # Get all the chapter directories and sort them alphanumerically self.chapter_dirs = os_sorted([ os.path.abspath(resource_path(dir)) for dir in glob(os.path.join(self.manga_path, "*/")) if os.path.isdir(dir) ]) self.grid = MDStackLayout(adaptive_height=True, padding=("30dp", "50dp", "30dp", "0dp"), spacing="20dp", pos_hint={"center_x": .5}) # Loop to create buttons for each chapter of a manga for chapter_dir in self.chapter_dirs: chapter_name = os.path.basename(resource_path(chapter_dir)) def reload_func(manga_title=self.manga_title, name=chapter_name, path=chapter_dir): self.master.create_manga_reader(manga_title, name, path) self.chapter_btn = MDRectangleFlatButton( text=chapter_name, pos_hint={ "center_x": .5, "center_y": .8 }, on_release=partial(kill_screen, "Manga Reader Carousel", reload_func), ) self.grid.add_widget(self.chapter_btn) self.add_widget(self.grid)
def Name_changed(self, val, *args): ''' Name is changed as soon as the text is altered to allow lookup while other properties are changed on defocus. ''' # don't do any lookups if we are initialising if not self.check_ambiguity: return # lookup OTs = Component.get('ObservingList').lookup_OTs(val) # if unique match, fill in if len(OTs) == 1: self.exact_match(val + '/' + OTs[0]) return # clear all but name field to signal ambiguity for p in set(self.props) - {'Name'}: setattr(self, p, '') if len(OTs) == 0: # no match so set name self.Name = val self.new_values['Name'] = val self.check_for_change() return self.choose_OT = MDStackLayout(pos_hint={ 'x': .1, 'top': 1 }, spacing=[20, 20]) self.app.gui.add_widget(self.choose_OT) for ot in OTs: self.choose_OT.add_widget( MDTextButton(text=ot, on_press=partial(self.exact_match, val + '/' + ot)))
class DSO(Component): save_settings = ['show_DSO'] Name = StringProperty('') Con = StringProperty('') RA = StringProperty('') Dec = StringProperty('') OT = StringProperty('') Mag = StringProperty('') Diam = StringProperty('') Other = StringProperty('') otypes = DictProperty({}) props = ['Name', 'Con', 'OT', 'RA', 'Dec', 'Mag', 'Diam', 'Other'] show_DSO = BooleanProperty(False) check_ambiguity = BooleanProperty( False) # check for ambiguous Names e.g. PN/GG def __init__(self, **kwargs): super().__init__(**kwargs) self.app = App.get_running_app() otypes = Component.get('Catalogues').get_object_types() self.otypes = {k: v['name'] for k, v in otypes.items()} self.dso_panel = DSO_panel(self) self.app.gui.add_widget(self.dso_panel) self.initial_values = {} def on_new_object(self): ''' Called when user clicks new ''' # empty DSO details & store initial values self.update_props() self.initial_values = {p: getattr(self, p) for p in self.props} def on_previous_object(self): ''' Called when user selected object in observations table ''' # Data for new object is in Metadata settings = Component.get('Metadata').get({'Name', 'OT'}) # lookup rest from name and object type full_settings = Component.get('ObservingList').lookup_name( '{:}/{:}'.format(settings.get('Name', ''), settings.get('OT', ''))) # update props if we managed a lookup, else use metadata values if full_settings.get('Name', ''): self.update_props(settings=full_settings) else: self.update_props(settings=settings) # store initial values so we can check for changes self.initial_values = {p: getattr(self, p) for p in self.props} def new_DSO_name(self, settings): ''' Called when user selects a name from the DSO table. What we do depends on whether we already have an object loaded, in which case we note the change of name so that it can be confirmed on save. ''' # lookup rest full_settings = Component.get('ObservingList').lookup_name( '{:}/{:}'.format(settings.get('Name', ''), settings.get('OT', ''))) # update DSO properties self.update_props(settings=full_settings) # if stack is empty, treat this as a new observation if Component.get('Stacker').is_empty(): logger.info('new DSO from table {:}'.format(full_settings)) self.initial_values = {p: getattr(self, p) for p in self.props} else: logger.info('user changes DSO name {:}'.format(full_settings)) @logger.catch() def Name_changed(self, val, *args): ''' Name is changed as soon as the text is altered to allow lookup while other properties are changed on defocus. ''' # don't do any lookups if we are initialising if not self.check_ambiguity: return # lookup OTs = Component.get('ObservingList').lookup_OTs(val) # if unique match, fill in if len(OTs) == 1: self.exact_match(val + '/' + OTs[0]) return # clear all but name field to signal ambiguity for p in set(self.props) - {'Name'}: setattr(self, p, '') if len(OTs) == 0: # no match so set name self.Name = val self.new_values['Name'] = val self.check_for_change() return self.choose_OT = MDStackLayout(pos_hint={ 'x': .1, 'top': 1 }, spacing=[20, 20]) self.app.gui.add_widget(self.choose_OT) for ot in OTs: self.choose_OT.add_widget( MDTextButton(text=ot, on_press=partial(self.exact_match, val + '/' + ot))) def exact_match(self, m, *args): if hasattr(self, 'choose_OT'): self.choose_OT.clear_widgets() del self.choose_OT logger.debug('Found match: {:}'.format(m)) self.update_props( settings=Component.get('ObservingList').lookup_name(m), update_name_field=False, initialising=False) self.new_values['Name'] = self.Name self.check_for_change() @logger.catch() def update_props(self, settings=None, update_name_field=True, initialising=True): ''' update DSO properties, causing display update ''' if initialising: self.check_ambiguity = False if settings is None: settings = {} self.Name = settings.get('Name', '') self.RA = str(RA(settings.get('RA', math.nan))) self.Dec = str(Dec(settings.get('Dec', math.nan))) self.Con = settings.get('Con', '') self.OT = settings.get('OT', '') self.Mag = float_to_str(settings.get('Mag', '')) self.Other = settings.get('Other', '') self.Diam = arcmin_to_str(str_to_arcmin(str(settings.get('Diam', '')))) # update new values to reflect changes self.new_values = {p: getattr(self, p) for p in self.props} if update_name_field: self.dso_panel.name_field.text = self.Name self.check_for_change() self.check_ambiguity = True def OT_changed(self, widget): ''' For the moment we allow any object type but in the future could check if one of known types and allow user to introduce a new type via a dialog ''' widget.current_hint_text_color = self.app.hint_color ot = widget.text.upper() if len(ot) > 3: widget.current_hint_text_color = [1, 0, 0, 1] else: self.OT = ot self.new_values['OT'] = ot self.check_for_change() def Con_changed(self, widget): ''' Likewise, we should check constellations in future ''' widget.current_hint_text_color = self.app.hint_color con = widget.text.upper() if len(con) > 3: widget.current_hint_text_color = [1, 0, 0, 1] else: self.Con = con self.new_values['Con'] = con self.check_for_change() @logger.catch() def RA_changed(self, widget): ''' ''' widget.current_hint_text_color = self.app.hint_color RA_str = widget.text.strip() if not RA_str: return ra_deg = RA.parse(RA_str) if ra_deg is None: widget.current_hint_text_color = [1, 0, 0, 1] else: self.RA = '-' # need to force an update self.RA = str(RA(ra_deg)) self.new_values['RA'] = self.RA self.check_for_change() def Dec_changed(self, widget): widget.current_hint_text_color = self.app.hint_color Dec_str = widget.text.strip() if not Dec_str: return dec_deg = Dec.parse(Dec_str) if dec_deg is None: widget.current_hint_text_color = [1, 0, 0, 1] else: self.Dec = '-' # need to force an update self.Dec = str(Dec(dec_deg)) self.new_values['Dec'] = self.Dec self.check_for_change() def Mag_changed(self, widget): ''' should be a int or float ''' mag_str = widget.text.strip() if not mag_str: return try: mag = str(float(mag_str)) self.Mag = '-' self.Mag = mag self.new_values['Mag'] = mag_str self.check_for_change() except: widget.invalid = True def Diam_changed(self, widget): ''' Suffix can be d or degree symbol, ' or " assume arcmin if no suffix ''' diam = widget.text.strip() if not diam: return try: # normalise diam by converting to arcmin then back to string diam = arcmin_to_str(str_to_arcmin(diam)) self.new_values['Diam'] = diam self.Diam = diam self.check_for_change() except: logger.warning('invalid format for diameter {:}'.format(diam)) widget.invalid = True def Other_changed(self, widget): self.Other = widget.text.strip() self.new_values['Other'] = self.Other self.check_for_change() @logger.catch() def check_for_change(self): ''' Check if any property has changed ''' changes = [] for k, v in self.initial_values.items(): if k in self.new_values and self.new_values[k] != v: changes += [True] # tell gui about any changes self.app.gui.has_changed('DSO', any(changes)) @logger.catch() def on_save_object(self): ''' On saving we ensure that Name, OT and Con is saved as these appear in the previous object table; other props don't need to be saved as they are looked up from the DSO database on each load. ''' Component.get('Metadata').set({ 'Name': self.Name.strip(), 'OT': self.OT, 'Con': self.Con }) # prepare props in canonical format props = { 'Name': self.Name.strip(), 'OT': self.OT.strip(), 'Con': self.Con.strip(), 'RA': RA.parse(self.RA), 'Dec': Dec.parse(self.Dec), 'Diam': str_to_arcmin(self.Diam), 'Mag': str_to_float(self.Mag), 'Other': self.Other } # get catalogue to check if anything has changed and update user objects if necc. Component.get('Catalogues').check_update(props) def current_object_coordinates(self): ''' Called by platesolver ''' if self.RA and self.Dec: return (float(RA(self.RA)), float(Dec(self.Dec))) return None, None
def goToStreams(self, *a): "Go to a page wherein we can list user-modifiable services." self.streamsEditPanel.clear_widgets() layout = self.streamsEditPanel bar = BoxLayout(spacing=10, adaptive_height=True, size_hint=(1, None)) stack = StackLayout(spacing=10, adaptive_height=True, size_hint=(1, None)) layout.add_widget(bar) layout.add_widget(MDToolbar(title="My Streams")) layout.add_widget(stack) def upOne(*a): self.gotoMainScreen() btn1 = Button(text='Up') btn1.bind(on_press=upOne) bar.add_widget(btn1) bar.add_widget(self.makeBackButton()) btn2 = Button(text='Create a Stream') btn2.bind(on_press=self.promptAddStream) stack.add_widget(btn2) def f(selection): if selection: dn = 'file:' + os.path.basename(selection) while dn in daemonconfig.userDatabases: dn = dn + '2' try: daemonconfig.loadUserDatabase(selection, dn) self.editStream(dn) except: logging.exception(dn) self.openFM.close() #This lets us view notebook files that aren't installed. def promptOpen(*a): try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") from .kivymdfmfork import MDFileManager from . import directories self.openFM = MDFileManager(select_path=f) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) btn1 = Button(text='Open Book File') btn1.bind(on_press=promptOpen) stack.add_widget(btn1) def goHere(): self.screenManager.current = "Streams" self.backStack.append(goHere) self.backStack = self.backStack[-50:] layout.add_widget(MDToolbar(title="Open Streams:")) try: s = daemonconfig.userDatabases time.sleep(0.5) for i in s: layout.add_widget(self.makeButtonForStream(i)) try: for j in daemonconfig.userDatabases[i].connectedServers: if daemonconfig.userDatabases[i].connectedServers[ j] > (time.time() - (10 * 60)): w = 'online' else: w = 'idle/offline' layout.add_widget( self.saneLabel(j[:28] + ": " + w, layout)) except: logging.exception("Error showing node status") except Exception: logging.info(traceback.format_exc()) self.screenManager.current = "Streams"
def editStream(self, name): if not name in daemonconfig.userDatabases: self.goToStreams() db = daemonconfig.userDatabases[name] c = db.config try: c.add_section("Service") except: pass try: c.add_section("Info") except: pass self.streamEditPanel.clear_widgets() topbar = BoxLayout(size_hint=(1, None), adaptive_height=True, spacing=5) stack = StackLayout(size_hint=(1, None), adaptive_height=True, spacing=5) def upOne(*a): self.goToStreams() btn1 = Button(text='Up') btn1.bind(on_press=upOne) topbar.add_widget(btn1) topbar.add_widget(self.makeBackButton()) self.streamEditPanel.add_widget(topbar) self.streamEditPanel.add_widget(MDToolbar(title=name)) def goHere(): self.editStream(name) self.backStack.append(goHere) self.backStack = self.backStack[-50:] btn2 = Button(text='Notebook View') def goPosts(*a): self.gotoStreamPosts(name) btn2.bind(on_press=goPosts) stack.add_widget(btn2) btn2 = Button(text='Feed View') def goPosts(*a): self.gotoStreamPosts(name, parent=None) btn2.bind(on_press=goPosts) stack.add_widget(btn2) btn2 = Button(text='Stream Settings') def goSettings(*a): self.editStreamSettings(name) btn2.bind(on_press=goSettings) stack.add_widget(btn2) if name.startswith('file:'): btn2 = Button(text='Close Stream') def close(*a): daemonconfig.closeUserDatabase(name) self.goToStreams() btn2.bind(on_press=close) stack.add_widget(btn2) importData = Button(text="Import Data File") def promptSet(*a): try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") def f(selection): if selection: def f2(x): if x: with daemonconfig.userDatabases[name]: with open(selection) as f: daemonconfig.userDatabases[ name].importFromToml(f.read()) daemonconfig.userDatabases[name].commit() self.askQuestion("Really import?", "yes", cb=f2) self.openFM.close() from .kivymdfmfork import MDFileManager from . import directories self.openFM = MDFileManager(select_path=f) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) importData.bind(on_release=promptSet) stack.add_widget(importData) export = Button(text="Export All Posts") def promptSet(*a): from .kivymdfmfork import MDFileManager from .. import directories try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") def f(selection): if selection: if not selection.endswith(".toml"): selection = selection + ".toml" def g(a): if a == 'yes': r = daemonconfig.userDatabases[ name].getDocumentsByType('post', parent='') data = daemonconfig.userDatabases[ name].exportRecordSetToTOML( [i['id'] for i in r]) logging.info("Exporting data to:" + selection) with open(selection, 'w') as f: f.write(data) self.openFM.close() if os.path.exists(selection): self.askQuestion("Overwrite?", 'yes', g) else: g('yes') #Autocorrect had some fun with the kivymd devs self.openFM = MDFileManager(select_path=f, save_mode=(name + '.toml')) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) export.bind(on_release=promptSet) stack.add_widget(export) self.streamEditPanel.add_widget(stack) #Show recent changes no matter where they are in the tree. #TODO needs to be hideable for anti-spoiler purposes in fiction. self.streamEditPanel.add_widget(MDToolbar(title="Recent Changes:")) for i in daemonconfig.userDatabases[name].getDocumentsByType( 'post', orderBy='arrival DESC', limit=5): x = self.makePostWidget(name, i, includeParent=True) self.streamEditPanel.add_widget(x) self.screenManager.current = "EditStream"
def gotoMainScreen(self): self.mainScreenlayout.clear_widgets() layout=self.mainScreenlayout label = MDToolbar(title="Drayer Journal") label.icon=os.path.join(os.path.dirname(os.path.abspath("__file__")),'assets','icons',"Craftpix.net",'medival','cart.jpg') layout.add_widget(label) stack = StackLayout(size_hint=(1,None),adaptive_height=True,spacing=5) l = self.saneLabel("Notice: streams may be stored on the SD card. Some other apps may be able to read them",layout) layout.add_widget(l) btn1 = Button(text='My Streams') stack.add_widget(btn1) btn1.bind(on_press=self.goToStreams) btn1 = Button(text='Discover Services') btn1.bind(on_press=self.goToDiscovery) stack.add_widget(btn1) btn5 = Button(text='Settings+Tools') btn5.bind(on_press=self.goToSettings) stack.add_widget(btn5) btn6 = Button(text='Help') btn6.bind(on_press=self.goToHelp) stack.add_widget(btn6) layout.add_widget(stack) label = MDToolbar(title="Bookmarks") layout.add_widget(label) for i in sorted(list(daemonconfig.getBookmarks().keys())): bw =BoxLayout(orientation='horizontal', spacing=10, size_hint=(1, None),adaptive_height=True) b = Button(text=i[:32]) bd = Button(text="Del") def dlbm(*a,i=i): def f(a): if a: daemonconfig.setBookmark(a,None,None) self.gotoMainScreen() self.askQuestion("Delete Bookmark?",i,f) bd.bind(on_press=dlbm) def bm(*a,i=i): self.gotoBookmark(i) b.bind(on_press=bm) bw.add_widget(b) bw.add_widget(bd) layout.add_widget(bw) self.screenManager.current = "Main"
def callback_1(self, *args, **kwargs): sl = MDStackLayout() sl.add_widget(MDTextField(hint_text='Enter something')) sl.add_widget(MDRaisedButton(text="I AM A BUTTON")) self.boxlayout2.add_widget(sl)
class MangaCoverContainer(ScrollView): def __init__(self, master, **kwargs): super().__init__(**kwargs) self.master = master self.manga_data = self.master.manga_data self.downloader_links_methods = { "manganelo": MangaNelo.download_manga, "kissmanga": KissManga.download_manga, "rawdevart": RawDevArt.download_manga, "senmanga": SenManga.download_manga } self.effect_cls = "ScrollEffect" self.bar_width = "10dp" self.pos_hint = {"top": .9} # This grid acts as a container for the number of manga found and the table with the clickable tiles self.outer_gird = MDGridLayout(rows=2, adaptive_height=True, padding=("0dp", "20dp", "0dp", "20dp"), pos_hint={"top": .8}) self.outer_gird.add_widget( MDLabel(text=f"{len(self.manga_data)} manga were found", halign="center", pos_hint={ "center_x": .5, "y": .9 })) # This grid acts a table to store all found manga self.grid = MDStackLayout(adaptive_height=True, orientation="lr-tb", spacing=("20dp", "20dp"), padding=("5dp", "30dp", "5dp", "30dp")) for title, links_tuple in self.manga_data.items(): self.btn = MangaCoverTile(source=links_tuple[1], text=title, on_release=partial( self.make_request, title), size_hint=(.25, .25)) self.grid.add_widget(self.btn) # Checks to see if any manga were found; An empty dict means no manga were found with the inputted text if self.manga_data == {}: self.grid.add_widget( MDLabel(text="No Manga found", halign="center", pos_hint={ "center_x": .5, "center_y": .5 })) self.outer_gird.add_widget(self.grid) self.add_widget(self.outer_gird) # Note: the param tile acts as a button instance def make_request(self, title, tile): # Calls the appropriate downloader based on the selected site and starts a thread to prevent kivy event loop from locking download_thread_target = partial( self.downloader_links_methods.get(self.master.downloader), tile, title, self.manga_data.get(title)) download_thread = Thread(name=f"Download Thread {title}", target=download_thread_target) # Flag used to ensure that the same manga is not downloaded while it is already being downloaded if self.master.download_threads.get( title, None ) == None: # If a manga is already being downloaded it will not return None self.master.download_threads.update({title: download_thread}) self.master.currently_downloading = True tile.progressbar.opacity = 1 # Creates the directory for that manga within the manga root and changes to it create_manga_dirs(self.master.downloader, title) download_thread.start() else: display_message(f"{title} is already downloading")