class SongsList(App): def __init__(self, **kwargs): """ Installing all the required widgets for the layout of kivy app """ super().__init__(**kwargs) self.song_list = SongList() # Bottom status label and Top count label self.top_label = Label(text="", id="count_label") self.status_label = Label(text="") # layout widget left part self.sort_label = Label(text="Sort by:") # Putting default sort method as Artist self.spinner = Spinner(text='Artist', values=('Artist', 'Title', 'Year', 'Required')) self.add_song_label = Label(text="Add New Song...") self.title_label = Label(text="Title:") self.title_text_input = TextInput(write_tab=False, multiline=False) self.artist_label = Label(text="Artist:") self.artist_text_input = TextInput(write_tab=False, multiline=False) self.year_label = Label(text="Year:") self.year_text_input = TextInput(write_tab=False, multiline=False) # To add and clear for the bottom widget self.add_song_button = Button(text='Add Song') self.clear_button = Button(text='Clear') def songs_sort(self, *args): """ The code that handle the sorts base on the click of the spinner """ self.song_list.sort(self.spinner.text) self.root.ids.rightLayout.clear_widgets() self.right_widgets() def build(self): """ opening the kivy app and putting a little object """ self.title = "Songs List 2.0" self.root = Builder.load_file('app.kv') self.song_list.load_songs() self.song_list.sort('Artist') self.building_widgets() self.right_widgets() return self.root def building_widgets(self): """ left layout creation base on widgets created in an order """ self.root.ids.leftLayout.add_widget(self.sort_label) self.root.ids.leftLayout.add_widget(self.spinner) self.root.ids.leftLayout.add_widget(self.add_song_label) self.root.ids.leftLayout.add_widget(self.title_label) self.root.ids.leftLayout.add_widget(self.title_text_input) self.root.ids.leftLayout.add_widget(self.artist_label) self.root.ids.leftLayout.add_widget(self.artist_text_input) self.root.ids.leftLayout.add_widget(self.year_label) self.root.ids.leftLayout.add_widget(self.year_text_input) self.root.ids.leftLayout.add_widget(self.add_song_button) self.root.ids.leftLayout.add_widget(self.clear_button) self.root.ids.topLayout.add_widget(self.top_label) # Setting on click for sorting spinner, add button and clear button self.spinner.bind(text=self.songs_sort) self.add_song_button.bind(on_release=self.add_song_handler) self.clear_button.bind(on_release=self.clear_fields) def right_widgets(self): """ Building right layout with widgets based on the list we created. """ # Sets the count label self.top_label.text = "To Learn: " + str( self.song_list.get_required_songs_count()) + ". Learned: " + str( self.song_list.get_learned_songs_count()) # Goes through each song in the list and check if it learned or required and setts color based on that for song in self.song_list.songs: # n = Learned if song[0].status == 'n': song_button = Button(text='"' + song[0].title + '"' + " by " + song[0].artist + " (" + str(song[0].year) + ") " "(Learned)", id=song[0].title) song_button.background_color = [88, 89, 0, 0.3] # y = required to learn else: song_button = Button(text='"' + song[0].title + '"' + " by " + song[0].artist + " (" + str(song[0].year) + ")", id=song[0].title) song_button.background_color = [0, 88, 88, 0.3] # Setting on click for the buttons created song_button.bind(on_release=self.click_handler) self.root.ids.rightLayout.add_widget(song_button) def click_handler(self, button): """ Handles on click for each song button created """ # if button user clicked is learned change it to required to learn and update the status bar if self.song_list.get_song(button.id).status == 'n': self.song_list.get_song(button.id).status = 'y' self.root.ids.bottomLayout.text = "You need to learn " + str( self.song_list.get_song(button.id).title) # if button user clicked is Required to learn change it to learned and update the status bar else: self.song_list.get_song(button.id).status = 'n' self.root.ids.bottomLayout.text = "You have learned " + str( self.song_list.get_song(button.id).title) # Update the sorting and reloads the right layout self.songs_sort() self.root.ids.rightLayout.clear_widgets() self.right_widgets() def clear_fields(self, *args): """ Handles clearing up all the text fields and status bar """ self.title_text_input.text = "" self.artist_text_input.text = "" self.year_text_input.text = "" self.root.ids.bottomLayout.text = "" def add_song_handler(self, *args): """ This method handles all the error checking for user input on text field and creates a Song object """ # Checks if all the input fields are complete if not error text will be displayed if str(self.title_text_input.text).strip() == '' or str( self.artist_text_input.text).strip() == '' or str( self.year_text_input.text).strip() == '': self.root.ids.bottomLayout.text = "All fields must be completed" else: try: # If year is negative if int(self.year_text_input.text) < 0: self.root.ids.bottomLayout.text = "Please enter a valid number" # If all the criteria matches it creates a Song object in song_list class else: self.song_list.add_song(self.title_text_input.text, self.artist_text_input.text, int(self.year_text_input.text)) self.song_list.sort(self.spinner.text) self.clear_fields() self.root.ids.rightLayout.clear_widgets() self.right_widgets() # String Error checking for year input except ValueError: self.root.ids.bottomLayout.text = "Please enter a valid number" def stop(self): # By closing, all the data form the list will be saved to to songs.csv file self.song_list.save_file()
class SongsToLearnApp(App): def __init__(self, **kwargs): super().__init__(**kwargs) self.song_list = SongList() self.requireSong = 0 self.learnedSong = 0 def build(self): self.title = "Song To learn 2.0" # Add the title of the program self.root = Builder.load_file('app.kv') # Reference kivy file self.song_list.load_song() # Using class method to load CSV self.show_song() self.sorting(self.root.ids.sort_option.text) return self.root def show_song(self): # Display Songs in GUI self.requireSong = 0 self.learnedSong = 0 for i in self.song_list.song: if i.require == 'y': # y means need to learn song song_button = Button( text='' + '"' + i.title + '"' + " by " + i.artist + " (" + i.year + ")", id=i.title) # Display format for need to learn song song_button.background_color = [88, 89, 0, 0.3 ] # Button background colour self.requireSong += 1 else: song_button = Button( text='' + '"' + i.title + '"' + " by " + i.artist + " (" + i.year + ") (learned)", id=i.title) # Display format for learned song song_button.background_color = [0, 88, 88, 0.3 ] # Button background colour self.learnedSong += 1 song_button.bind(on_release=self.select) self.root.ids.all_song.add_widget(song_button) # Display learned and to learn song self.root.ids.title_learned.text = "To learn: {}, Learned: {}".format( self.requireSong, self.learnedSong) def select(self, button): # Display selected song if self.song_list.get_song( button.id).require == 'y': # Mark song as learned self.song_list.get_song(button.id).require = 'n' self.root.ids.program_detail.text = "{} is learned.".format( button.id) else: self.song_list.get_song( button.id).require = 'y' # Mark song as unlearn self.root.ids.program_detail.text = "{} need to learn.".format( button.id) # Display selected song format self.root.ids.program_detail.color = ANNOUNCEMENT # Set label colour self.sorting(self.root.ids.sort_option.text) self.root.ids.all_song.clear_widgets() # Clear widgets self.show_song() def sorting(self, chosen): # Sort song function available_choice = chosen if available_choice == 'Title': # Sort the song by Title self.song_list.sort(0) elif available_choice == 'Artist': # Sort the song by Artist self.song_list.sort(1) elif available_choice == 'Year': # Sort the song by Year self.song_list.sort(2) else: self.song_list.sort(3) # Sort the song by Require self.root.ids.sort_option.clear_widgets() self.root.ids.all_song.clear_widgets() self.show_song() def add_song(self): # Add new song to the list title = self.root.ids.title_fill.text artist = self.root.ids.artist_fill.text year = self.year_check() if title == '' or artist == '' or year == '': # No input validation self.root.ids.program_detail.color = ERROR_COLOUR self.root.ids.program_detail.text = 'Please fill every box' elif year == "string": # Year validation self.root.ids.program_detail.color = ERROR_COLOUR self.root.ids.program_detail.text = 'Year must be an integer' elif year < 0: # Year validation self.root.ids.program_detail.color = ERROR_COLOUR self.root.ids.program_detail.text = 'Year must have at least 4 digits' else: song_title = self.root.ids.title_fill.text song_artist = self.root.ids.artist_fill.text song_year = self.root.ids.year_fill.text song_input = Song(song_title, song_artist, song_year, 'y') self.song_list.add_song(song_input) # Add new song to song list self.root.ids.all_song.clear_widgets() self.clear_all() self.root.ids.program_detail.color = ANNOUNCEMENT self.root.ids.program_detail.text = 'A song have added to the song list' self.show_song() def year_check(self): # Validate the song year input try: year = int(self.root.ids.year_fill.text) return year except ValueError: year = 'string' return year def clear_all(self): # Clear input in text input function self.root.ids.title_fill.text = '' self.root.ids.artist_fill.text = '' self.root.ids.year_fill.text = '' self.root.ids.program_detail.text = '' def stop(self): self.song_list.save_song( ) # Update CSV file after the user close the program
# test empty SongList song_list = SongList() print(song_list) assert len(song_list.song) == 0 # test loading songs song_list.load_song() print(song_list) assert len(song_list.song) > 0 # assuming CSV file is not empty # TODO: add tests below to show the various required methods work as expected # test sorting songs print("Sorting by year") assert song_list.sort('year') print("Sorting by title") assert song_list.sort('title') # test adding a new Song song3 = Song('Hero', 'Enrique Iglesias', 2008, 'y') assert song_list.add_song(song3) # test get_song() assert song_list.get_song('Hero') # test getting the number of required and learned songs (separately) assert song_list.count_learned() assert song_list.count_require() # test saving songs (check CSV file manually to see results) song_list.save_song()
""" (incomplete) Tests for SongList class """ from songlist import SongList from song import Song # test empty SongList song_list = SongList() print(song_list) assert len(song_list.songs) == 0 # test loading songs song_list.load_songs('songs.csv') print(song_list) assert len(song_list.songs) > 0 # assuming CSV file is not empty # TODO: add tests below to show the various required methods work as expected # test sorting songs song_list.sort_songs() # test adding a new Song song_list.add_song() # test get_song() song_list.get_song() print(song_list) # test getting the number of required and learned songs (separately) # test saving songs (check CSV file manually to see results) song_list.save_songs()
class SongsToLearnApp(App): """ Main program - Kivy app to demo song list system """ status_text = StringProperty() status_text2 = StringProperty() def __init__(self, **kwargs): """ :Parameter:**kwargs Initiate the self.song_list to SongList() class :return:None """ super(SongsToLearnApp, self).__init__(**kwargs) self.song_list = SongList() def build(self): """ Build the Kivy GUI :return: reference to the root Kivy widget """ self.title = "Vaibhav Jain - Song List" self.root = Builder.load_file('app.kv') self.create_entry_buttons() #Display the entry at start return self.root def create_entry_buttons(self): """ Create the entry buttons and add them to the GUI :return: None """ num_song = 0 learned_num = 0 self.root.ids.entriesBox.clear_widgets() for each in self.song_list.list_song: # create a button for each song entry num_song += 1 #Add up the number of song for every song looped in the list if each.status == "n": temp_button = Button(text="{} by {} ({}) ({})".format( each.title, each.artist, each.year, "learned" )) #Format the text for learned song in temp_button else: temp_button = Button(text="{} by {} ({}) ".format( each.title, each.artist, each.year)) temp_button.bind(on_release=self.press_entry) temp_button.bind( on_release=each.markSonglearned ) #Mark the song chosen from the temp_button by clicking it learnt #Also note , by clicking refresh it will help self.root.ids.entriesBox.add_widget(temp_button) if each.status == "n": temp_button.background_color = [ 1, 0, 0, 1 ] #turn background color into red learned_num += 1 else: temp_button.background_color = [ 2, 1, 1, 2 ] #turn background color button into pink self.status_text = "To learn:{} learned :{}".format( num_song - learned_num, learned_num) def press_entry(self, instance): """ Handler for pressing entry buttons :param instance: the Kivy button instance :return: None """ name = instance.text self.status_text2 = "You have not learned {}".format( (self.song_list.get_song(name)) ) # This would update the bottom label if the user press on the temp_button instance.state = 'normal' #Note that I failed to update the bottom label text. def clear_text(self): """ Clear any buttons that have been selected (visually) and reset status text :return: None """ # use the .children attribute to access all widgets that are "in" another widget self.root.ids.Title.text = "" self.root.ids.Artist.text = "" #Empty the text boxes self.root.ids.Year.text = "" for instance in self.root.ids.entriesBox.children: #Normalise the button state instance.state = 'normal' self.root.ids.statusLabel2.text = "" #Empty the status label text box def add_song(self): """ Handler for pressing the add button :return: None """ if self.root.ids.Title.text == "" or self.root.ids.Artist.text == "" or self.root.ids.Year.text == "": self.root.ids.statusLabel.text = "All fields must be required" #Displayed when user does not fill in all the field and press Add Song return try: YEAR = int( self.root.ids.Year.text) #Make sure the year text is a number self.song_list.AddSongToSongList( self.root.ids.Title.text, self.root.ids.Artist.text, self.root.ids.Year.text, "n") #Return to function from songlist temp_button = Button(text="{} by {} ({}) ({})".format( self.root.ids.Title.text, self.root.ids.Artist.text, self.root.ids.Year.text, "y")) temp_button.bind(on_release=self.press_entry) temp_button.background_color = [ 1, 0, 0, 2 ] #Append the new temp button with color pink self.root.ids.entriesBox.add_widget( temp_button) #Adding widget temp_button self.root.ids.Title.text = "" self.root.ids.Artist.text = "" #Empty the text boxes self.root.ids.Year.text = "" except ValueError: self.status_text2 = "Please enter a valid number" #Display status label at the buttom def on_stop(self): """ Saves the songs to the csv file by calling the save_songs :return: None """ self.song_list.save_songs() def press_refresh(self): """ Refresh the page whether the user learn songs or choose to sort by title or artist or year from the spinner :return: None """ self.song_list.sort_songs( self.root.ids.spinner.text ) #Sort_songs based on the text on the spinner self.create_entry_buttons( ) #Recreate the entry button whenever you click #Note & Comment : This Button supposes not to be included in the Gui layout and could be worked independently for each temp button and for the spinner , it does not undo the songs that have been learned. #Keeping button helps a more convenient way to show the result after pressing it . def clear_fields(self): """ Clear the Title , Artist , Year boxes :return: None """ self.root.ids.Title.text = "" self.root.ids.Artist.text = "" self.root.ids.Year.text = ""