def __init__(self, util, listeners, voice_assistant, state): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.cdutil = CdUtil(util) self.factory = Factory(util) self.bounding_box = util.screen_rect self.layout = BorderLayout(self.bounding_box) self.layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "cd_tracks_screen_title", True, self.layout.TOP) color_dark_light = self.config[COLORS][COLOR_DARK_LIGHT] self.current_cd_drive_name = self.config[CD_PLAYBACK][CD_DRIVE_NAME] self.current_cd_drive_id = self.config[CD_PLAYBACK][CD_DRIVE_ID] self.filelist = self.get_filelist() self.file_menu = FileMenu(self.filelist, util, None, (0, 0, 0), self.layout.CENTER, align=ALIGN_LEFT) self.go_cd_player = listeners[KEY_PLAYER] self.file_menu.add_play_file_listener(self.play_track) Container.add_component(self, self.file_menu) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[KEY_EJECT] = self.eject_cd listeners[KEY_REFRESH] = self.refresh_tracks connected_cd_drives = self.cdutil.get_cd_drives_number() self.navigator = CdTracksNavigator(util, connected_cd_drives, self.layout.BOTTOM, listeners, color_dark_light) Container.add_component(self, self.navigator) self.file_menu.add_left_number_listener( self.navigator.left_button.change_label) self.file_menu.add_right_number_listener( self.navigator.right_button.change_label) self.file_menu.update_buttons() self.page_turned = False self.animated_title = True
def __init__(self, util, get_current_playlist, playlist_provider, listeners, voice_assistant): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.factory = Factory(util) self.bounding_box = util.screen_rect layout = BorderLayout(self.bounding_box) layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "file_browser_screen_title", True, layout.TOP) current_folder = self.util.file_util.current_folder d = {"current_title": current_folder} if self.config[FILE_PLAYBACK][ CURRENT_FILE_PLAYBACK_MODE] == FILE_PLAYLIST: f = self.config[FILE_PLAYBACK][CURRENT_FOLDER] p = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] d = f + os.sep + p self.screen_title.set_text(d) rows = self.config[FILE_BROWSER_ROWS] columns = self.config[FILE_BROWSER_COLUMNS] self.filelist = None playback_mode = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] if not playback_mode: self.config[FILE_PLAYBACK][ CURRENT_FILE_PLAYBACK_MODE] = playback_mode = FILE_AUDIO button_box = pygame.Rect(0, 0, layout.CENTER.w / columns, layout.CENTER.h / rows) if self.config[ALIGN_BUTTON_CONTENT_X] == 'center': location = TOP else: location = self.config[ALIGN_BUTTON_CONTENT_X] icon_box = self.factory.get_icon_bounding_box(button_box, location, self.config[IMAGE_AREA], self.config[IMAGE_SIZE], self.config[PADDING]) icon_box_without_label = self.factory.get_icon_bounding_box( button_box, location, 100, 100, self.config[PADDING], False) if playback_mode == FILE_AUDIO or playback_mode == FILE_RECURSIVE: folder_content = self.util.load_folder_content( current_folder, rows, columns, icon_box, icon_box_without_label) self.filelist = Page(folder_content, rows, columns) elif playback_mode == FILE_PLAYLIST: s = State() s.folder = self.config[FILE_PLAYBACK][CURRENT_FOLDER] s.music_folder = self.config[AUDIO][MUSIC_FOLDER] s.file_name = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] pl = self.get_filelist_items(get_current_playlist) if len(pl) == 0: pl = self.util.load_playlist(s, playlist_provider, rows, columns, (icon_box.w, icon_box.h)) else: pl = self.util.load_playlist_content(pl, rows, columns, (icon_box.w, icon_box.h)) self.filelist = Page(pl, rows, columns) self.file_menu = FileMenu(self.filelist, util, playlist_provider, layout.CENTER, location, icon_box, icon_box_without_label) self.file_menu.parent_screen = self self.file_menu.link_borders = self.link_borders self.file_menu.add_change_folder_listener(self.screen_title.set_text) self.file_menu.add_play_file_listener(listeners[KEY_PLAY_FILE]) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[GO_USER_HOME] = self.file_menu.switch_to_user_home listeners[GO_ROOT] = self.file_menu.switch_to_root listeners[GO_TO_PARENT] = self.file_menu.switch_to_parent_folder self.navigator = FileNavigator(util, layout.BOTTOM, listeners) left = str(self.filelist.get_left_items_number()) right = str(self.filelist.get_right_items_number()) left_button = self.navigator.get_button_by_name(KEY_PAGE_DOWN) right_button = self.navigator.get_button_by_name(KEY_PAGE_UP) self.back_button = self.navigator.get_button_by_name(KEY_BACK) left_button.change_label(left) right_button.change_label(right) self.file_menu.add_left_number_listener(left_button.change_label) self.file_menu.add_right_number_listener(right_button.change_label) self.add_navigator(self.navigator) self.page_turned = False self.animated_title = True self.file_menu.navigator = self.navigator self.add_menu(self.file_menu) self.link_borders() self.back_button.set_selected(True)
class FileBrowserScreen(Screen): """ File Browser Screen """ def __init__(self, util, get_current_playlist, playlist_provider, listeners, voice_assistant): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.factory = Factory(util) self.bounding_box = util.screen_rect layout = BorderLayout(self.bounding_box) layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "file_browser_screen_title", True, layout.TOP) current_folder = self.util.file_util.current_folder d = {"current_title": current_folder} if self.config[FILE_PLAYBACK][ CURRENT_FILE_PLAYBACK_MODE] == FILE_PLAYLIST: f = self.config[FILE_PLAYBACK][CURRENT_FOLDER] p = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] d = f + os.sep + p self.screen_title.set_text(d) rows = self.config[FILE_BROWSER_ROWS] columns = self.config[FILE_BROWSER_COLUMNS] self.filelist = None playback_mode = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] if not playback_mode: self.config[FILE_PLAYBACK][ CURRENT_FILE_PLAYBACK_MODE] = playback_mode = FILE_AUDIO button_box = pygame.Rect(0, 0, layout.CENTER.w / columns, layout.CENTER.h / rows) if self.config[ALIGN_BUTTON_CONTENT_X] == 'center': location = TOP else: location = self.config[ALIGN_BUTTON_CONTENT_X] icon_box = self.factory.get_icon_bounding_box(button_box, location, self.config[IMAGE_AREA], self.config[IMAGE_SIZE], self.config[PADDING]) icon_box_without_label = self.factory.get_icon_bounding_box( button_box, location, 100, 100, self.config[PADDING], False) if playback_mode == FILE_AUDIO or playback_mode == FILE_RECURSIVE: folder_content = self.util.load_folder_content( current_folder, rows, columns, icon_box, icon_box_without_label) self.filelist = Page(folder_content, rows, columns) elif playback_mode == FILE_PLAYLIST: s = State() s.folder = self.config[FILE_PLAYBACK][CURRENT_FOLDER] s.music_folder = self.config[AUDIO][MUSIC_FOLDER] s.file_name = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] pl = self.get_filelist_items(get_current_playlist) if len(pl) == 0: pl = self.util.load_playlist(s, playlist_provider, rows, columns, (icon_box.w, icon_box.h)) else: pl = self.util.load_playlist_content(pl, rows, columns, (icon_box.w, icon_box.h)) self.filelist = Page(pl, rows, columns) self.file_menu = FileMenu(self.filelist, util, playlist_provider, layout.CENTER, location, icon_box, icon_box_without_label) self.file_menu.parent_screen = self self.file_menu.link_borders = self.link_borders self.file_menu.add_change_folder_listener(self.screen_title.set_text) self.file_menu.add_play_file_listener(listeners[KEY_PLAY_FILE]) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[GO_USER_HOME] = self.file_menu.switch_to_user_home listeners[GO_ROOT] = self.file_menu.switch_to_root listeners[GO_TO_PARENT] = self.file_menu.switch_to_parent_folder self.navigator = FileNavigator(util, layout.BOTTOM, listeners) left = str(self.filelist.get_left_items_number()) right = str(self.filelist.get_right_items_number()) left_button = self.navigator.get_button_by_name(KEY_PAGE_DOWN) right_button = self.navigator.get_button_by_name(KEY_PAGE_UP) self.back_button = self.navigator.get_button_by_name(KEY_BACK) left_button.change_label(left) right_button.change_label(right) self.file_menu.add_left_number_listener(left_button.change_label) self.file_menu.add_right_number_listener(right_button.change_label) self.add_navigator(self.navigator) self.page_turned = False self.animated_title = True self.file_menu.navigator = self.navigator self.add_menu(self.file_menu) self.link_borders() self.back_button.set_selected(True) def get_filelist_items(self, get_current_playlist): """ Call player for files in the playlist :return: list of files from playlist """ playlist = get_current_playlist() files = [] if playlist: for n in range(len(playlist)): st = State() st.index = st.comparator_item = n st.file_type = FILE_AUDIO st.file_name = st.url = playlist[n] files.append(st) return files def add_screen_observers(self, update_observer, redraw_observer): """ Add screen observers :param update_observer: observer for updating the screen :param redraw_observer: observer to redraw the whole screen """ Screen.add_screen_observers(self, update_observer, redraw_observer) self.file_menu.add_menu_observers(update_observer, redraw_observer, release=False) self.file_menu.add_change_folder_listener(redraw_observer) self.file_menu.add_menu_navigation_listeners(redraw_observer) self.file_menu.add_left_number_listener(redraw_observer) self.file_menu.add_right_number_listener(redraw_observer) self.navigator.add_observers(update_observer, redraw_observer) def handle_event(self, event): """ Handle screen event :param event: the event to handle """ if event.type == USER_EVENT_TYPE and event.sub_type == SUB_TYPE_KEYBOARD and ( event.action == pygame.KEYUP or event.action == pygame.KEYDOWN): menu_selected = self.file_menu.get_selected_index() navigator_selected = self.navigator.is_selected() if menu_selected != None: self.file_menu.handle_event(event) elif navigator_selected: self.navigator.handle_event(event) else: Screen.handle_event(self, event)
def __init__(self, util, get_current_playlist, playlist_provider, listeners, voice_assistant): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.factory = Factory(util) self.bounding_box = util.screen_rect layout = BorderLayout(self.bounding_box) layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "file_browser_screen_title", True, layout.TOP) color_dark_light = self.config[COLORS][COLOR_DARK_LIGHT] current_folder = self.util.file_util.current_folder d = {"current_title" : current_folder} if self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] == FILE_PLAYLIST: f = self.config[FILE_PLAYBACK][CURRENT_FOLDER] p = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] d = f + os.sep + p self.screen_title.set_text(d) rows = 3 columns = 3 self.filelist = None playback_mode = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] if not playback_mode: self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] = playback_mode = FILE_AUDIO if playback_mode == FILE_AUDIO or playback_mode == FILE_RECURSIVE: folder_content = self.util.load_folder_content(current_folder, rows, columns, layout.CENTER) self.filelist = Page(folder_content, rows, columns) elif playback_mode == FILE_PLAYLIST: s = State() s.folder = self.config[FILE_PLAYBACK][CURRENT_FOLDER] s.music_folder = self.config[AUDIO][MUSIC_FOLDER] s.file_name = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] pl = self.get_filelist_items(get_current_playlist) if len(pl) == 0: pl = self.util.load_playlist(s, playlist_provider, rows, columns) else: pl = self.util.load_playlist_content(pl, rows, columns) self.filelist = Page(pl, rows, columns) self.file_menu = FileMenu(self.filelist, util, playlist_provider, (0, 0, 0), layout.CENTER) Container.add_component(self, self.file_menu) self.file_menu.add_change_folder_listener(self.screen_title.set_text) self.file_menu.add_play_file_listener(listeners[KEY_PLAY_FILE]) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[GO_USER_HOME] = self.file_menu.switch_to_user_home listeners[GO_ROOT] = self.file_menu.switch_to_root listeners[GO_TO_PARENT] = self.file_menu.switch_to_parent_folder self.navigator = Navigator(util, layout.BOTTOM, listeners, color_dark_light) left = str(self.filelist.get_left_items_number()) right = str(self.filelist.get_right_items_number()) self.navigator.left_button.change_label(left) self.navigator.right_button.change_label(right) self.file_menu.add_left_number_listener(self.navigator.left_button.change_label) self.file_menu.add_right_number_listener(self.navigator.right_button.change_label) Container.add_component(self, self.navigator) self.page_turned = False
class FileBrowserScreen(Screen): """ File Browser Screen """ def __init__(self, util, get_current_playlist, playlist_provider, listeners, voice_assistant): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.factory = Factory(util) self.bounding_box = util.screen_rect layout = BorderLayout(self.bounding_box) layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "file_browser_screen_title", True, layout.TOP) color_dark_light = self.config[COLORS][COLOR_DARK_LIGHT] current_folder = self.util.file_util.current_folder d = {"current_title" : current_folder} if self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] == FILE_PLAYLIST: f = self.config[FILE_PLAYBACK][CURRENT_FOLDER] p = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] d = f + os.sep + p self.screen_title.set_text(d) rows = 3 columns = 3 self.filelist = None playback_mode = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] if not playback_mode: self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYBACK_MODE] = playback_mode = FILE_AUDIO if playback_mode == FILE_AUDIO or playback_mode == FILE_RECURSIVE: folder_content = self.util.load_folder_content(current_folder, rows, columns, layout.CENTER) self.filelist = Page(folder_content, rows, columns) elif playback_mode == FILE_PLAYLIST: s = State() s.folder = self.config[FILE_PLAYBACK][CURRENT_FOLDER] s.music_folder = self.config[AUDIO][MUSIC_FOLDER] s.file_name = self.config[FILE_PLAYBACK][CURRENT_FILE_PLAYLIST] pl = self.get_filelist_items(get_current_playlist) if len(pl) == 0: pl = self.util.load_playlist(s, playlist_provider, rows, columns) else: pl = self.util.load_playlist_content(pl, rows, columns) self.filelist = Page(pl, rows, columns) self.file_menu = FileMenu(self.filelist, util, playlist_provider, (0, 0, 0), layout.CENTER) Container.add_component(self, self.file_menu) self.file_menu.add_change_folder_listener(self.screen_title.set_text) self.file_menu.add_play_file_listener(listeners[KEY_PLAY_FILE]) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[GO_USER_HOME] = self.file_menu.switch_to_user_home listeners[GO_ROOT] = self.file_menu.switch_to_root listeners[GO_TO_PARENT] = self.file_menu.switch_to_parent_folder self.navigator = Navigator(util, layout.BOTTOM, listeners, color_dark_light) left = str(self.filelist.get_left_items_number()) right = str(self.filelist.get_right_items_number()) self.navigator.left_button.change_label(left) self.navigator.right_button.change_label(right) self.file_menu.add_left_number_listener(self.navigator.left_button.change_label) self.file_menu.add_right_number_listener(self.navigator.right_button.change_label) Container.add_component(self, self.navigator) self.page_turned = False def get_filelist_items(self, get_current_playlist): """ Call player for files in the playlist :return: list of files from playlist """ playlist = get_current_playlist() files = [] if playlist: for n in range(len(playlist)): st = State() st.index = st.comparator_item = n st.file_type = FILE_AUDIO st.file_name = st.url = playlist[n] files.append(st) return files def add_screen_observers(self, update_observer, redraw_observer): """ Add screen observers :param update_observer: observer for updating the screen :param redraw_observer: observer to redraw the whole screen """ Screen.add_screen_observers(self, update_observer, redraw_observer) self.file_menu.add_menu_observers(update_observer, redraw_observer=None, release=False) self.file_menu.add_change_folder_listener(redraw_observer) self.file_menu.add_menu_navigation_listeners(redraw_observer) self.file_menu.add_left_number_listener(redraw_observer) self.file_menu.add_right_number_listener(redraw_observer) self.navigator.add_observers(update_observer, redraw_observer)
class CdTracksScreen(Screen): """ File Browser Screen """ def __init__(self, util, listeners, voice_assistant, state): """ Initializer :param util: utility object :param listeners: file browser listeners """ self.util = util self.config = util.config self.cdutil = CdUtil(util) self.factory = Factory(util) self.bounding_box = util.screen_rect self.layout = BorderLayout(self.bounding_box) self.layout.set_percent_constraints(PERCENT_TOP_HEIGHT, PERCENT_BOTTOM_HEIGHT, 0, 0) Screen.__init__(self, util, "", PERCENT_TOP_HEIGHT, voice_assistant, "cd_tracks_screen_title", True, self.layout.TOP) self.current_cd_drive_name = self.config[CD_PLAYBACK][CD_DRIVE_NAME] self.current_cd_drive_id = self.config[CD_PLAYBACK][CD_DRIVE_ID] self.filelist = self.get_filelist() self.file_menu = FileMenu(self.filelist, util, None, self.layout.CENTER, align=ALIGN_LEFT) self.go_cd_player = listeners[KEY_PLAYER] self.file_menu.add_play_file_listener(self.play_track) self.add_component(self.file_menu) listeners[GO_LEFT_PAGE] = self.file_menu.page_down listeners[GO_RIGHT_PAGE] = self.file_menu.page_up listeners[KEY_EJECT] = self.eject_cd listeners[KEY_REFRESH] = self.refresh_tracks connected_cd_drives = self.cdutil.get_cd_drives_number() self.navigator = CdTracksNavigator(util, connected_cd_drives, self.layout.BOTTOM, listeners) self.add_component(self.navigator) self.file_menu.add_left_number_listener( self.navigator.left_button.change_label) self.file_menu.add_right_number_listener( self.navigator.right_button.change_label) self.file_menu.update_buttons() self.page_turned = False self.animated_title = True def play_track(self, state): """ Set config and go to player :param state: state object with track info """ self.config[CD_PLAYBACK][CD_DRIVE_ID] = self.current_cd_drive_id self.config[CD_PLAYBACK][CD_DRIVE_NAME] = self.current_cd_drive_name self.config[CD_PLAYBACK][CD_TRACK_TIME] = "0" if self.current_cd_drive_name != self.screen_title.text: state.album = self.screen_title.text self.go_cd_player(state) def get_filelist(self): """ Return CD tracks :return: page with CD tracks """ rows = 5 columns = 2 layout = GridLayout(self.layout.CENTER) layout.set_pixel_constraints(rows, columns, 1, 1) constr = layout.get_next_constraints() fixed_height = int((constr.h * 35) / 100.0) tracks = self.cdutil.get_cd_tracks(rows, columns, fixed_height, self.current_cd_drive_name) if tracks == None: tracks = [] return Page(tracks, rows, columns) def set_current(self, state): """ Set current CD drive :param state: state object with drive info """ if not self.current_cd_drive_name or ( getattr(state, "name", None) and self.current_cd_drive_name != state.name): change_menu = False if not self.current_cd_drive_name: self.current_cd_drive_name = self.cdutil.get_default_cd_drive( )[1] change_menu = True elif state.name != "cd": self.current_cd_drive_name = state.name change_menu = True name = self.current_cd_drive_name tracks = len(self.file_menu.filelist.items) empty = self.cdutil.is_drive_empty(name) if name and empty != 1 and tracks == 0: change_menu = True if change_menu: id = self.cdutil.get_cd_drive_id_by_name( self.current_cd_drive_name) self.current_cd_drive_id = id self.file_menu.selected_index = 0 self.set_file_menu() self.set_screen_title() def set_screen_title(self): """ Set screen title """ title = drive_name = self.current_cd_drive_name try: title = self.util.cd_titles[drive_name] except: pass self.screen_title.set_text(title) def eject_cd(self, state): """ Eject CD :param state: state object with drive info """ if len(str(self.current_cd_drive_id)) == 0: return del self.util.cd_track_names_cache[self.current_cd_drive_id] if state == None or getattr(state, "no_physical_eject", None) == None: self.cdutil.eject_cd(int(self.current_cd_drive_id)) self.util.cd_titles[ self.current_cd_drive_name] = self.current_cd_drive_name self.file_menu.selected_index = 0 self.set_file_menu() self.set_screen_title() def refresh_tracks(self, state): """ Refresh tracks menu :param state: not used """ name = self.current_cd_drive_name if name and self.cdutil.is_drive_empty(name): s = State() s.no_physical_eject = True self.eject_cd(s) self.set_file_menu() self.set_screen_title() def set_file_menu(self): """ Set file menu """ filelist = self.get_filelist() self.file_menu.filelist = filelist self.file_menu.update_buttons() page = filelist.get_current_page() self.file_menu.set_page(filelist.current_item_index_in_page, page) for b in self.file_menu.buttons.values(): b.parent_screen = self def get_filelist_items(self, get_current_playlist): """ Call player for files in the playlist :return: list of files from playlist """ playlist = get_current_playlist() files = [] if playlist: for n in range(len(playlist)): st = State() st.index = st.comparator_item = n st.file_type = FILE_AUDIO st.file_name = st.url = playlist[n] files.append(st) return files def add_screen_observers(self, update_observer, redraw_observer): """ Add screen observers :param update_observer: observer for updating the screen :param redraw_observer: observer to redraw the whole screen """ Screen.add_screen_observers(self, update_observer, redraw_observer) self.file_menu.add_menu_observers(update_observer, redraw_observer=None, release=False) self.file_menu.add_change_folder_listener(redraw_observer) self.file_menu.add_menu_navigation_listeners(redraw_observer) self.file_menu.add_left_number_listener(redraw_observer) self.file_menu.add_right_number_listener(redraw_observer) self.navigator.add_observers(update_observer, redraw_observer)