CoverArt Browser
v2.0
Browse your cover-art albums in Rhythmbox
|
00001 # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- 00002 # 00003 # Copyright (C) 2012 - fossfreedom 00004 # Copyright (C) 2012 - Agustin Carrasco 00005 # 00006 # This program is free software; you can redistribute it and/or modify 00007 # it under the terms of the GNU General Public License as published by 00008 # the Free Software Foundation; either version 2, or (at your option) 00009 # any later version. 00010 # 00011 # This program is distributed in the hope that it will be useful, 00012 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 # GNU General Public License for more details. 00015 # 00016 # You should have received a copy of the GNU General Public License 00017 # along with this program; if not, write to the Free Software 00018 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 00019 00020 from datetime import date 00021 from collections import OrderedDict 00022 from collections import namedtuple 00023 00024 from gi.repository import GObject 00025 from gi.repository import Gdk 00026 from gi.repository import RB 00027 from gi.repository import Gio 00028 from gi.repository import GLib 00029 00030 from coverart_browser_prefs import CoverLocale 00031 from coverart_browser_prefs import GSetting 00032 from coverart_utils import create_pixbuf_from_file_at_size 00033 from coverart_utils import GenreConfiguredSpriteSheet 00034 from coverart_utils import ConfiguredSpriteSheet 00035 from coverart_utils import get_stock_size 00036 from coverart_utils import CaseInsensitiveDict 00037 from coverart_utils import Theme 00038 import rb 00039 00040 00041 MenuNodeT = namedtuple('MenuNode', 'label menutype typevalue') 00042 00043 00044 def MenuNode(label, menutype=None, typevalue=None): 00045 return MenuNodeT(label, menutype, typevalue) 00046 00047 00048 class OptionsController(GObject.Object): 00049 # properties 00050 options = GObject.property(type=object, default=None) 00051 current_key = GObject.property(type=str, default=None) 00052 update_image = GObject.property(type=bool, default=False) 00053 enabled = GObject.property(type=bool, default=True) 00054 00055 def __init__(self): 00056 super(OptionsController, self).__init__() 00057 00058 # connect the variations on the current key to the controllers action 00059 self.connect('notify::current-key', self._do_action) 00060 00061 def get_current_key_index(self): 00062 return self.options.index(self.current_key) 00063 00064 def option_selected(self, key): 00065 if key != self.current_key: 00066 # update the current value 00067 self.current_key = key 00068 00069 def _do_action(self, *args): 00070 self.do_action() 00071 00072 def do_action(self): 00073 pass 00074 00075 def get_current_image(self): 00076 return None 00077 00078 def get_current_description(self): 00079 return self.current_key 00080 00081 def update_images(self, *args): 00082 pass 00083 00084 def create_spritesheet(self, plugin, sheet, typestr): 00085 ''' 00086 helper function to create a specific spritesheet 00087 ''' 00088 if sheet: 00089 del sheet 00090 00091 return ConfiguredSpriteSheet(plugin, typestr, get_stock_size()) 00092 00093 def create_button_image(self, plugin, image, icon_name): 00094 ''' 00095 helper function to create a button image 00096 ''' 00097 if image: 00098 del image 00099 00100 path = 'img/' + Theme(self.plugin).current + '/' 00101 00102 return create_pixbuf_from_file_at_size( 00103 rb.find_plugin_file(self.plugin, path + icon_name), 00104 *get_stock_size()) 00105 00106 00107 class PlaylistPopupController(OptionsController): 00108 def __init__(self, plugin, album_model): 00109 super(PlaylistPopupController, self).__init__() 00110 00111 self._album_model = album_model 00112 00113 shell = plugin.shell 00114 self.plugin = plugin 00115 00116 # get the library name and initialize the superclass with it 00117 self._library_name = shell.props.library_source.props.name 00118 00119 # get the queue name 00120 self._queue_name = shell.props.queue_source.props.name 00121 00122 if " (" in self._queue_name: 00123 self._queue_name = self._queue_name[0:self._queue_name.find(" (")] 00124 00125 self._spritesheet = None 00126 self._update_options(shell) 00127 00128 # get the playlist model so we can monitor changes 00129 playlist_model = shell.props.display_page_model 00130 00131 # connect signals to update playlists 00132 playlist_model.connect('row-inserted', self._update_options, shell) 00133 playlist_model.connect('row-deleted', self._update_options, shell) 00134 playlist_model.connect('row-changed', self._update_options, shell) 00135 00136 def update_images(self, *args): 00137 self._spritesheet = self.create_spritesheet(self.plugin, 00138 self._spritesheet, 'playlist') 00139 00140 if args[-1]: 00141 self.update_image = True 00142 00143 def _update_options(self, *args): 00144 shell = args[-1] 00145 self.update_images(False) 00146 00147 playlist_manager = shell.props.playlist_manager 00148 still_exists = self.current_key == self._library_name or \ 00149 self.current_key == self._queue_name 00150 00151 # retrieve the options 00152 values = OrderedDict() 00153 00154 # library and play queue sources 00155 values[self._library_name] = None 00156 values[self._queue_name] = shell.props.queue_source 00157 00158 # playlists 00159 playlists_entries = playlist_manager.get_playlists() 00160 00161 for playlist in playlists_entries: 00162 if playlist.props.is_local: 00163 name = playlist.props.name 00164 values[name] = playlist 00165 00166 still_exists = still_exists or name == self.current_key 00167 00168 self.values = values 00169 self.options = list(values.keys()) 00170 00171 self.current_key = self.current_key if still_exists else \ 00172 self._library_name 00173 00174 def do_action(self): 00175 playlist = self.values[self.current_key] 00176 00177 if not playlist: 00178 self._album_model.remove_filter('model') 00179 else: 00180 self._album_model.replace_filter('model', 00181 playlist.get_query_model()) 00182 00183 def get_current_image(self): 00184 playlist = self.values[self.current_key] 00185 00186 if self.current_key == self._library_name: 00187 image = self._spritesheet['music'] 00188 elif self._queue_name in self.current_key: 00189 image = self._spritesheet['queue'] 00190 elif isinstance(playlist, RB.StaticPlaylistSource): 00191 image = self._spritesheet['playlist'] 00192 else: 00193 image = self._spritesheet['smart'] 00194 00195 return image 00196 00197 00198 class GenrePopupController(OptionsController): 00199 # properties 00200 new_genre_icon = GObject.property(type=bool, default=False) 00201 00202 def __init__(self, plugin, album_model): 00203 super(GenrePopupController, self).__init__() 00204 00205 cl = CoverLocale() 00206 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00207 00208 self._album_model = album_model 00209 00210 shell = plugin.shell 00211 self.plugin = plugin 00212 00213 # create a new property model for the genres 00214 genres_model = RB.RhythmDBPropertyModel.new(shell.props.db, 00215 RB.RhythmDBPropType.GENRE) 00216 00217 query = shell.props.library_source.props.base_query_model 00218 genres_model.props.query_model = query 00219 00220 # initial genre 00221 self._initial_genre = _('All Genres') 00222 00223 self._spritesheet = None 00224 self._default_image = None 00225 self._unrecognised_image = None 00226 00227 self._connect_properties() 00228 self._connect_signals(query, genres_model) 00229 00230 # generate initial popup 00231 self._update_options(genres_model) 00232 00233 def update_images(self, *args): 00234 if self._spritesheet: 00235 del self._spritesheet 00236 00237 self._spritesheet = GenreConfiguredSpriteSheet(self.plugin, 00238 'genre', get_stock_size()) 00239 self._default_image = self.create_button_image(self.plugin, 00240 self._default_image, 'default_genre.png') 00241 self._unrecognised_image = self.create_button_image(self.plugin, 00242 self._unrecognised_image, 'unrecognised_genre.png') 00243 00244 if args[-1]: 00245 self.update_image = True 00246 00247 def _connect_signals(self, query, genres_model): 00248 # connect signals to update genres 00249 self.connect('notify::new-genre-icon', self._update_options, genres_model) 00250 query.connect('row-inserted', self._update_options, genres_model) 00251 query.connect('row-deleted', self._update_options, genres_model) 00252 query.connect('row-changed', self._update_options, genres_model) 00253 00254 def _connect_properties(self): 00255 gs = GSetting() 00256 setting = gs.get_setting(gs.Path.PLUGIN) 00257 00258 setting.bind(gs.PluginKey.NEW_GENRE_ICON, self, 'new_genre_icon', 00259 Gio.SettingsBindFlags.GET) 00260 00261 def _update_options(self, *args): 00262 genres_model = args[-1] 00263 00264 self.update_images(False) 00265 00266 still_exists = False 00267 00268 # retrieve the options 00269 options = [] 00270 row_num = 0 00271 for row in genres_model: 00272 if row_num == 0: 00273 cl = CoverLocale() 00274 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00275 genre = _('All Genres') 00276 row_num = row_num + 1 00277 else: 00278 genre = row[0] 00279 00280 options.append(genre) 00281 00282 still_exists = still_exists or genre == self.current_key 00283 00284 self.options = options 00285 00286 self.current_key = self.current_key if still_exists else \ 00287 self._initial_genre 00288 00289 def do_action(self): 00290 ''' 00291 called when genre popup menu item chosen 00292 return None if the first entry in popup returned 00293 ''' 00294 if self.current_key == self._initial_genre: 00295 self._album_model.remove_filter('genre') 00296 else: 00297 self._album_model.replace_filter('genre', self.current_key) 00298 00299 def get_current_image(self): 00300 test_genre = self.current_key.lower() 00301 00302 if test_genre == self._initial_genre.lower(): 00303 image = self._default_image 00304 else: 00305 image = self._find_alternates(test_genre) 00306 00307 if image == self._unrecognised_image and \ 00308 test_genre in self._spritesheet: 00309 image = self._spritesheet[test_genre] 00310 00311 return image 00312 00313 def _find_alternates(self, test_genre): 00314 # the following genre checks are required 00315 # 1. if we have user defined genres 00316 # 2. then check locale specific system genres 00317 # 3. then check local specific alternates 00318 # 4. then check if we system genres 00319 00320 # where necessary check if any of the genres are a substring 00321 # of test_genre - check in reverse order so that we 00322 # test largest strings first (prevents spurious matches with 00323 # short strings) 00324 # N.B. we use RB.search_fold since the strings can be 00325 # in a mixture of cases, both unicode (normalized or not) and str 00326 # and as usual python cannot mix and match these types. 00327 00328 00329 test_genre = RB.search_fold(test_genre) 00330 00331 ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_USER) 00332 if ret: 00333 return sprite 00334 00335 for genre in sorted(self._spritesheet.locale_names, 00336 key=lambda b: (-len(b), b)): 00337 if RB.search_fold(genre) in test_genre: 00338 return self._spritesheet[self._spritesheet.locale_names[genre]] 00339 00340 # next check locale alternates 00341 ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_LOCALE) 00342 if ret: 00343 return sprite 00344 00345 ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_SYSTEM) 00346 if ret: 00347 return sprite 00348 00349 # check if any of the default genres are a substring 00350 # of test_genre - check in reverse order so that we 00351 # test largest strings first (prevents spurious matches with 00352 # short strings) 00353 for genre in sorted(self._spritesheet.names, 00354 key=lambda b: (-len(b), b)): 00355 if RB.search_fold(genre) in test_genre: 00356 return self._spritesheet[genre] 00357 00358 # if no matches then default to unrecognised image 00359 return self._unrecognised_image 00360 00361 def _match_genres(self, test_genre, genre_type): 00362 case_search = CaseInsensitiveDict( 00363 dict((k.name, v) for k, v in self._spritesheet.genre_alternate.items() 00364 if k.genre_type == genre_type)) 00365 00366 if test_genre in case_search: 00367 return (True, self._spritesheet[case_search[test_genre]]) 00368 else: 00369 return (False, None) 00370 00371 00372 def get_current_description(self): 00373 cl = CoverLocale() 00374 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00375 if self.current_key == self._initial_genre: 00376 return _('All Genres') 00377 else: 00378 return self.current_key 00379 00380 00381 class SortPopupController(OptionsController): 00382 def __init__(self, plugin, viewmgr): 00383 super(SortPopupController, self).__init__() 00384 00385 self._viewmgr = viewmgr 00386 self.plugin = plugin 00387 # sorts dictionary 00388 cl = CoverLocale() 00389 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00390 00391 self.values = OrderedDict([(_('Sort by album name'), 'name'), 00392 (_('Sort by album artist'), 'artist'), 00393 (_('Sort by year'), 'year'), 00394 (_('Sort by rating'), 'rating')]) 00395 00396 self.options = list(self.values.keys()) 00397 00398 # get the current sort key and initialise the superclass 00399 gs = GSetting() 00400 source_settings = gs.get_setting(gs.Path.PLUGIN) 00401 value = source_settings[gs.PluginKey.SORT_BY] 00402 00403 self._spritesheet = None 00404 self.update_images(False) 00405 00406 self.current_key = list(self.values.keys())[ 00407 list(self.values.values()).index(value)] 00408 00409 def update_images(self, *args): 00410 self._spritesheet = self.create_spritesheet(self.plugin, 00411 self._spritesheet, 'sort') 00412 00413 if args[-1]: 00414 self.update_image = True 00415 00416 def do_action(self): 00417 sort = self.values[self.current_key] 00418 00419 gs = GSetting() 00420 settings = gs.get_setting(gs.Path.PLUGIN) 00421 settings[gs.PluginKey.SORT_BY] = sort 00422 00423 self._viewmgr.current_view.get_default_manager().emit('sort', "album") 00424 00425 def get_current_image(self): 00426 sort = self.values[self.current_key] 00427 return self._spritesheet[sort] 00428 00429 00430 class ArtistSortPopupController(OptionsController): 00431 def __init__(self, plugin, viewmgr): 00432 super(ArtistSortPopupController, self).__init__() 00433 00434 self._viewmgr = viewmgr 00435 self.plugin = plugin 00436 # sorts dictionary 00437 cl = CoverLocale() 00438 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00439 00440 self.values = OrderedDict([(_('Sort by album name'), 'name_artist'), 00441 (_('Sort by year'), 'year_artist'), 00442 (_('Sort by rating'), 'rating_artist')]) 00443 00444 self.options = list(self.values.keys()) 00445 00446 # get the current sort key and initialise the superclass 00447 gs = GSetting() 00448 source_settings = gs.get_setting(gs.Path.PLUGIN) 00449 value = source_settings[gs.PluginKey.SORT_BY_ARTIST] 00450 00451 if value not in list(self.values.values()): 00452 print("here") 00453 value = 'name_artist' 00454 source_settings[gs.PluginKey.SORT_BY_ARTIST] = value 00455 00456 self._spritesheet = None 00457 self.update_images(False) 00458 00459 self.current_key = list(self.values.keys())[ 00460 list(self.values.values()).index(value)] 00461 print(self.current_key) 00462 00463 def update_images(self, *args): 00464 self._spritesheet = self.create_spritesheet(self.plugin, 00465 self._spritesheet, 'sort_artist') 00466 00467 if args[-1]: 00468 self.update_image = True 00469 00470 def do_action(self): 00471 sort = self.values[self.current_key] 00472 00473 gs = GSetting() 00474 settings = gs.get_setting(gs.Path.PLUGIN) 00475 settings[gs.PluginKey.SORT_BY_ARTIST] = sort 00476 00477 self._viewmgr.current_view.get_default_manager().emit('sort', "artist") 00478 00479 def get_current_image(self): 00480 sort = self.values[self.current_key] 00481 return self._spritesheet[sort] 00482 00483 00484 class PropertiesMenuController(OptionsController): 00485 favourites = GObject.property(type=bool, default=False) 00486 follow = GObject.property(type=bool, default=False) 00487 00488 def __init__(self, plugin, source): 00489 super(PropertiesMenuController, self).__init__() 00490 00491 self._source = source 00492 self.plugin = plugin 00493 self._connect_properties() 00494 # sorts dictionary 00495 cl = CoverLocale() 00496 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00497 # options 00498 self.values = OrderedDict() 00499 self.values[MenuNode(_('Download all covers'))] = 'download' 00500 self.values[MenuNode(_('Play random album'))] = 'random' 00501 self.values[MenuNode(_('Follow playing song'), 'check', 00502 (True if self.follow else False))] = 'follow' 00503 self.values[MenuNode('separator1', 'separator')] = '' 00504 self.values[MenuNode(_('Use favourites only'), 'check', 00505 (True if self.favourites else False))] = 'favourite' 00506 self.values[MenuNode('separator2', 'separator')] = '' 00507 self.values[MenuNode(_('Browser Preferences'))] = 'browser prefs' 00508 self.values[MenuNode(_('Search Preferences'))] = 'search prefs' 00509 00510 self.options = list(self.values.keys()) 00511 00512 self.update_images(False) 00513 00514 if self.favourites: 00515 self._source.propertiesbutton_callback('favourite') 00516 00517 if self.follow: 00518 self._source.propertiesbutton_callback('follow') 00519 00520 self.current_key = None 00521 00522 def _connect_properties(self): 00523 gs = GSetting() 00524 setting = gs.get_setting(gs.Path.PLUGIN) 00525 setting.bind( 00526 gs.PluginKey.USE_FAVOURITES, 00527 self, 00528 'favourites', 00529 Gio.SettingsBindFlags.DEFAULT) 00530 setting.bind( 00531 gs.PluginKey.FOLLOWING, 00532 self, 00533 'follow', 00534 Gio.SettingsBindFlags.DEFAULT) 00535 00536 def _change_key(self, dict, old, new): 00537 for i in range(len(dict)): 00538 k, v = dict.popitem(False) 00539 dict[new if old == k else k] = v 00540 00541 def update_images(self, *args): 00542 self._image = self.create_button_image(self.plugin, 00543 None, 'properties.png') 00544 00545 if args[-1]: 00546 self.update_image = True 00547 00548 def do_action(self): 00549 if self.current_key: 00550 key = [node for node in self.values if node.label == self.current_key] 00551 00552 if self.current_key == _('Use favourites only'): 00553 self.favourites = not self.favourites 00554 00555 if self.current_key == _('Follow playing song'): 00556 self.follow = not self.follow 00557 00558 self._source.propertiesbutton_callback(self.values[key[0]]) 00559 self.current_key = None 00560 00561 def get_current_image(self): 00562 return self._image 00563 00564 def get_current_description(self): 00565 return _('Properties') 00566 00567 00568 class DecadePopupController(OptionsController): 00569 def __init__(self, plugin, album_model): 00570 super(DecadePopupController, self).__init__() 00571 00572 self._album_model = album_model 00573 self.plugin = plugin 00574 00575 self._spritesheet = None 00576 00577 # decade options 00578 cl = CoverLocale() 00579 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00580 00581 self.values = OrderedDict() 00582 00583 self.values[_('All Decades')] = [-1, 'All Decades'] 00584 #'20s' as in the decade 2010 00585 self.values[_('20s')] = [2020, '20s'] 00586 #'10s' as in the decade 2010 00587 self.values[_('10s')] = [2010, '10s'] 00588 #'00s' as in the decade 2000 00589 self.values[_('00s')] = [2000, '00s'] 00590 #'90s' as in the decade 1990 00591 self.values[_('90s')] = [1990, '90s'] 00592 #'80s' as in the decade 1980 00593 self.values[_('80s')] = [1980, '80s'] 00594 #'70s' as in the decade 1970 00595 self.values[_('70s')] = [1970, '70s'] 00596 #'60s' as in the decade 1960 00597 self.values[_('60s')] = [1960, '60s'] 00598 #'50s' as in the decade 1950 00599 self.values[_('50s')] = [1950, '50s'] 00600 #'40s' as in the decade 1940 00601 self.values[_('40s')] = [1940, '40s'] 00602 #'30s' as in the decade 1930 00603 self.values[_('30s')] = [1930, '30s'] 00604 #'Older' as in 'older than the year 1930' 00605 self.values[_('Older')] = [-1, 'Older'] 00606 00607 self.options = list(self.values.keys()) 00608 00609 # if we aren't on the 20s yet, remove it 00610 if date.today().year < 2020: 00611 self.options.remove(_('20s')) 00612 00613 # define a initial decade an set the initial key 00614 self._initial_decade = self.options[0] 00615 self.update_images(False) 00616 00617 self.current_key = self._initial_decade 00618 00619 def update_images(self, *args): 00620 self._spritesheet = self.create_spritesheet(self.plugin, 00621 self._spritesheet, 'decade') 00622 00623 if args[-1]: 00624 self.update_image = True 00625 00626 def do_action(self): 00627 if self.current_key == self._initial_decade: 00628 self._album_model.remove_filter('decade') 00629 else: 00630 self._album_model.replace_filter('decade', 00631 self.values[self.current_key][0]) 00632 00633 def get_current_image(self): 00634 decade = self.values[self.current_key][1] 00635 return self._spritesheet[decade] 00636 00637 def get_current_description(self): 00638 return self.current_key 00639 00640 00641 class SortOrderToggleController(OptionsController): 00642 toolbar_type = "album" 00643 00644 def __init__(self, plugin, viewmgr): 00645 super(SortOrderToggleController, self).__init__() 00646 00647 self._viewmgr = viewmgr 00648 self.plugin = plugin 00649 00650 # options 00651 self.values = OrderedDict([(_('Sort in descending order'), False), 00652 (_('Sort in ascending order'), True)]) 00653 self.options = list(self.values.keys()) 00654 00655 self._images = [] 00656 00657 # set the current key 00658 self.gs = GSetting() 00659 self.settings = self.gs.get_setting(self.gs.Path.PLUGIN) 00660 self.key = self.get_key() 00661 sort_order = self.settings[self.key] 00662 self.current_key = list(self.values.keys())[ 00663 list(self.values.values()).index(sort_order)] 00664 self.update_images(False) 00665 00666 def get_key(self): 00667 return self.gs.PluginKey.SORT_ORDER 00668 00669 def update_images(self, *args): 00670 # initialize images 00671 if len(self._images) > 0: 00672 del self._images[:] 00673 00674 self._images.append(self.create_button_image(self.plugin, 00675 None, 'arrow_down.png')) 00676 self._images.append(self.create_button_image(self.plugin, 00677 None, 'arrow_up.png')) 00678 00679 if args[-1]: 00680 self.update_image = True 00681 00682 def do_action(self): 00683 sort_order = self.values[self.current_key] 00684 self.settings[self.key] = sort_order 00685 self._viewmgr.current_view.get_default_manager().emit('sort', self.toolbar_type) 00686 00687 def get_current_image(self): 00688 return self._images[self.get_current_key_index()] 00689 00690 00691 class ArtistSortOrderToggleController(SortOrderToggleController): 00692 toolbar_type = "artist" 00693 00694 def __init__(self, plugin, model): 00695 super(ArtistSortOrderToggleController, self).__init__(plugin, model) 00696 00697 def get_key(self): 00698 return self.gs.PluginKey.SORT_ORDER_ARTIST 00699 00700 00701 class AlbumSearchEntryController(OptionsController): 00702 # properties 00703 search_text = GObject.property(type=str, default='') 00704 00705 def __init__(self, album_model): 00706 super(AlbumSearchEntryController, self).__init__() 00707 00708 self._album_model = album_model 00709 self._filter_type = 'all' 00710 00711 # options 00712 self.values = OrderedDict() 00713 self.values[_('Search all fields')] = 'all' 00714 self.values[_('Search album artists')] = 'album_artist' 00715 self.values[_('Search track artists')] = 'artist' 00716 self.values[_('Search composers')] = 'composers' 00717 self.values[_('Search albums')] = 'album_name' 00718 self.values[_('Search titles')] = 'track' 00719 00720 self.options = list(self.values.keys()) 00721 self.current_key = list(self.values.keys())[0] 00722 00723 self._typing = False 00724 self._typing_counter = 0 00725 self._current_search_text = "" 00726 00727 def do_action(self): 00728 # remove old filter 00729 self._album_model.remove_filter(self._filter_type, False) 00730 00731 # assign the new filter 00732 self._filter_type = self.values[self.current_key] 00733 00734 self.do_search(self.search_text, True) 00735 00736 def _search_typing(self, *args): 00737 self._typing_counter = self._typing_counter + 1 00738 00739 if self._typing_counter >= 4 and self._typing: 00740 self._typing = False 00741 self._change_filter(self._current_search_text, False) 00742 00743 return self._typing 00744 00745 def _change_filter(self, search_text, force): 00746 #self.search_text = search_text 00747 self._current_search_text = search_text 00748 00749 if search_text: 00750 self._album_model.replace_filter(self._filter_type, 00751 search_text) 00752 elif not force: 00753 self._album_model.remove_filter(self._filter_type) 00754 00755 def do_search(self, search_text, force=False): 00756 ''' 00757 if self.search_text != search_text or force: 00758 self.search_text = search_text 00759 00760 if search_text: 00761 self._album_model.replace_filter(self._filter_type, 00762 search_text) 00763 elif not force: 00764 self._album_model.remove_filter(self._filter_type) 00765 00766 ''' 00767 #self.search_text = search_text 00768 if force: 00769 self._typing_counter = 99 00770 self._typing = False 00771 self._change_filter(search_text, force) 00772 return 00773 00774 if self._current_search_text != search_text: 00775 00776 #self.search_text = search_text 00777 self._current_search_text = search_text 00778 self._typing_counter = 0 00779 00780 if not self._typing: 00781 self._typing = True 00782 00783 Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100, 00784 self._search_typing) 00785 00786 00787 class AlbumQuickSearchController(object): 00788 def __init__(self, album_manager): 00789 self._album_manager = album_manager 00790 00791 def connect_quick_search(self, quick_search): 00792 quick_search.connect('quick-search', self._on_quick_search) 00793 quick_search.connect('arrow-pressed', self._on_arrow_pressed) 00794 quick_search.connect('hide', self._on_hide) 00795 00796 def _on_quick_search(self, quick_search, search_text, *args): 00797 album = self._album_manager.model.find_first_visible('album_name', 00798 search_text) 00799 00800 if album: 00801 path = self._album_manager.model.get_path(album) 00802 self._album_manager.current_view.select_and_scroll_to_path(path) 00803 00804 def _on_arrow_pressed(self, quick_search, key, *args): 00805 current = self._album_manager.current_view.get_selected_objects()[0] 00806 search_text = quick_search.get_text() 00807 album = None 00808 00809 if key == Gdk.KEY_Up: 00810 album = self._album_manager.model.find_first_visible( 00811 'album_name', search_text, current, True) 00812 elif key == Gdk.KEY_Down: 00813 album = self._album_manager.model.find_first_visible( 00814 'album_name', search_text, current) 00815 00816 if album: 00817 path = self._album_manager.model.get_path(album) 00818 self._album_manager.current_view.select_and_scroll_to_path(path) 00819 00820 def _on_hide(self, quick_search, *args): 00821 self._album_manager.current_view.grab_focus() 00822 00823 00824 class ViewController(OptionsController): 00825 def __init__(self, shell, viewmgr): 00826 super(ViewController, self).__init__() 00827 00828 self._viewmgr = viewmgr 00829 00830 from coverart_browser_source import Views 00831 00832 views = Views(shell) 00833 00834 self.values = OrderedDict() 00835 for view_name in views.get_view_names(): 00836 self.values[views.get_menu_name(view_name)] = view_name 00837 print(view_name) 00838 00839 self.options = list(self.values.keys()) 00840 viewmgr.connect('new-view', self.on_notify_view_name) 00841 00842 def on_notify_view_name(self, *args): 00843 for key in self.options: 00844 if self.values[key] == self._viewmgr.view_name: 00845 self.current_key = key 00846 00847 def do_action(self): 00848 if self._viewmgr.view_name != self.values[self.current_key]: 00849 self._viewmgr.view_name = self.values[self.current_key]