Comparar commits

...

810 Commits

Autor SHA1 Mensagem Data
David Mohammed f8562992d9 Merge pull request #400 from mlvtito/master
rename variable to avoid use of key word
2019-07-31 12:00:44 +01:00
Arnaud Fonce ee1ac265bd rename variable to avoid use of key word 2019-07-01 12:05:36 +02:00
fossfreedom 6efdf8b750 one more try to get the headerbar to correctly display the search toolbar 2015-06-03 17:49:30 +01:00
fossfreedom 0d2fcc27bb better working with alternative-toolbar and headerbar - view button is shown correctly depending upon what mode the alternative toolbar is using 2015-05-30 09:22:04 +01:00
fossfreedom 43f2dec84f make autostart work with alternative toolbar 2015-05-24 08:54:31 +01:00
fossfreedom d5768b8184 Merge remote-tracking branch 'origin/master' 2015-05-14 21:09:14 +01:00
fossfreedom ab4888efca faster startup of plugin by loading artist data only if necessary 2015-05-14 21:08:45 +01:00
David Mohammed 5847ab0081 Update README.md 2015-05-12 16:32:33 +01:00
fossfreedom 7a4b510a2a do some proper cleanup of the headerbar objects when the plugin exits 2015-05-06 23:51:15 +01:00
fossfreedom 9b188f4d4e fix lockup due to llyrics plugin not releasing gdk threads but flow view was dependent also on similar thread usage - issue #342 2015-04-27 00:12:10 +01:00
fossfreedom ae918845ff correct symbolic icons for coverart_browser and entryview - issue #360 2015-04-25 23:55:37 +01:00
fossfreedom ed275b8736 default to sidebar for the overlay picture - issue #351 2015-04-25 22:40:20 +01:00
fossfreedom e377735696 update readme - issue #356 2015-04-25 16:06:53 +01:00
fossfreedom 0979b84e85 cleanup of coverart_playsource - issue #356 2015-04-25 16:04:07 +01:00
fossfreedom b4cd3aa305 rework playlist_source so that it behaves like the other views which means we dont need the separate whatsplaying icon and other access methods - issue #356 2015-04-25 16:02:54 +01:00
fossfreedom d266d851ac ensure alttoolbarsidebar is a recognised treeview for the playsource toggle 2015-04-19 22:53:53 +01:00
fossfreedom cb76fc11ff interact with the alternative-toolbar correctly to setup the headerbar stuff 2015-04-18 11:38:08 +01:00
fossfreedom 8e4ff492f2 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2015-04-13 19:58:58 +01:00
fossfreedom 20440530e2 hide the source unless needed 2015-04-13 19:58:49 +01:00
fossfreedom 5fde6b4db4 correctly load the new switcher symbolic icon 2015-04-10 13:16:04 +01:00
fossfreedom e5b3ac585f rejig so that views are correctly defined depending upon the combinations of song-category and stackswitcher - issue #359 2015-04-08 22:37:57 +01:00
fossfreedom 13007fa6af hook up headerbar with coverart and switcher - issue #359 2015-04-07 23:53:23 +01:00
fossfreedom 3baac6bf26 add some tracing statements 2015-04-06 23:05:17 +01:00
fossfreedom 6d995200f9 initial implementation of header switch - not as yet hooked up - issue #359 2015-04-06 22:13:26 +01:00
fossfreedom d9e952bcba update readme for icons fix 2015-03-14 00:29:17 +00:00
fossfreedom 4c0f10b950 fix dark-theme icon visibility issues for key display icons - issue #352 2015-03-14 00:27:45 +00:00
fossfreedom b77391b13d switch to playlist via properties menu - issue #353 2015-03-10 18:19:55 +00:00
fossfreedom 5d30d526fc tidy up and make playing from toolbar possible - issue #347 2015-03-10 00:54:58 +00:00
fossfreedom a199f1019e save and load methods for persistent playlist - issue #347 2015-03-09 22:25:14 +00:00
fossfreedom b6e0295968 add gtk_version helper function 2015-03-01 21:42:48 +00:00
fossfreedom d9c310bdf9 remember export dialog settings between openings - issue #350 2015-02-28 23:08:01 +00:00
fossfreedom fd69e2fbaa take latest po's from release 2.1 branch 2015-02-28 22:04:59 +00:00
fossfreedom 45e59f82d5 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2015-02-25 20:40:42 +00:00
fossfreedom 08c0ad0991 fix spurious dbchange error when playing radio streams instead of musi 2015-02-25 20:40:16 +00:00
fossfreedom 58339c78f7 fix for api changein rb3.2 for rb.append_plugin_source_path 2015-02-25 20:39:42 +00:00
David Mohammed 3000ce1dd2 Update README.md 2015-02-17 20:38:46 +00:00
fossfreedom fa5833a969 add ability to select and play using RB's own toolbar play button - issue #349 2015-02-11 21:34:55 +00:00
fossfreedom 1a83844fe0 on startup make sure we set the playing/selected source correctly so that clicking the toolbar playbutton doesnt start with a random track from the music library 2015-02-09 22:20:39 +00:00
fossfreedom 5a946e87e5 a bit of cleanup and explanation - issue #348 2015-02-09 19:35:29 +00:00
fossfreedom 9a892d0a67 fix retrieval of coverat info for rb 3.2 - issue #348 2015-02-09 00:08:50 +00:00
fossfreedom 01534acefa respond to toolbar visibility signal for the alternative toolbar 2015-02-08 20:29:47 +00:00
fossfreedom a51c43fc0d update readme for beta 1 2015-02-06 22:32:12 +00:00
fossfreedom 0a3d4be746 latest tr translation 2015-02-05 16:23:38 +00:00
fossfreedom f6a7119247 smaller resizing - issue #79 2015-02-03 23:25:02 +00:00
fossfreedom 850f9bcb9b add smooth-scrolling support to icon-view resize - issue #79 2015-01-29 23:59:10 +00:00
fossfreedom 92995493d0 add ctrl+mousewheel icon resizing - issue #79 2015-01-29 23:35:49 +00:00
fossfreedom 4bc283a420 remove lots of deprecated Gtk syntax 2015-01-28 20:40:04 +00:00
David Mohammed 124f6f647d revert previous change and some cleanup 2015-01-28 13:47:50 +00:00
fossfreedom a49ec03b67 run update_all on translations 2015-01-26 21:23:11 +00:00
fossfreedom bdf8a7638a update README for issue #318 2015-01-26 20:08:31 +00:00
fossfreedom b268362880 add picture icons to track-view and play-source to allow switching between views - issue #318 2015-01-26 20:05:00 +00:00
David Mohammed 35ce340691 use $HOME rather than $USER for those users who's home is not home 2015-01-24 23:28:25 +00:00
fossfreedom 444cc7cd58 latest ru translation 2015-01-24 21:27:24 +00:00
fossfreedom 5ce1e3edd5 respond to alternative-toolbar toolbar-visibility message to hide/show the source toolbar - issue #321 2015-01-11 15:16:44 +00:00
fossfreedom 53a5b0bc3d latest translations 2015-01-07 22:50:22 +00:00
fossfreedom b01a20d806 rerun translation stuff 2014-12-21 23:14:14 +00:00
fossfreedom ceea5d06dc more logical right-click menu structures 2014-12-08 20:00:59 +00:00
fossfreedom 9425b1e1b5 pycharm autorefactor 2014-12-03 19:32:49 +00:00
David Mohammed 72fc8cb4b2 Update README.md 2014-11-29 10:31:30 +00:00
fossfreedom 8880d9a08e add text alignment options - issue #340 2014-11-23 00:11:41 +00:00
fossfreedom a51329e0dd add text alignment options - issue #340 2014-11-23 00:09:59 +00:00
fossfreedom 4ffb187890 better overlay icon 2014-11-20 10:21:29 +00:00
fossfreedom 0256a65b18 fix toggle coverart to work with new overlay 2014-11-19 23:26:24 +00:00
fossfreedom cbbb254886 update readme 2014-11-19 21:49:14 +00:00
fossfreedom d24459f6a4 Merge remote-tracking branch 'origin/master'
Conflicts:
	README.md
2014-11-19 21:44:42 +00:00
fossfreedom 2dfb1a1566 add a coverwindow - issue #339 2014-11-19 21:42:26 +00:00
David Mohammed 8e53da5ca1 Update README.md 2014-11-12 11:26:56 +00:00
fossfreedom 746e6b9ede make the hover play icon consistently positioned - issue #343 2014-11-10 21:22:56 +00:00
fossfreedom d3bbfa798a some minor code cleanup 2014-11-05 20:29:40 +00:00
fossfreedom 908b564cb8 update release me with issue #313 2014-10-30 22:03:44 +00:00
fossfreedom ffa85938a2 add double click to track and cover pane handle to open to full height, close if already open - issue #313 2014-10-30 22:01:25 +00:00
fossfreedom c634c8f514 add play-next option for tracks - issue #329 2014-10-24 20:06:15 +01:00
fossfreedom d7eea33c9d add play-next option for albums - issue #329 2014-10-23 23:35:45 +01:00
fossfreedom 6a02d3b74a fix obscure GtkBuilder bug with parameter inheritance issues for iconview - issue #338 2014-10-18 10:01:24 +01:00
fossfreedom 92020328ea update readme 2014-10-12 10:01:16 +01:00
fossfreedom 5ad55ccab3 auto-close bottom pane if too small - issue #337 2014-10-12 09:54:36 +01:00
fossfreedom 16d2593b09 intelligent pane handles - issue #335 2014-10-04 09:37:18 +01:00
fossfreedom 47e5220357 resize entry grid cover correctly - issue #336 2014-10-03 22:25:32 +01:00
fossfreedom aca7ff9d02 replace expander with hidden version - issue #320 2014-10-02 20:46:10 +01:00
fossfreedom 3abf4537db add double click support for artist-info pane - issue #314 2014-07-21 23:40:02 +01:00
fossfreedom 666395e547 add double click support for track-artist pane - issue #314 2014-07-20 00:12:34 +01:00
fossfreedom 533c830838 fix links icons in global installation 2014-07-16 21:57:04 +01:00
fossfreedom 1900e51ff1 fix global darker theme install and missing css file 2014-07-15 22:48:05 +01:00
fossfreedom 513f9301dc corrected call from makefile to ensure translation build does not fail 2014-07-15 22:47:38 +01:00
fossfreedom 3aa9e24f81 fixed makefile install for translations 2014-07-15 22:47:09 +01:00
David Mohammed 58eed3a889 Update README.md 2014-07-12 21:55:55 +01:00
fossfreedom 4058056b70 adjust readme for release info 2014-07-12 00:24:48 +01:00
fossfreedom a9fe7ddd27 release info in plugin files 2014-07-12 00:21:38 +01:00
fossfreedom ac52161047 latest readme 2014-07-12 00:19:35 +01:00
fossfreedom e2803df857 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2014-07-12 00:00:30 +01:00
fossfreedom 8aa2e7105e updated coverart_artistinfo icons as per jrbastiens recommendations - issue #317 2014-07-12 00:00:08 +01:00
fossfreedom f968221bff new majority translation - ru 2014-07-11 13:48:31 +01:00
fossfreedom 90b4bf31f3 latest translation - ms 2014-07-11 07:10:15 +01:00
fossfreedom 49b355b8cf latest ko translation and run and update_all 2014-07-08 08:12:11 +01:00
fossfreedom edfd24bbd3 some fixups when adding and deleting tracks 2014-07-06 22:59:49 +01:00
fossfreedom c5042bdc57 amend readme 2014-07-06 13:00:00 +01:00
fossfreedom e6c650d7b8 replace views label with an ellipsis - issue #328 2014-07-06 12:58:20 +01:00
fossfreedom 52e3e7e2ef latest translations 2014-07-05 22:01:41 +01:00
fossfreedom 82558792b5 latest translations and remove translations where no longer majority translated 2014-07-04 19:49:40 +01:00
fossfreedom e0e5e5b812 reduce install to a users locale or if it doesnt exist then default to en_US - issue #327 2014-07-03 21:16:33 +01:00
fossfreedom 31041fa39a rework coverart display to overcome perceived GTK scrollwindow resizing bug when policy is usually set to NEVER to hide the scrollwindow scrollbars - issue #325 2014-07-03 20:41:05 +01:00
fossfreedom 1dcbc4ce07 latest translations 2014-07-01 23:03:22 +01:00
fossfreedom 003b10afa6 change entryview compact image from AspectFrame to Frame - this gives the image better balance vertically 2014-06-30 23:06:43 +01:00
fossfreedom c1fbb27b0c latest fr_CA translation 2014-06-30 12:28:31 +01:00
fossfreedom 657abe17fb update readme to reflect localisation - issue #319 2014-06-29 19:50:34 +01:00
fossfreedom ba5d9e139a add internationalization on lastfm results - issue #319 2014-06-29 19:41:42 +01:00
fossfreedom d345e75952 latest de translation 2014-06-28 23:29:18 +01:00
fossfreedom 0f20559e2f latest pt_BR translation 2014-06-28 22:11:23 +01:00
fossfreedom 5977335253 latest translations from launchpad 2014-06-28 21:44:37 +01:00
fossfreedom 6d5be5812a delay cover_size update slightly to improve usability 2014-06-28 15:18:45 +01:00
fossfreedom 5c20b3bdcc readme update 2014-06-28 10:54:57 +01:00
fossfreedom 17c871897e cleanup unneeded icons in source base 2014-06-28 10:27:28 +01:00
fossfreedom 8947aebe48 reversed shadow cover hover icons - prefer the standard white icons 2014-06-28 10:25:44 +01:00
fossfreedom 802cd5a36a various cleanups and fix display of entry-pane coverart if artist info is displayed and the entry-view is opened for the first time 2014-06-28 10:18:58 +01:00
fossfreedom 115a652a71 use more solid hover icons when covers have shadow effect 2014-06-26 21:36:12 +01:00
fossfreedom 2148fd428a corrected hover icon calculation to take into account icon spacing and padding correctly - issue #316 2014-06-26 21:11:18 +01:00
fossfreedom 9c90b0b8bb add missing files and add the alternative hover icon set 2014-06-25 23:27:14 +01:00
fossfreedom e10a75945a revamped single click icons - issue #300 2014-06-25 23:09:33 +01:00
fossfreedom b1837365a2 add ability to see and adjust what is playing - issue #296 2014-06-25 22:53:49 +01:00
fossfreedom d9dc24447c latest es and fr_CA translations - issue #288 2014-06-22 14:45:10 +01:00
fossfreedom b35f4b5615 add missing imports - issue #288 2014-06-22 10:33:02 +01:00
fossfreedom ad04ae14dd remove bool error and regen locales to capture extra message - issue #288 2014-06-22 10:30:40 +01:00
fossfreedom 8392648ffe fix propagation issues for areas that respond to button_release events - the idea being that button_release events must have a corresponding button_press event otherwise the call is invalid and comes from somewhere else - issue #298 2014-06-20 21:08:49 +01:00
fossfreedom fd07433c87 Merge remote-tracking branch 'origin/master' 2014-06-20 17:26:45 +01:00
fossfreedom a78ad58de5 fix issue with track artist filter opening when clicking on artist info panes - issue #309 2014-06-20 17:26:12 +01:00
fossfreedom 052df42b29 add missing queued to playing notification message - issue #288 2014-06-20 13:09:04 +01:00
fossfreedom cd3847ba67 latest fr_CA from transifex 2014-06-20 09:16:42 +01:00
David Mohammed cabc112d1c Merge pull request #310 from jrbastien/master
Updated monochrome coverart browser icon and original design files
2014-06-20 08:46:57 +01:00
jrbastien cfde5a42b4 Changing the monochrome coverart browser icon for the left tool bar and adding the original design files as per discussion in issue #300. 2014-06-19 21:25:37 -04:00
fossfreedom 72710425e5 latest fr_CA and it translations - issue #299 2014-06-19 20:24:32 +01:00
fossfreedom 606bb0d065 fix blink display of single click icon if moving the mouse fast between cells 2014-06-18 23:50:36 +01:00
fossfreedom 7ec7ff72e3 stop inadvertent refresh of tile display due to setting of the display text radio button on first load - issue #308 2014-06-18 19:58:39 +01:00
fossfreedom b6dd370470 new entryview icon - issue #300 2014-06-18 19:16:35 +01:00
fossfreedom 5b23026ae8 better spacing in flow appearance dialog - issue #266 2014-06-18 19:13:28 +01:00
fossfreedom e71656aeb3 fix locale display issues with the coverart info panes - issue #299 2014-06-17 20:06:00 +01:00
fossfreedom 67b5575fb4 latest it.po translation 2014-06-16 21:14:27 +01:00
fossfreedom 0a71517161 add and remove some debug trying to track down issue #307 2014-06-15 10:34:10 +01:00
fossfreedom 5735d6d6f3 added some more translator hints and ensure that these comments are correctly extracted - issue #299 2014-06-14 23:21:33 +01:00
fossfreedom c98881b676 latest translations from release 1.1 and update config files to pull the latest translation items from the source files - issue #299 2014-06-14 12:14:33 +01:00
fossfreedom 7806c91c86 incorporated recommendations from mgratton to resolve focus crashes - issue #305 2014-06-12 23:40:28 +01:00
fossfreedom a34ebbd3db better vertical pane handles - simple gradient with a smaller size. Works with dark themes and standard themes such as adwaita and ambiance 2014-06-10 21:41:03 +01:00
fossfreedom 635afa73e5 completely rewrite single click capability - issue #288 2014-06-09 22:34:40 +01:00
fossfreedom ee5b1a19ea ignore ALT-Left key to summons the quick search - issue #305 2014-06-08 09:43:28 +01:00
fossfreedom cb3b67991b replicated auto-open capability for track artist pane for artist info pane 2014-06-07 22:40:59 +01:00
fossfreedom 1504c90b4d stop using child allocated width and just use the paned position #298 2014-06-07 17:56:37 +01:00
fossfreedom 9a8efff960 more debug info to track down issue #298 2014-06-07 09:59:58 +01:00
fossfreedom 9068149d07 Merge remote-tracking branch 'origin/master' 2014-06-07 07:17:42 +01:00
fossfreedom 4decb6c260 add notification feedback that an album has been queue when single click append has been chosen - issue #288 2014-06-07 07:17:21 +01:00
David Mohammed 8303145003 Update README.md 2014-06-06 08:44:59 +01:00
fossfreedom 039a772795 fix local genre error on playing - issue #303 2014-06-06 00:08:40 +01:00
fossfreedom 84f34bcf18 hide whatplayingicon correctly 2014-06-05 21:43:53 +01:00
fossfreedom c766a4dedc fix locale tooltip for All Genres - issue #303 2014-06-05 21:37:42 +01:00
fossfreedom 4fe43a950f make the highlight a little more stylish - issue #298 2014-06-05 20:58:28 +01:00
fossfreedom 437dc66cf7 add highlight for the vertical panes to indicate that something can be selected - issue #298 2014-06-05 20:18:17 +01:00
fossfreedom d4e172aafb add highlight for the vertical panes to indicate that something can be selected - issue #298 2014-06-05 20:17:40 +01:00
fossfreedom 28a8c346c7 remove bottom display hiding option - issue #302 2014-06-04 19:45:00 +01:00
fossfreedom a4c8d23fbb fix for preferences button from plugins - issue #301 2014-06-04 19:38:44 +01:00
fossfreedom d35f173453 prelim stuff for displaying queue tracks - issue #296 2014-06-04 19:35:27 +01:00
fossfreedom 3a9589074f run pycharms optimize imports over the python modules in the project 2014-06-03 20:04:38 +01:00
David Mohammed 8cf80c99bd Update README.md 2014-06-03 17:39:25 +01:00
David Mohammed 0bbde2077c Update README.md 2014-06-03 16:30:56 +01:00
fossfreedom 519fb78c84 general cleanup - remove old rb2 code that is no longer applicable 2014-06-02 20:31:26 +01:00
fossfreedom 2d828b4587 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2014-06-02 00:18:31 +01:00
fossfreedom 0dd34ca8f3 missing file for entryview smallwindow support 2014-06-02 00:17:56 +01:00
fossfreedom 2d6bcd02b6 add missing file for compact view 2014-06-01 23:26:18 +01:00
fossfreedom 5fa4fd391e latest readme 2014-05-26 14:08:38 +01:00
fossfreedom 535059df62 add uninstall for coverart-browser - issue #232 2014-05-26 14:04:12 +01:00
fossfreedom 6f10706ac1 support smallwindow plugin - issue #295 2014-05-25 00:13:14 +01:00
fossfreedom a5a3d6b85c reformat code according to pycharm 2014-05-24 14:52:09 +01:00
fossfreedom cae74bf6e7 entryview add-to-playing menu item. Also have only one query model for the entire source rather than as before seperate query models for entry views and main source - issue #288 2014-05-24 00:10:19 +01:00
fossfreedom 08b979fa0a entryview add-to-playing menu item. Also have only one query model for the entire source rather than as before seperate query models for entry views and main source - issue #288 2014-05-23 23:52:40 +01:00
fossfreedom 7a20522c2f view add-to-playing menu item - issue #288 2014-05-23 19:54:11 +01:00
fossfreedom 8243b2ed34 single click queue adds to playing source - issue #288 2014-05-22 23:47:28 +01:00
fossfreedom 1ea5d3dd67 Add Views button to Play Queue and and Play Queue within the views button - issue #294 2014-05-21 21:37:05 +01:00
fossfreedom 4c2ef51454 latest README updates 2014-05-21 19:50:15 +01:00
fossfreedom c7696d412c slightly delay filter whilst typing - issue #293 2014-05-21 19:26:42 +01:00
fossfreedom 82adb4653d better spacing of the coverart - issue #291 2014-05-19 22:11:25 +01:00
fossfreedom 41e80ee47b various fixups part 2 - issue #291 2014-05-19 21:28:09 +01:00
fossfreedom 87ea90d054 various fixups - issue #291 2014-05-19 21:05:54 +01:00
fossfreedom ec24905ed0 update README with the latest information 2014-05-18 23:48:35 +01:00
fossfreedom 1758a56ef2 refactored track pane code issue #291 2014-05-18 23:32:33 +01:00
fossfreedom 934606eb2f fix for autosizing problem in compact view issue #289 2014-05-18 10:39:53 +01:00
fossfreedom 043790eda2 add follow song option to properties menu together with its implementation issue #278 2014-05-18 08:41:21 +01:00
fossfreedom eed81c45a1 complete stack conversion for artist view issue #287 2014-05-17 23:49:45 +01:00
fossfreedom edf995395a change display of cover to be a stack to give it an animation effect when changing the selection - issue #289 2014-05-10 23:49:23 +01:00
fossfreedom e12d09e52e cleanup and use scrolledwindow to display image rather than custom draw - issue #289 2014-05-10 21:23:31 +01:00
fossfreedom 0ec0cd8f75 replace notebook with GtkStack - issue #289 2014-05-09 22:36:54 +01:00
fossfreedom 473b656412 ignore pycharms idea files 2014-05-05 20:33:31 +01:00
fossfreedom 22c0b68add its not unusual to not be returned similar tracks for a given search - lets reduce the display of the info box to real errors being returned from the provider 2014-05-04 11:08:56 +01:00
fossfreedom 8edf61af6a add similar genre playlist 2014-05-04 10:54:59 +01:00
fossfreedom e8aad1c034 remember the setting of the entry view mode as well as making the menu signals unique for each entry view. We need to do this otherwise clicking on a menu option will invoke the method in the wrong entry view class - e.g. SongInfo dialog fails to display in compact view mode because the signal being invoked is the full entry view class. 2014-05-03 23:16:09 +01:00
fossfreedom 4091c9be50 fix closure of artist info where the icons confused the width calculation 2014-05-03 18:38:07 +01:00
fossfreedom d2be6895f0 some cleanup 2014-05-02 23:49:42 +01:00
fossfreedom 52b8588f3f add initial capability for compact vs full entry view mode - issue #289 2014-05-02 22:43:07 +01:00
fossfreedom e7669d3117 attempt to redraw correctly to improve appearance of icon - issue #285 2014-04-17 00:23:48 +01:00
fossfreedom 1804a55426 single click queue - issue #288 2014-04-17 00:09:00 +01:00
fossfreedom b514740e19 add similar playlist capability - issue #258 2014-04-14 22:24:44 +01:00
David Mohammed d39e964bc5 Update README.md 2014-04-04 21:57:09 +01:00
fossfreedom c7f6281e11 fix locale display of window title - issue #284 2014-04-04 21:34:37 +01:00
fossfreedom 4afc22c686 add filter by composer 2014-03-29 00:28:14 +00:00
fossfreedom fb64f74663 copy coverart_rb3compat module from release1.1 2014-03-28 21:50:44 +00:00
fossfreedom 3896c62844 fix search placement so that it correctly moves when rhythmbox window is resized 2014-03-28 21:42:38 +00:00
David Mohammed 52033cb0a9 Update README.md 2014-03-22 10:05:02 +00:00
fossfreedom 48c21be900 lets remember the last genre folder chosen - issue #215 2014-03-22 10:00:58 +00:00
fossfreedom 72a903992d Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2014-03-22 09:35:31 +00:00
fossfreedom f8351aadb4 lets save custom user icon info in the cache folder not the plugin folder so that it survives a reinstall - issue #215 2014-03-22 09:35:07 +00:00
David Mohammed 29da8eda3e Update README.md 2014-03-22 00:17:33 +00:00
fossfreedom b202684e0a boost artistinfo with echonest biography - issue #272 2014-03-21 23:50:55 +00:00
fossfreedom 873571f1ab add filter buttons to artist info to allow filtering by similar artist - issue #279 2014-03-17 21:31:41 +00:00
fossfreedom 48342cdd18 prettify export dialog - issue #266 2014-03-10 23:43:49 +00:00
fossfreedom 90e02345fb prettify the browser plugin preferences - issue #266 2014-03-10 21:57:41 +00:00
fossfreedom b48731ed6a cleanup 2014-03-09 23:10:31 +00:00
fossfreedom 870b4cdd7b rework jump to playing so that all views are correctly scrolled to - issue #278 2014-03-09 23:09:46 +00:00
fossfreedom ecfdc66706 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2014-03-09 18:53:19 +00:00
fossfreedom aa8d23e65b fix focus issues so that we don't jump back to the plugin unless we started playing from the plugin - issue #278 2014-03-09 18:52:54 +00:00
David Mohammed 5013de9127 Update README.md 2014-03-09 11:54:10 +00:00
fossfreedom 0c0d4d6c57 add support for JumpToPlaying song shortcut - issue #278 2014-03-09 11:43:36 +00:00
fossfreedom 9da85017b9 manually apply #277 patch 2014-03-06 18:31:31 +00:00
fossfreedom 4174f71436 fix track artist filter display on startup if closed 2014-02-15 22:54:53 +00:00
fossfreedom 3088768fe6 some tidyups 2014-02-15 21:24:11 +00:00
fossfreedom acb6caaba0 wrong override caused failure to check lastfm plugin presence in artist view - issue #264 2014-02-15 00:29:54 +00:00
fossfreedom 8619d73007 fix 14.04 playlists + openfolder plugin support - issue #263 2014-02-14 22:54:33 +00:00
fossfreedom 18666de36f change some of the defaults for new users 2014-02-05 23:53:23 +00:00
fossfreedom e9af41d634 rework track artist filter to mirror the artist info pane - issue #271 2014-02-05 00:02:21 +00:00
fossfreedom 9763743a95 snap shut the artist paned if too small to visually see anything useful - issue #268 2014-02-04 19:46:58 +00:00
fossfreedom b6ffa2de70 update README 2014-02-04 00:07:51 +00:00
fossfreedom 74adbe6c3f transfer darker stuff from popups.xml to popups.xml.in so that it is not overwritten when we run the translation stuff 2014-02-04 00:06:02 +00:00
fossfreedom fd42d4e601 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2014-02-04 00:03:11 +00:00
fossfreedom 0b97a39edc add artist-info-pane implementation - issue #268 2014-02-04 00:03:01 +00:00
David Mohammed 23e2ed5297 Merge pull request #270 from jrbastien/master
Fine tuning of the lighter theme and added new darker theme.
2014-02-03 15:17:06 -08:00
jrbastien 273841ec65 Fine tuning of the lighter theme and added new darker theme. 2014-02-02 19:29:25 -05:00
fossfreedom d128149d8d this is now a python3 only install - remove the old python2 installation stuff 2014-01-26 22:01:39 +00:00
fossfreedom 9cdf49e975 fix rb2 encoding issue with last commit - issue #262 2014-01-22 21:40:49 +00:00
fossfreedom 74319826d2 correct action_name to be valid in RB3 - issue #262 2014-01-22 21:33:16 +00:00
fossfreedom afea82eee5 use rb3compat functions - issue #257 2014-01-22 20:30:24 +00:00
fossfreedom dda323ac04 fix crash for album titles that have unicode characters - issue #257 2014-01-22 20:20:23 +00:00
fossfreedom a09a25690c fix crash for non-english locales where artist is not translated by the plugin 2014-01-22 18:10:09 +00:00
fossfreedom f27ee2464c replace tabs with spaces 2014-01-20 23:59:04 +00:00
fossfreedom 18aaf08f4b fix sorting of artist view after album information such its rating has been changed - issue #257 2014-01-20 23:14:28 +00:00
fossfreedom c03166007f fix boundary issues which caused crash when moving albums to the end of the view - issue #259 2014-01-20 23:06:56 +00:00
fossfreedom 0c66334569 latest sprite image - issue #240 2014-01-20 09:11:34 +00:00
fossfreedom 1e88bab104 latest bg translation 2014-01-19 20:23:46 +00:00
fossfreedom b2eabeb8eb fix sorting issue after restart together with scroll sync issue between artist and tile view - issue #257 2014-01-19 19:46:33 +00:00
fossfreedom d93ea16d1a fix for float issues in earlier versions of rhythmbox - issue #256 2014-01-18 21:22:22 +00:00
fossfreedom ac321f7815 update README for beta 3 release 2014-01-18 14:43:05 +00:00
fossfreedom c53e6c33f8 scale tooltip to original image dimensions - issue #256 2014-01-18 14:35:15 +00:00
fossfreedom 368dda444b latest translations 2014-01-18 14:02:54 +00:00
fossfreedom d2640cf11a latest translations 2014-01-16 22:50:23 +00:00
fossfreedom b56ec0e5d9 reorder the album when the sort keys change - issue #254 2014-01-16 21:34:45 +00:00
fossfreedom 5d0d513f93 fix trusty icon offset issue 2014-01-15 23:57:53 +00:00
fossfreedom 914098f4f4 fix trusty spacing issues 2014-01-15 22:54:43 +00:00
fossfreedom 16beb62e9c fix display of icons in trusty - issue #253 2014-01-15 22:00:29 +00:00
fossfreedom c1eb1d279e redisplay pictures in iconview 2014-01-15 21:44:13 +00:00
fossfreedom f9ab0104cd remove depreciation of dialog creation 2014-01-15 20:46:44 +00:00
fossfreedom 1a7e154f64 more depreciation this time with GObject.idle_add 2014-01-15 20:16:02 +00:00
fossfreedom 4cfe692b9e fix various depreciations in trusty 2014-01-15 19:58:19 +00:00
fossfreedom d165f5fc0c updated README for beta 2 2014-01-14 21:50:24 +00:00
fossfreedom 06a7478c70 latest translations 2014-01-14 21:23:10 +00:00
fossfreedom 7d629e5bcb add a space after labels in export dialog - issue #248 2014-01-14 21:11:19 +00:00
fossfreedom 5ddf0b306c resolve misaligned sorting issues - issue #250 2014-01-14 20:46:30 +00:00
fossfreedom ade9939053 rerun translation extraction 2014-01-13 21:50:19 +00:00
fossfreedom e6f8c887c9 Merge branch 'sort'
Conflicts:
	po/fr_CA.po
2014-01-13 21:35:58 +00:00
fossfreedom db09860d08 add new sprites, fix sorting of artists column so that we cannot become unsorted and finally, fix a bug on startup showing sort key error 2014-01-13 21:33:41 +00:00
fossfreedom 1718296e46 swap sort keys for album artist and album name so that we can use those sort keys correctly - issue #249 2014-01-12 21:56:14 +00:00
fossfreedom f5a6b56c3a latest cs translation 2014-01-12 12:37:25 +00:00
fossfreedom 53ff1990d2 add new cs translation 2014-01-11 23:18:52 +00:00
fossfreedom 4e5929237f tidied up sort actions 2014-01-11 22:52:13 +00:00
fossfreedom f1f4f451b8 Merge branch 'master' into sort
Conflicts:
	po/fr_CA.po
2014-01-11 12:03:32 +00:00
fossfreedom 581c1b6c0c work in progress - connect artist sort buttons and sort viewable albums 2014-01-11 12:01:33 +00:00
fossfreedom 2cafad60b6 use the correct translator comments for px - issue #248 2014-01-10 00:14:43 +00:00
fossfreedom 1cd75c4664 give new artist icons its own config called sort_artist 2014-01-09 23:51:38 +00:00
fossfreedom 161997c0d0 use separate sort and sort order buttons for album and artist views and connect signals for these buttons to sort independently 2014-01-08 23:26:34 +00:00
fossfreedom cc32904e80 latest fr_CA translation 2014-01-08 14:54:11 +00:00
fossfreedom c0edd4895e resolve out of synch picture popup - issue #244 2014-01-07 21:53:03 +00:00
fossfreedom f9dfa0c39d add latest fr_CA translation 2014-01-05 23:42:49 +00:00
fossfreedom 7eaeebf168 set the default sort indicator on the artist sort column to be ascending - issue #245 2014-01-05 23:08:09 +00:00
fossfreedom 290f06981e rerun translation stuff to capture new lastfm translation 2014-01-05 16:05:37 +00:00
fossfreedom 87de8b9a8b check if valid lastfm connection before searching - issue #243 2014-01-05 12:25:43 +00:00
fossfreedom 7a233c2dbb display artist albums ordered by year + their name rather than the current random order - issue #241 2014-01-05 11:18:28 +00:00
fossfreedom 931135ba70 remove secondary image update for disabled toolbar icons - issue #240 2014-01-05 10:23:31 +00:00
fossfreedom adba91d077 fix shake in treeview caused by expander lines - need to add space in next column to allow the expander lines to float into - issue #239 2014-01-05 00:13:17 +00:00
fossfreedom 466112068a encode unicode string to fix crash when clicking on artist - issue #238 2014-01-04 22:57:50 +00:00
fossfreedom 767e2791cf tidied up clicking on row actions to correct display of track & cover pane together with ensure that we show the artist cover search when clicking on artists - issue #236 2014-01-04 09:00:27 +00:00
fossfreedom f60bf048b2 latest es.po 2014-01-03 23:21:42 +00:00
fossfreedom 006d621327 add english language locales 2014-01-03 21:34:09 +00:00
fossfreedom 03b01b58f2 ensure translation of Views button works correctly 2014-01-03 21:17:54 +00:00
fossfreedom e915b353cc translation clarifications 2014-01-03 04:39:00 +00:00
fossfreedom cf8e2b383e version and help info moved to v1.0 2014-01-02 11:02:31 +00:00
fossfreedom 272127f5a3 make sure we copy the plugin3 file to allow the POSTINST script to run in the rb3 debian package 2014-01-01 21:12:55 +00:00
fossfreedom d4f0e9212a corrected version and source icon display for rb3 2014-01-01 20:40:42 +00:00
fossfreedom 78546f3643 re-run translation stuff to upload to launchpad 2014-01-01 15:31:00 +00:00
David Mohammed e1cd035095 Update README.md 2014-01-01 00:56:59 +00:00
fossfreedom 07aca0cdf6 redo deletion of artists when there is no albums - this time lets do stuff simply - just look at the iters we remembered when adding the albums to artists - issue #209 2013-12-28 22:05:30 +00:00
fossfreedom 7b96b47a6c correctly rename album artist and update the artist model - issue #209 2013-12-28 19:20:14 +00:00
fossfreedom d3654e1457 bug fix crash when modifying album in artist view 2013-12-28 17:31:04 +00:00
fossfreedom 98045cae32 blank expander column allows clicking on artist cover - issue #209 2013-12-28 15:15:26 +00:00
fossfreedom 50ae5a2a99 remove export and reduce search to just the artist - issue #209 2013-12-28 12:11:01 +00:00
fossfreedom 0ca674fb7b add right click menu for artists - issue #209 2013-12-28 11:50:47 +00:00
fossfreedom 0ed6f0e3ec tidied up permissions and reduce spacing in carousel to improve look 2013-12-27 17:09:11 +00:00
fossfreedom ed1d8899be add lighter icons to the Makefile 2013-12-27 16:27:15 +00:00
fossfreedom 0c8d408688 disable flowview if webkit support is disabled 2013-12-27 09:02:16 +00:00
fossfreedom d67a802b3d updated readme 2013-12-27 08:57:50 +00:00
fossfreedom 51c05b6202 add xingmux for accurate bitrate resampling 2013-12-27 00:40:41 +00:00
fossfreedom 2a098b1047 copy po's from release 0.9 2013-12-23 00:24:24 +00:00
fossfreedom 5effd50acd latest translation updates 2013-12-23 00:19:14 +00:00
fossfreedom 729d7f1ff0 sync artist view with the other views so that the artist last selected in the other views is scrolled to correctly 2013-12-22 23:50:40 +00:00
fossfreedom 47faf9a509 reflect album name changes back into the artist view. Also took the opportunity to fix a bug specific to RB3 to ensure all track changes are correctly updated in the album-model - issue #209 2013-12-20 18:20:24 +00:00
fossfreedom 6afe9bbed5 remove naff try except by using arrays rather than generators 2013-12-18 23:35:16 +00:00
fossfreedom 6bb5f885bb bug fix when calculating album year - ignore tracks where the year is zero ... this can cause the year to be the current date because the min value returns zero rather than the min of real track years 2013-12-18 23:10:11 +00:00
fossfreedom f763b00f3a cope with locales in markup - issue #209 2013-12-18 16:45:04 +00:00
fossfreedom 7d903e84cf format album names for artist view - issue #209 2013-12-18 16:38:59 +00:00
fossfreedom 7cbf51d168 update README for toolbar changes 2013-12-18 00:06:05 +00:00
fossfreedom 4a1443cf66 link our plugin menu in the music library up with our plugin - issue #230 2013-12-16 23:55:21 +00:00
fossfreedom 7a45d9531e switch to music library source when requested from the plugin view button - issue #230 2013-12-15 16:16:22 +00:00
fossfreedom b1544a8983 temporary fix to prevent crash for RB2.96 2013-12-15 00:12:02 +00:00
fossfreedom 1ff243df66 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-12-14 23:59:09 +00:00
fossfreedom 9076627ca9 rework toolbars to be RB3 visually compatible with popups placed below the buttons and themed accordingly. Also taken the opportunity to redo the right toolbar to fit on one less line since this sidebar is often used with lyrics which is wider and thus offers more space. Preliminary views button created for library source although not connected as yet. issue #230 2013-12-14 23:58:21 +00:00
fossfreedom 49d9540f8c 26 languages not 22 in the README 2013-12-07 21:24:46 +00:00
fossfreedom 5d1e68c268 more readme changes 2013-12-07 21:09:20 +00:00
fossfreedom 4692c6b8ee add rb2 support for repeat one song - issue #231 2013-12-07 20:52:53 +00:00
fossfreedom f34fd79952 revert plugin loader 2013-12-07 20:16:04 +00:00
fossfreedom 295ee80351 add rb3 menu item for repeat one song - issue#231 2013-12-07 20:15:05 +00:00
fossfreedom a508e52f0d latest README for changes 2013-12-07 17:20:43 +00:00
fossfreedom 2bda0f1cb0 reduce list of target to just uris - issue #233 2013-12-07 17:12:21 +00:00
fossfreedom 74f37075e5 cleanup 2013-12-07 15:08:18 +00:00
fossfreedom 88821ef68a drag-drop support for albums in artist view 2013-12-07 14:57:13 +00:00
fossfreedom 677855e5c7 fix cancel mechanism for artist search 2013-12-07 00:24:07 +00:00
fossfreedom d593463a25 cleanup 2013-12-06 19:39:16 +00:00
fossfreedom 66070a6b0e cleanup 2013-12-03 23:06:35 +00:00
fossfreedom 66b4895458 rework to use new coverartextdb 2013-12-02 21:49:02 +00:00
fossfreedom 2f6af4fa7a use coverview for artists as well as for albums 2013-11-24 21:19:00 +00:00
fossfreedom 80eefa8737 expand or collapse artist on single click 2013-11-23 13:27:41 +00:00
fossfreedom 5fa51b6ed3 allow drag-drop from coverview and also autoexpand when clicking on artist 2013-11-22 22:54:31 +00:00
fossfreedom 4bdd1b4809 show artist cover in a enlarged tooltip 2013-11-22 18:55:48 +00:00
fossfreedom 0c65a494d6 use download covers menu option to download artist covers 2013-11-21 22:48:50 +00:00
fossfreedom f0acad9dca also support album cover update for drag and drop events 2013-11-20 23:21:40 +00:00
fossfreedom 10b4c152c4 support drag and drop of pictures onto artists 2013-11-20 20:29:33 +00:00
fossfreedom 1eab887896 fire the load of the artists after the album model has loaded so that the artists view is correctly initialised 2013-11-17 13:00:58 +00:00
fossfreedom 616ccb2d53 add standard right-click menu for albums 2013-11-17 09:42:40 +00:00
fossfreedom 8b5e234cd2 link filters to artists so that they show or hide artists in the treeview 2013-11-16 10:28:31 +00:00
fossfreedom dfc0002a2c some cleanups 2013-11-13 21:54:30 +00:00
fossfreedom 111147ca2a remove unneeded file 2013-11-13 20:52:22 +00:00
fossfreedom 297378349c change view to be an album-artists view 2013-11-13 20:51:51 +00:00
fossfreedom 0678179585 convert adding of artists as iterators to improve visual appearance when loading 2013-11-12 22:55:51 +00:00
fossfreedom a03119ef29 disable rather than hide toolbar objects 2013-11-11 23:11:51 +00:00
fossfreedom 66ddba7d78 remove unnecessary file 2013-11-11 00:10:22 +00:00
fossfreedom 4a23b78816 update toolbar signal whenever view is initialised or moved to 2013-11-11 00:09:53 +00:00
fossfreedom 07273d8351 remove obsolete file 2013-11-08 23:50:35 +00:00
fossfreedom 1dc678f31f refactor toolbar stuff into its own module 2013-11-08 23:50:09 +00:00
fossfreedom 64f7f3a978 various cleanups and use RB.search_fold for ordering of track artists 2013-11-08 21:20:04 +00:00
fossfreedom 846e995d82 add dummy column so that main column doesnt excessively expand to fill the space 2013-11-08 13:38:35 +00:00
fossfreedom 840ef0fd68 add clickable header for sorting 2013-11-08 13:00:03 +00:00
fossfreedom c562d3c4b3 ensure startup of view has a collapsed paned and is also initialised correctly when switching to it from another view 2013-11-06 23:44:27 +00:00
fossfreedom b2aec88ccf rework to use selection changed event which is similar to how the iconview is done 2013-11-06 22:53:59 +00:00
fossfreedom 7302165293 connect click and double click events 2013-11-05 00:09:36 +00:00
fossfreedom 9502bcf528 display artist and album images in tree-view 2013-11-04 23:00:24 +00:00
fossfreedom afbc8bd3b9 display albums for artists when the artist is expanded 2013-11-04 19:37:23 +00:00
fossfreedom 958e6ad6de refactor to separate data model from the view to be analagous to how album data model is done 2013-11-03 18:07:48 +00:00
fossfreedom 7027112b58 reactivate artist button ready for new artist treeview 2013-10-31 21:57:05 +00:00
fossfreedom 2c8554d79f display message if albums do not meet threshold - issue #213 2013-10-29 17:22:57 +00:00
fossfreedom 16f6055488 make sure double click and hover play use favourites - issue #213 2013-10-29 00:28:10 +00:00
fossfreedom d09ac59971 use 'New Playlist' as the default label when adding a new playlist - issue #213 2013-10-29 00:19:27 +00:00
fossfreedom 9e83e420bc its back! trick was realising calculation needs to be in widget coords - issue #84 2013-10-28 11:13:48 +00:00
fossfreedom 7e2eb1b8f9 temporarily abandon hotspot functionality - when view is scrolled it doesnt work anyway due to the way the hotspot position is calculated. Need to investigate further - issue #84 2013-10-28 09:42:38 +00:00
fossfreedom d7b7de854a rejig to delay immediate hotspot display slightly and also to force a view redraw so that hover is displayed even if the mouse is not moving - issue #84 2013-10-28 09:24:59 +00:00
fossfreedom 025cd52e3c refine hover display - displays immediately if move cursor over the hotspot region - issue #84 2013-10-27 23:42:24 +00:00
fossfreedom ab7643d7fa turn-off tooltips if cover display-text is active - issue #228 2013-10-26 23:14:54 +01:00
fossfreedom 211f4019bb slightly reduce time interval and remove print statements 2013-10-26 23:00:43 +01:00
fossfreedom 5ccd07e4b2 rework with new button images and half-second delay before display of hover - issue #84 2013-10-26 21:36:55 +01:00
fossfreedom 7b2c529228 rework to support play-pause on hover icon 2013-10-23 21:58:19 +01:00
fossfreedom 2a28592360 rejig so that iconview controls play icon 2013-10-23 20:08:33 +01:00
fossfreedom 00e0c80594 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-10-22 21:37:27 +01:00
fossfreedom a79591f9c0 ensure play icon responds to mouse hovering over it by dimming to show it is clickable - issue #84 2013-10-22 21:37:04 +01:00
David Mohammed f32713855f Update README.md 2013-10-20 17:49:03 +01:00
fossfreedom 27fdc07f28 fix multiple progress bars appearing on loading - issue #226 2013-10-20 17:36:28 +01:00
fossfreedom 8ba870184d add rb3 monochrome icon for the browser source icon - issue #225 2013-10-20 17:08:35 +01:00
fossfreedom bd9f7c52c0 update readme and available GTK version for single click play - issue #84 2013-10-19 22:37:53 +01:00
fossfreedom 3fd7fa281e single click play on hovered play-symbol shown in cover - issue #84 2013-10-19 22:06:59 +01:00
fossfreedom cafda03761 lighten the mood 2013-10-16 22:07:00 +01:00
fossfreedom 752357ad68 removed obsolete file 2013-10-13 19:49:58 +01:00
fossfreedom 1394cedbd2 latest az translation 2013-10-13 19:47:26 +01:00
fossfreedom 5bc4d5ef31 updated missing artwork images - issue #183 2013-10-12 14:05:16 +01:00
fossfreedom 97ec1dab0a ensure that text_within option does not conflict with add-shadow option 2013-10-11 21:10:16 +01:00
fossfreedom d0e3f218e0 link up coverart-text-pos with cellrenderer 2013-10-11 20:14:36 +01:00
fossfreedom e37655d7e0 add text position option to preferences and schema 2013-10-11 00:02:50 +01:00
fossfreedom d8967fb4e1 add cellrenderer for the text embed capability 2013-10-10 22:59:46 +01:00
fossfreedom b7a5a8e023 readded jrbastiens changes in popup.xml into the translation equivalent so that we dont lose the changes when we regenerate the translation files later 2013-10-07 13:30:58 +01:00
David Mohammed 57808278d3 Update README.md 2013-10-07 13:19:27 +01:00
David Mohammed 9412ddfb29 Merge pull request #222 from jrbastien/master
Introducing a new icons theme for the coverart browser
2013-10-07 05:09:55 -07:00
jrbastien d99cab53d1 Merge branch 'master' of https://github.com/jrbastien/coverart-browser 2013-10-06 20:00:46 -04:00
jrbastien 9312c37305 Adding the documentary genre as an alternate genre for the spoken genre. 2013-10-06 19:58:23 -04:00
Jean-Rene Bastien f8eb618d84 Delete Tour_Effel.png
Custom icon. Should not have been included in the theme.
2013-10-06 19:33:55 -04:00
jrbastien 3f610a74a1 Introducing a new icons theme with the goal of having something less distracting on the eyes. All icons have been redesigned but the genre sprite. 2013-10-06 19:29:13 -04:00
fossfreedom 12826cdde6 minor mods for finnish translation 2013-10-05 23:05:01 +01:00
fossfreedom c6eb082aca ensure that columns are correctly ordered - issue #219 2013-09-30 20:39:22 +01:00
fossfreedom 58815e03a9 add progress bar if required for RB3.0 - issue #189 2013-09-29 20:14:58 +01:00
fossfreedom 1380515c63 made properties option favourites and quick artist filter persistent - issue #213 2013-09-26 21:06:59 +01:00
fossfreedom 3c18ca9052 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-09-23 19:45:45 +01:00
fossfreedom f9422d8a19 remove all the pointless menu label changing stuff - #213 2013-09-23 19:45:23 +01:00
fossfreedom d64f1bcaf7 change labels for favourites - issue #213 2013-09-22 10:21:13 +01:00
fossfreedom 27c386e172 fix for external plugin case sensitive issue and fix wrong group name for opencontainingfolder plugin 2013-09-17 00:01:24 +01:00
fossfreedom 5e2cec619e add new translation - pt 2013-09-16 22:49:51 +01:00
fossfreedom 8ab66a192f tidied master readme 2013-09-11 21:10:42 +01:00
fossfreedom 4e480c40fe Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-09-11 20:58:12 +01:00
fossfreedom 9b58df3784 add use favourites menu option to properties menu - issue #213 2013-09-11 20:57:54 +01:00
fossfreedom 34583e0086 latest translations from launchpad 2013-09-11 16:09:58 +01:00
fossfreedom 368e582fe2 fix initial display of album info on startup - issue #214 2013-09-11 08:35:00 +01:00
fossfreedom 186977f6ef ensure the version of the coverart_search_providers plugin is correct to prevent unnecessary crashes and other confusion - issue #212 2013-09-08 21:53:22 +01:00
fossfreedom ce119120b7 connect resize art option - issue #212 2013-09-08 20:43:08 +01:00
fossfreedom 6759472a2e add optional mp3 conversion with bitrate calculation - issue #212 2013-09-08 11:47:55 +01:00
fossfreedom 58ef685380 latest spanish translation 2013-08-27 20:51:31 +01:00
fossfreedom 594feaca93 latest ro translation 2013-08-27 12:45:10 +01:00
fossfreedom edcf74f005 latest az, fr & pl translations 2013-08-26 16:38:51 +01:00
fossfreedom a6993e3d2e latest tr.po 2013-08-26 10:54:07 +01:00
fossfreedom 5ccc4c1c7d Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-08-25 23:54:33 +01:00
fossfreedom 35335da6b8 latest translations and removed those translations that are less than 50 percent translated 2013-08-25 23:54:22 +01:00
David Mohammed ba9ae93d87 Update README.md 2013-08-25 23:45:18 +01:00
David Mohammed 6b999b0099 Update README.md 2013-08-25 23:33:53 +01:00
David Mohammed 388e8faec6 Updated - ready for release 2013-08-25 23:22:52 +01:00
fossfreedom da92ed7d9a bug fix - prevent help button being continually duplicated when preferences dialog repeatedly opened 2013-08-25 10:09:01 +01:00
fossfreedom a17a32e33b latest translations 25/08/13 2013-08-25 09:43:29 +01:00
fossfreedom 17db0b98df remove obsolete icons 2013-08-22 14:09:04 +01:00
fossfreedom d500fdb0bf latest translations 22/08/13 2013-08-22 11:52:14 +01:00
fossfreedom 69fccf10f4 latest fr_CA translations - issue #202 2013-08-18 20:06:11 +01:00
David Mohammed fa2050486e remove "sh" from the running of the bash shell script install.sh - issue #211 2013-08-18 15:57:08 +01:00
fossfreedom 3f85bc1da6 updated padding description - issue #202 2013-08-18 15:42:56 +01:00
fossfreedom 9d59cb0748 remember the position of the quick artist filter handle position - issue #210 2013-08-18 15:26:46 +01:00
fossfreedom e1aa1ba082 readd previous translations that jrbastien made - issue #202 2013-08-18 10:57:58 +01:00
fossfreedom 8ac60117ac center view buttons and allow search button to expand to its full extent - issue #202 2013-08-18 10:33:18 +01:00
fossfreedom 7179b2e601 latest fr_CA translation 2013-08-17 21:58:21 +01:00
fossfreedom e93a7dd36b Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-08-17 20:38:53 +01:00
fossfreedom 85df2fa79d regenerated po's using less restrictive intltool-extract 2013-08-17 20:30:27 +01:00
fossfreedom 3e55c8daa9 changed tile to tiles, tooltip swap and added translator info for 'All' 2013-08-17 20:07:16 +01:00
David Mohammed 59f1a840ec Update README.md 2013-08-15 23:02:10 +01:00
fossfreedom 74d6a62238 add help button to preferences dialog called from coverart-browser 2013-08-13 19:36:46 +01:00
fossfreedom ef657da311 remove obsolete code when right click on source in rb2.96 2013-08-11 21:31:12 +01:00
David Mohammed 250eea8053 Update README.md 2013-08-10 21:24:59 +01:00
fossfreedom f243e2aba2 add llyrics and fileorganizer as supported plugins for rb2.99 users 2013-08-10 21:17:23 +01:00
fossfreedom e47c3dedac latest translations update 2013-08-07 14:50:31 +01:00
fossfreedom 2a2a51c1ed hook up quick artist filter to properties menu item 2013-08-06 21:54:04 +01:00
fossfreedom c27ae05e3c connect up quick artist filter to album model 2013-08-06 20:43:34 +01:00
fossfreedom 00ccb84fc3 Merge branch 'master' of https://github.com/fossfreedom/coverart-browser into quickfilter 2013-08-05 21:07:30 +01:00
fossfreedom 83e73f95d5 fix missing import of RB 2013-08-05 21:06:05 +01:00
fossfreedom 154e5df152 force the state of a stateful action - this fixes the issue where an accelerator is used to activate rather than a menu option - issue #208 2013-08-05 21:04:23 +01:00
David Mohammed 5781a67a70 Update coverart_rb3compat.py 2013-08-05 13:26:01 +01:00
David Mohammed 0322dfd248 Merge pull request #207 from dmo60/patch-1
enable passing a custom stock_id for actions
2013-08-05 05:22:29 -07:00
Timo Loewe ecd1a07026 Update coverart_rb3compat.py
enable passing a custom stock_id for actions
2013-08-04 12:48:48 +02:00
fossfreedom 3273c920ec create paned layout and connect model to library source 2013-08-04 00:20:14 +01:00
fossfreedom 7b33009e67 fixed name reference for our panedcollapsible widget 2013-08-03 21:30:29 +01:00
fossfreedom 73e218e99f update readme for iconview toggle 2013-08-03 18:10:35 +01:00
fossfreedom 30a6ebeb45 add optional track-view toggle to keep consistent with flow-view 2013-08-03 18:08:15 +01:00
fossfreedom 2e27bbd794 add rb3 install instructions - issue #194 2013-08-03 13:48:52 +01:00
fossfreedom a029c43144 add option to install RB version 3 - issue #194 2013-08-03 13:46:21 +01:00
fossfreedom 474fba83c0 fix python3 depreciation warnings in ubuntu saucy - issue #187 2013-08-02 22:23:27 +01:00
fossfreedom 491d35b581 update makefile for global installation and deb creation 2013-08-02 16:14:48 +01:00
fossfreedom e7b68f91b7 uplift source files for translation activities 2013-08-02 15:01:45 +01:00
fossfreedom c348988325 each view now remembers if the track-view should be open or closed when switching between those views - issue #206 2013-08-01 23:06:06 +01:00
fossfreedom 6c76820891 relaxed check for mouse to be over the coverflow when controlling the coverflow via keyboard - issue #206 2013-07-31 10:18:17 +01:00
fossfreedom fadb361a0c remove scrollbar 2013-07-30 23:07:05 +01:00
fossfreedom 52d6828480 remove unwanted file 2013-07-30 23:02:47 +01:00
fossfreedom 9d0666144b fix resizing issue on coverflow 2013-07-30 23:02:17 +01:00
fossfreedom c9d27129b1 some python3 fixups 2013-07-30 21:20:21 +01:00
fossfreedom 5fc8e853ad move signal update out of loop so that it always fires not just when the caption is visible 2013-07-29 22:00:52 +01:00
fossfreedom 0c072028c0 add optional argument 2013-07-29 20:46:32 +01:00
fossfreedom bc3d194920 centre view buttons correctly on top-bar - issue #206 2013-07-29 14:59:07 +01:00
fossfreedom da37365b38 display active album in trackview whilst scrolling in coverflow - issue #206 2013-07-29 14:08:34 +01:00
fossfreedom ebfc12ef15 reset on_first_use counter after it actually has been through this section of code 2013-07-29 11:38:59 +01:00
fossfreedom 017312e7c5 expand pane on very first use on coverflow - issue #206 2013-07-29 11:27:08 +01:00
fossfreedom 5302cca0a1 changed label to reflect true meaning 2013-07-29 09:50:53 +01:00
fossfreedom b9f82a794f changed filter message to be more meaningful - issue #206 2013-07-29 09:48:29 +01:00
fossfreedom b9ac6a5c8a Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-07-28 23:52:34 +01:00
fossfreedom 1e1e6481a9 move items creation to filter_changed to be consistent with the rest of the html manipulation 2013-07-28 23:52:03 +01:00
David Mohammed 6840a52d67 Update README.md 2013-07-28 19:21:02 +01:00
fossfreedom 42fb3f650b add support to disable call to webkit library via a gsettings value - issue #199 2013-07-28 19:19:02 +01:00
fossfreedom 9130f32038 focus issues on coverflow after quick search and expand descriptions of the abstract view 2013-07-28 09:15:14 +01:00
fossfreedom 6a7b7f7146 remove obsolete code and renamed methods that are really just private to the coverflow 2013-07-28 08:59:50 +01:00
fossfreedom f4f1086883 fix for reason why rating was not being recalculating - issue #204 2013-07-27 22:37:33 +01:00
fossfreedom 14b84f1a9c lets add generic support for RB2.98 and 2.99 i.e. assume something has modified on the album 2013-07-27 17:26:56 +01:00
fossfreedom 44cf1a94c6 remove some strange code causing spurious album updates whenever anything changes - now the track modified signal is only thrown when a track artist details are changed 2013-07-27 17:19:02 +01:00
fossfreedom e348ec9b45 fix locking issue due to incorrect use of threads 2013-07-27 05:51:01 +01:00
fossfreedom 0ea4d413c3 some cleanups including reducing flickering when changing album properties or changing cover as well as fixing the quick search on the flow 2013-07-26 20:15:39 +01:00
fossfreedom 69800ebb6d move separators to more logical locations 2013-07-26 12:57:20 +01:00
fossfreedom e9c869e5ad fix typos primarily in album-updated emit signal 2013-07-26 12:53:50 +01:00
fossfreedom 34367b8e85 add jrbastiens new icons and hide the artist button 2013-07-26 09:25:42 +01:00
fossfreedom 77436aca08 fix quick search on iconview if flowview was the default on startup 2013-07-25 23:51:55 +01:00
fossfreedom d72cac5424 a bit of a hack - refilter the coverflow whenever something on an album changes such as changing the picture 2013-07-25 21:50:01 +01:00
fossfreedom 10d26bb4d3 disable default drag-drop on webview and enable the active element to receive the dropped picture from the cover pane 2013-07-24 20:36:21 +01:00
fossfreedom f9cff865bf substitute properties.png with jrbastien's version 2013-07-24 13:28:46 +01:00
fossfreedom 0e43b68a73 simplify processing, link display to flow-max number, re-add slider and circular-flow 2013-07-23 21:09:25 +01:00
David Mohammed 0541665488 Update README.md 2013-07-23 13:00:57 +01:00
fossfreedom 76a9610422 add random album functionality - issue #196 2013-07-21 10:34:27 +01:00
fossfreedom 6c09a042f9 connect automatic click setting to coverflow 2013-07-20 22:09:28 +01:00
fossfreedom 844a514ccc revert toggle buttons to text only and differentiate the label colour depending upon which is active 2013-07-20 11:07:53 +01:00
fossfreedom b991fd6993 add separator in properties menu to differentiate between preferences and other view functionality 2013-07-20 09:48:36 +01:00
fossfreedom ab47c5f97d add search providers dialog to properties button 2013-07-19 20:40:05 +01:00
fossfreedom 6a5eace53d link up plugin preferences dialog to properties button menu 2013-07-19 14:57:07 +01:00
fossfreedom 639429fed1 changed my mind - lets display albums anyway - just need to add the limit when refactoring the batch processing part 2013-07-18 22:31:13 +01:00
fossfreedom 28ee9474f8 connect max albums to preferences 2013-07-18 21:55:57 +01:00
fossfreedom b98ea85bdb add max number of albums to display in coverflow 2013-07-18 21:38:47 +01:00
fossfreedom bb02cbc035 correct names for notify properties so that they sync when preferences change 2013-07-18 14:29:34 +01:00
fossfreedom 3ef9bb6afc refactor coverflow code for properties and their signals 2013-07-18 13:14:34 +01:00
fossfreedom b3ed080e09 wrong default width for coverflow 2013-07-18 12:24:30 +01:00
fossfreedom eb5014483a slight modifications to README 2013-07-17 23:54:07 +01:00
fossfreedom 8bd4799bf4 connect startup gsettings values to various values in the coverflow index.html 2013-07-17 23:35:49 +01:00
fossfreedom a3c3631ecb link prefs dialog flow items to gsettings 2013-07-17 19:27:24 +01:00
David Mohammed e4ecbb3c11 Add LICENSE file via addalicense.com 2013-07-17 01:21:38 -07:00
fossfreedom 94f7e1045e Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-07-16 21:20:08 +01:00
fossfreedom a5a9096732 add check on paned release event - temporary resolution for flow click issue 2013-07-16 21:20:00 +01:00
fossfreedom 1f3f5b7df1 add GUI options to preferences - note, these have not been linked as yet to the flow 2013-07-16 13:01:36 +01:00
fossfreedom da09955aeb add check and use path instead of pixbuf when creating Cover on drag-drop - issue #195 2013-07-14 20:03:26 +01:00
fossfreedom 1fce749c3e reinstate download all covers via properties button 2013-07-13 00:02:28 +01:00
fossfreedom 1bb5e94b1e add various flow addons 2013-07-12 11:32:09 +01:00
fossfreedom f19a5d3e75 relinked the toggle with the view on startup 2013-07-11 23:47:24 +01:00
fossfreedom e479bb2983 fix picture display on top toolbar 2013-07-11 23:05:19 +01:00
fossfreedom 2d61b6ebab fix sidebar display of new buttons 2013-07-11 21:58:32 +01:00
fossfreedom 339b25633a rework toggle buttons to be image buttons and add a dummy properties button as well as a dummy artist view button 2013-07-11 20:45:16 +01:00
fossfreedom a651ac93d4 fix right click on coverflow as well as selection issues when switching views 2013-07-10 23:13:54 +01:00
fossfreedom 44a281ff99 minor mod to use scalefactor rather than maxheight for the sizing of images 2013-07-10 22:45:39 +01:00
fossfreedom 64b900f21a minor mod to use scalefactor rather than maxheight for the sizing of images 2013-07-10 22:44:04 +01:00
fossfreedom dd84f9183c various fixes to ensure that the coverflow is loaded after all covers have been loaded as well as resolving various scroll to album issues 2013-07-09 23:24:22 +01:00
fossfreedom acb86fea37 Update coverart_browser.plugin 2013-07-08 08:19:59 +01:00
fossfreedom dcc89ed8a2 now works for RB3.00 - adjusted comment - issue #186 2013-07-08 00:05:42 +01:00
fossfreedom 100a552d56 fix some python3 issues 2013-07-07 23:59:22 +01:00
fossfreedom f96bc30716 tidyup playlist monitoring code 2013-07-07 22:51:32 +01:00
fossfreedom 727165a9d5 assume the shell display-page-model changes can be used to monitor playlist events as well - issue #192 2013-07-07 22:24:15 +01:00
fossfreedom b6bb7eaeb6 fix playlist crash in RB2.99 - issue #191 2013-07-07 21:56:56 +01:00
fossfreedom 6002454340 tie two views to clickable toolbar radio-buttons 2013-07-07 15:14:07 +01:00
fossfreedom 023ebcf1d3 must remember to at least run RB to check for crashes... 2013-07-07 08:58:11 +01:00
fossfreedom 4a3add381b fix for fedora19 crash #191 2013-07-07 08:51:23 +01:00
fossfreedom 206f574cdf delegate switching of views to themselves and better sync between views 2013-07-06 07:41:43 +01:00
fossfreedom 9789b697f0 sync selected albums when flipping between views 2013-07-05 21:48:10 +01:00
fossfreedom 0116413aa2 connect views to viewmanager via a schema key 2013-07-04 21:59:31 +01:00
fossfreedom 2b03440adc ensure new batch is passed the album identifier 2013-07-04 13:24:18 +01:00
fossfreedom d22355d29d refactor iconview specific methods from abstractview 2013-07-04 12:38:59 +01:00
fossfreedom 1ccde3b5d4 fix constant dragging issue after right-click menu is removed from the display 2013-07-03 23:09:21 +01:00
fossfreedom a10dc87ef0 add rudimentary right-click support 2013-07-03 22:26:46 +01:00
fossfreedom 828787dc8d added rudimentary response to click and double click events 2013-07-02 23:49:57 +01:00
fossfreedom bc7052f671 link coverflow with albummodel 2013-07-02 21:36:46 +01:00
fossfreedom afc4a2939c bug fix - playlists were never updated on the playlist menu 2013-07-02 21:35:59 +01:00
fossfreedom 5bad5fa5e2 various RB3 & python3 issues 2013-07-01 21:07:51 +01:00
fossfreedom 452de7f834 integrate coverflow folder structure using initial prototype code 2013-06-30 20:32:18 +01:00
fossfreedom 205fa02435 update supported external plugins 2013-06-30 12:08:48 +01:00
fossfreedom 791787559b restructure so that we have a definitive list of methods a view should have and create a ViewManager class which will eventually manage our different views 2013-06-29 23:35:24 +01:00
fossfreedom 70575fb308 add space between : to cope with french locale requirements - issue #185 2013-06-25 21:38:18 +01:00
fossfreedom 7360482c9d add space between : to cope with french locale requirements - issue #185 2013-06-25 10:41:00 +01:00
fossfreedom d05231de82 latest az translation 2013-06-22 16:04:18 +01:00
fossfreedom 8b38ae236e move cover view specific stuff from covert_album to the cover icon view module 2013-06-22 00:09:09 +01:00
fossfreedom 0ed9d54e22 regeneration of plugin and popups 2013-06-21 20:36:46 +01:00
fossfreedom e7d7efaf61 readd copyright statement 2013-06-21 20:36:14 +01:00
fossfreedom 4cac3ed47d latest translations in release-0.8 2013-06-21 20:32:50 +01:00
fossfreedom 777e78fbc4 more translations for zh_CN 2013-06-21 19:35:02 +01:00
fossfreedom e5e66a1f24 fix change selection 2013-06-21 19:33:30 +01:00
fossfreedom 6aaee47cb6 latest zh_CN translation 2013-06-21 15:57:06 +01:00
fossfreedom db028fa7c8 latest zh_CN translation 2013-06-21 09:23:13 +01:00
fossfreedom 2f50a382ea move icon notify gsetting signals to new module 2013-06-20 23:58:52 +01:00
fossfreedom 60c6cba7cf move signals for iconview to new module 2013-06-20 23:41:22 +01:00
fossfreedom 424f18799d drag drop refactoring 2013-06-20 23:25:04 +01:00
fossfreedom b12423ca63 new translations - bs, el and fi 2013-06-20 22:38:36 +01:00
fossfreedom f6c23576a0 refactored covericonview specifics into its own source module 2013-06-20 22:02:55 +01:00
fossfreedom 5c1736cb04 Update README.md 2013-06-19 21:04:52 +02:00
fossfreedom ebe6678e43 latest pl translation 2013-06-19 09:29:47 +01:00
fossfreedom 6ab0bcecd3 cherry pick 97e1f79ad5 2013-06-18 23:53:48 +01:00
fossfreedom b0b6a09100 Merge branch 'release-0.8' of https://github.com/fossfreedom/coverart-browser into release-0.8 2013-06-18 23:42:53 +01:00
fossfreedom 97e1f79ad5 fix for crash if using BPM column - issue #188 2013-06-18 23:42:19 +01:00
fossfreedom 302048e93b Update README.md 2013-06-18 22:16:05 +02:00
fossfreedom 157cc0359b added new romanian translation 2013-06-18 10:00:37 +01:00
fossfreedom 85b76af44a Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-06-17 22:43:45 +01:00
fossfreedom 6f29b47576 python3 fixes for git compiled version of RB running on saucy 2013-06-17 22:43:25 +01:00
fossfreedom 45aa5281e2 remove obsolete param in constructor call 2013-06-17 11:52:28 +01:00
fossfreedom 6f4ff35547 revert python3 requirement 2013-06-17 11:51:45 +01:00
fossfreedom b578d8f386 fixed some errors that prevented plugin initialisation under python3 2013-06-15 16:55:27 +01:00
fossfreedom 8d40dad422 Update README.md 2013-06-13 12:37:03 +02:00
fossfreedom c7d6dc3481 resync from release-0.8 branch 2013-06-13 11:26:37 +01:00
fossfreedom 953a159018 latest pl translation 2013-06-13 11:19:17 +01:00
fossfreedom 68aacc4f72 latest az translation 2013-06-10 15:35:24 +01:00
fossfreedom 1dad8ae6d3 sync with lastfmqueue changes 2013-06-08 11:32:32 +01:00
fossfreedom 6860b4d4a3 latest polish translation 2013-06-06 20:05:08 +01:00
fossfreedom 28e6ad9a04 pull in changes whilst working on lastfm_queue 2013-06-05 18:00:25 +01:00
fossfreedom c2260d6806 fix display of accelerators in rb299 2013-05-31 16:30:25 +01:00
fossfreedom 6ffdc361b3 add support for RB2.99 view app menu as well as the default tools app menu 2013-05-31 00:01:06 +01:00
fossfreedom 4e8a081cad added API to associate accelerators with actions 2013-05-30 17:32:55 +01:00
fossfreedom 193bd37572 Update README.md 2013-05-28 21:05:23 +02:00
fossfreedom e666803e2d Update README.md 2013-05-28 21:04:36 +02:00
fossfreedom d6128831c4 remove spurious .orig files 2013-05-28 19:50:03 +01:00
fossfreedom 3277635a97 merge 2013-05-28 19:47:20 +01:00
fossfreedom cd1a6d1f91 add header to compat module 2013-05-28 19:20:34 +01:00
fossfreedom ce452dbba5 add app action toggle 2013-05-26 08:51:15 +01:00
fossfreedom cf0b264062 fix rb299 action_name vs label override issue 2013-05-25 21:32:15 +01:00
fossfreedom e95e4a0af7 fix tabs vs spaces and oops with send-first action name 2013-05-25 20:17:31 +01:00
fossfreedom c851f3d913 fix some cleanup issues 2013-05-25 16:48:36 +01:00
fossfreedom 83168a14bd bug fix to display browser menu items enabled in rb299 2013-05-25 16:07:40 +01:00
fossfreedom b792a42371 finalised RB299 functionality and completed code definitions - issue #171 2013-05-25 11:15:44 +01:00
fossfreedom dc0b9a113b fix menu crash in rb299 2013-05-25 00:04:40 +01:00
fossfreedom 77a3867152 bug fixes for rb296 after rework of Action object 2013-05-24 22:38:04 +01:00
fossfreedom 6fb90792af add new ast translation 2013-05-22 10:10:20 +01:00
Agustin Carrasco dabb80dc65 Merge branch 'order-keys'
Added support for multiple ordering on some order types (to allow a
better subordering inside each type).

closes #138
Conflicts:
	coverart_album.py
	coverart_browser_source.py
	coverart_utils.py
2013-05-22 00:46:25 -03:00
Agustin Carrasco 8855b85653 Merge branch 'refactor-statusbar' into order-keys 2013-05-22 00:20:26 -03:00
fossfreedom ccff276ab0 fixed app menu support for rb299 2013-05-21 23:50:06 +01:00
fossfreedom 8f42a9ea71 add label support for rb296 actions 2013-05-21 20:57:17 +01:00
fossfreedom c094a8f445 made menu more generic by removing the dependency on the source for the Menu creation 2013-05-20 23:56:30 +01:00
fossfreedom ae44b76a41 bug fixes for rb299 support 2013-05-20 23:02:16 +01:00
fossfreedom 263df5c62c finish restructuring for rb296 2013-05-19 23:39:56 +01:00
fossfreedom c7f8fe0ded fixed activation events for rb296 2013-05-19 21:03:19 +01:00
fossfreedom 3bd947bf16 preliminary rework to external plugins to support rb299 2013-05-18 23:51:34 +01:00
Agustin Carrasco 65de81ebc0 statusbar: removed extra space on status text 2013-05-16 22:52:35 -03:00
Agustin Carrasco a4c8cc4258 statusbar: finished refactoring statusbar (issue #123) 2013-05-16 22:39:48 -03:00
fossfreedom bc59cd85a2 rework external plugin support to use XML datafile instead of python classes 2013-05-15 19:34:38 +01:00
fossfreedom 08dc4ee2e1 config file used to describe external plugins 2013-05-14 20:08:11 +01:00
fossfreedom c098d0a5ac latest changes made by coverart-search-providers 2013-05-14 20:07:28 +01:00
fossfreedom 419b95d49c Update README.md 2013-05-13 00:52:46 +02:00
fossfreedom 8f3c66b726 Update README.md 2013-05-13 00:51:40 +02:00
Agustin Carrasco 35b2bda943 statusbar: start isolating statusbar behavior in a couple of classes 2013-05-11 23:46:57 -03:00
fossfreedom 7c584aa1e3 working playlists - issue #171 2013-05-10 23:15:43 +01:00
fossfreedom d1583f78f5 latest translations from launchpad 2013-05-10 13:57:01 +01:00
fossfreedom fd4f1a86fd add structure around playlist support - need to debug playlist creation and test against older versions of rhythmbox to ensure restructuring hasnt broken extant capabilities - issue #171 2013-05-08 23:26:38 +01:00
fossfreedom 8d58c448e8 converted entry-view menu - issue #171 2013-05-06 09:07:59 +01:00
fossfreedom 83f0194f2f first cut of rb2.99 support - lots of stuff still not working such as playlists, external plugins and track-view still to be converted - issue #171 2013-05-05 17:12:32 +01:00
fossfreedom 6347abcd2f fixed typos 2013-04-28 22:58:40 +01:00
fossfreedom 09c73972a5 fix missing merge 2013-04-28 11:52:41 +01:00
fossfreedom e9f16ab6b3 merge from master 2013-04-28 11:43:47 +01:00
fossfreedom 0a012d97bc convert pure list-map items to loops to prevent wasteful list generation - issue #179 2013-04-27 23:29:54 +01:00
Agustin Carrasco d674618904 albums: fix dumb name collision on sort_keys 2013-04-27 17:00:37 -03:00
Agustin Carrasco 9349e965c3 albums: added internal support for multiple propery sorting (issue #138)
Had to do some little changes to make this work on the AlbumModel:
- The album_sort and album_sort_artist were returning a redundant list
  that made ordering kinda quirky. Used the uniqyfy function to make
  them return a more sane result.
- Added a sort_keys dictionary to translate the different sort keys into
  a group of properties to sort by. This may have to change in the
  future to make it more mantianable.
- Changed the sort method to use the sort_keys when creating the sort
  fuction for the sorted collection.
2013-04-27 16:47:22 -03:00
Agustin Carrasco 0a6dc5620b utils: added a uniqyfy_and_sort function that removes duplicates and sorts an iterable 2013-04-27 16:40:01 -03:00
fossfreedom f78c77c98d Update README.md 2013-04-25 15:36:52 +02:00
fossfreedom 2c025d32f8 Update README.md 2013-04-25 15:36:27 +02:00
fossfreedom aa6144d575 Update README.md 2013-04-25 15:34:39 +02:00
fossfreedom 37f1cfd632 adjust help hyperlink to release-0.8 2013-04-24 23:45:12 +01:00
fossfreedom c38bdf0608 necessary changes to allow plugin to work in global install location 2013-04-24 23:07:27 +01:00
fossfreedom 2550d46f15 necessary changes to allow plugin to work in global install location 2013-04-24 23:06:05 +01:00
fossfreedom c868c1a44e rework specific python2 to 3 function issues - issue #179 2013-04-24 20:55:46 +01:00
fossfreedom 9aa78d4c6a latest polish translation 2013-04-24 12:51:20 +01:00
fossfreedom 059f8a937b Update README.md 2013-04-23 14:00:51 +02:00
fossfreedom 204ff93a59 Merge branch 'release-0.8' of https://github.com/fossfreedom/coverart-browser into release-0.8 2013-04-22 20:17:02 +01:00
fossfreedom f6d83086c4 remove less than 50 percent translated translations 2013-04-22 20:16:21 +01:00
fossfreedom a100ed29da latest polish translation 2013-04-22 20:06:24 +01:00
fossfreedom 555d6490b5 Update README.md 2013-04-22 20:48:30 +02:00
Agustin Carrasco ae6042e228 albums: Added a check to save_to_disk property before adding entry
Doing this prevents that a "trancient" entry gets added to an Album.
This kind of entries usually come from external devices, which generally
generates duplicates on the album track list. (issue #169)
2013-04-22 19:40:23 +01:00
Agustin Carrasco aa44101ed5 albums: Added a check to save_to_disk property before adding entry
Doing this prevents that a "trancient" entry gets added to an Album.
This kind of entries usually come from external devices, which generally
generates duplicates on the album track list. (issue #169)
2013-04-21 23:29:22 -03:00
fossfreedom c6099bb89d a quick 2to3 run to see what breaks when running under python2.7 - this will give a better idea of how to keep both python 2.7 and python3 compatibility - issue #179 2013-04-21 20:43:03 +01:00
fossfreedom 86b25086ac updated spanish translations 2013-04-21 18:25:59 +01:00
fossfreedom 70e8139b48 update readme for release v0.8 2013-04-21 18:17:08 +01:00
fossfreedom c0a5d2566f updated readme for v0.9 2013-04-21 18:14:04 +01:00
fossfreedom 9f4b104322 Merge branch 'viewadvanceoptions' 2013-04-21 17:45:56 +01:00
fossfreedom ab60fb0eb3 latest translations 2013-04-20 23:09:02 +01:00
fossfreedom 292201f926 change the position and icon of the preferences wiki link button 2013-04-20 23:02:02 +01:00
fossfreedom 522cdb25f8 add advance options for iconview spacing and padding 2013-04-20 21:44:55 +01:00
fossfreedom 362abfbedc regenerate translation pot file 2013-04-20 11:53:38 +01:00
fossfreedom 2be1cf05b1 remove unneeded markup tooltips 2013-04-20 11:50:33 +01:00
fossfreedom 82777c340c add link to wiki 2013-04-19 12:51:56 +01:00
fossfreedom 207c3ee97d latest pt_BR translation 2013-04-19 12:51:32 +01:00
fossfreedom 6a5ef715e5 Update README.md 2013-04-19 00:54:30 +02:00
fossfreedom 8be7155fd0 changed launchpad credit to be visible only on request via info button - issue #168 2013-04-18 23:37:30 +01:00
fossfreedom a395c00df8 display translators on preferences dialog - issue #168 2013-04-18 21:50:47 +01:00
fossfreedom e0ef6f040d add translator-credits 2013-04-18 20:16:44 +01:00
fossfreedom 3d41829949 Merge branch 'master' of http://github.com/fossfreedom/coverart-browser 2013-04-18 08:14:02 +01:00
fossfreedom 9c142c6036 latest es.po translation 2013-04-18 08:13:42 +01:00
fossfreedom 6cb11029b1 Update README.md 2013-04-17 23:56:08 +02:00
fossfreedom 449d0375de Update README.md 2013-04-17 23:55:58 +02:00
fossfreedom e880bba103 corrected help link - this will be version defined 2013-04-17 22:21:05 +01:00
Agustin Carrasco c4504c274d Damn, I keep getting it wrong 2013-04-17 08:21:02 -03:00
Agustin Carrasco 0c70ecdb6b Oops, wrong link 2013-04-17 08:18:45 -03:00
Agustin Carrasco edef1f564e Updated url for asermax fattr button 2013-04-17 08:12:20 -03:00
fossfreedom 33820cfeb9 make sure preferences use translation locale before building - issue #176 2013-04-17 10:45:23 +01:00
fossfreedom 50b7c1f936 Update README.md 2013-04-16 20:45:49 +02:00
fossfreedom 8c756fa01a add support for WikipediaSearch plugin 2013-04-16 16:56:43 +01:00
fossfreedom acb5ff7061 prevent crash if search providers plugin is not installed - issue #159 2013-04-16 15:30:13 +01:00
fossfreedom 6af7e36af8 latest launchpad translations 2013-04-16 09:40:39 +01:00
fossfreedom bf56c47c3e latest translations 2013-04-15 12:33:13 +01:00
fossfreedom f3f389006a latest translations from launchpad 2013-04-14 13:43:15 +01:00
fossfreedom 8f18571626 regenerate after asermax ui translate change 2013-04-14 11:13:45 +01:00
Agustin Carrasco ae8f3a5da3 bugfix ui: added translatable marter to the collapsible pane label
Issue #123
2013-04-13 20:17:55 -03:00
fossfreedom 8fcf0f6712 update README for new coverart export and embed functionality 2013-04-13 23:49:02 +01:00
fossfreedom 9ce6e8c7d9 regenerate po for new coverart export and embed strings 2013-04-13 23:46:55 +01:00
fossfreedom aa4904ded7 minor typo 2013-04-13 23:45:51 +01:00
fossfreedom da31e95cab Merge branch 'master' of http://github.com/fossfreedom/coverart-browser 2013-04-13 23:45:02 +01:00
fossfreedom 198426e55e implementation of coverart export and embed functionality - issue #159 2013-04-13 23:44:40 +01:00
fossfreedom ef2ff02b28 add help button on preferences 2013-04-09 09:41:01 +01:00
Agustin Carrasco 3744855057 albums: fixed bug introduced when changed to using _sort properties for
sorting: whenever an album would need to get deleted (because it was
empty), it would fail since the key using for sorting and finding an
element inside the sorted structure was given by the _sort key. The
_sort key was naively decided to be obtained from the first track of the
album, and since the album is empty, it would raise a IndexError.
2013-04-07 16:51:29 -03:00
fossfreedom f296c644a6 drag and drop coverart onto playlists and sources 2013-04-06 12:18:36 +02:00
fossfreedom cd665e2bb9 change the drag icon 2013-04-06 00:06:44 +01:00
fossfreedom 35c02421a5 seems playlists are working now 2013-04-05 23:18:55 +01:00
fossfreedom dcc3dc3187 basic support for drag and drop from coverview - issue #169 2013-04-05 23:02:25 +01:00
fossfreedom 75b9a540fe minor mods 2013-04-03 21:00:34 +01:00
fossfreedom 1d639d6730 remove automatic enablement of plugin 2013-04-03 21:00:17 +01:00
fossfreedom ca1fbcd899 add lLyrics to supported external plugins 2013-04-03 20:59:32 +01:00
fossfreedom 25940e3485 minor mods 2013-04-03 20:12:29 +01:00
Agustin Carrasco 2438cbdd08 widgets: fix bug on EnhancedIconview where the selected objects were returned in reversed order 2013-03-31 23:31:33 -03:00
Agustin Carrasco bab9311225 On play album: use the queue_selected_album instead of copying the entryview query (issue #130) 2013-03-31 23:31:09 -03:00
Agustin Carrasco 76215de1b2 Changed the method to play entries/albums to replace the source's query model
instead of queuing the entries into the play queue (issue #130)
2013-03-31 23:16:12 -03:00
fossfreedom 3c6a3e8c4d queue remaining tracks in entry view on playing - issue #130 2013-03-31 23:53:54 +01:00
fossfreedom eddfe978a3 Merge branch 'master' of http://github.com/fossfreedom/coverart-browser 2013-03-31 11:53:49 +01:00
fossfreedom a38fc28763 latest french translations 2013-03-31 11:53:32 +01:00
fossfreedom 9bf3f333ea Update README.md 2013-03-30 17:06:25 +00:00
fossfreedom 6d2d45b518 add file-organizer support - this is pending a pull request being accepted. It it isnt, this will need to be backed out before release 0.8 2013-03-29 20:20:37 +00:00
fossfreedom 408f78d634 latest fr_CA translation 2013-03-29 16:57:01 +00:00
fossfreedom ced468ac65 fix external module name for opencontainingfolder - issue #147 2013-03-29 11:25:47 +00:00
fossfreedom de9bdf67e3 Merge branch 'master' of http://github.com/fossfreedom/coverart-browser 2013-03-28 23:40:46 +00:00
fossfreedom 8859688606 initial support for external plugins - issue #147 2013-03-28 23:40:37 +00:00
Agustin Carrasco ccd8f10786 widgets: added some documentation for the EnhancedIconView widget. 2013-03-28 19:53:43 -03:00
Agustin Carrasco fe159d07b7 Merge branch 'collapsible-paned'
Refactored code that managed the paned behavior of the source main view on it's
own widget, which also takes care of the expandable on one of it's children.
This new widget abstracts the storing of the paned handle position, handles the
expandable child and handles the workaround to keep the handle in a fixed place
(when the expandable child is collapsed).
2013-03-28 19:10:21 -03:00
Agustin Carrasco 4057318255 widgets: added some more comments to the CollapsiblePaned widget 2013-03-28 19:10:08 -03:00
fossfreedom 3f47a48f71 fix tooltip - issue #168 2013-03-28 10:16:55 +00:00
fossfreedom 2d24ca1d38 latest translations 2013-03-27 12:37:36 +00:00
fossfreedom 039330dbae embed GtkArrows - issue #167 2013-03-26 09:54:40 +00:00
fossfreedom 0590acc605 embed GtkArrows - issue #167 2013-03-26 09:53:27 +00:00
fossfreedom 2a881319e3 Update README.md 2013-03-25 23:52:34 +00:00
fossfreedom fabe82c919 add comments to methods 2013-03-25 23:45:28 +00:00
fossfreedom 0a32a19aec separated providers plugin from coverartbrowser 2013-03-25 19:51:41 +00:00
fossfreedom 62ac5f5a15 latest translation bits 2013-03-25 16:44:24 +00:00
fossfreedom 9889e2ecb8 slight modifications to label positions - issue #151 2013-03-25 15:56:40 +00:00
fossfreedom d3e9961a56 revert excessive cleanup - issue #151 2013-03-25 11:03:57 +00:00
fossfreedom c8624ea86f move load icons and fix system genre lookup match - issue #151 2013-03-24 23:47:55 +00:00
fossfreedom 6e2bd1f650 tidy up genre gui - issue #151 2013-03-24 17:33:01 +00:00
fossfreedom dbdadac043 makes sense for delete button to use the remove icon to complement the add icon 2013-03-22 13:33:21 +00:00
fossfreedom cd9d36f688 add labels, change save icon and move load icons button - issue #151 2013-03-22 13:24:29 +00:00
fossfreedom 7993695a11 cleanup files 2013-03-21 23:30:58 +00:00
fossfreedom ac861a82b5 Merge branch 'master' into customgenre 2013-03-21 23:27:02 +00:00
fossfreedom 69c5979fb6 new genre gui and connect GUI changes with plugin controller 2013-03-21 23:24:39 +00:00
Agustin Carrasco b1d598b762 Added some comments and rewrote a function to reuse a portion of code instead of repeating it 2013-03-20 13:07:59 -03:00
fossfreedom 0db85fe051 background code and gui for alternative genre icons 2013-03-09 07:33:57 +00:00
Agustin Carrasco 6dbce1846e Finished porting some stuff to the CollapsiblePaned widget and removed now obsolete code from coverart_browser_source 2013-03-08 12:29:00 -03:00
Agustin Carrasco f336b2f64e widget: updated the PanedCollapsible and replaced the GtkPaned on the interface. Now it supports the basic expand/collapse functionality and blocking the handle when collapsed 2013-03-05 22:20:07 -03:00
fossfreedom d6028ca00c Update README.md 2013-03-04 12:17:05 +00:00
fossfreedom db8123d960 Merge branch 'master' into artistorder 2013-03-04 12:11:48 +00:00
Agustin Carrasco 6845ebc3b9 CoverartRequester: remove debugging prints 2013-03-02 21:51:39 -03:00
Agustin Carrasco 71cc9976de CoverRequester: modified the locking mechanism, to avoid the call to next from the same album search or timeout to be processed twice 2013-03-02 21:51:39 -03:00
Agustin Carrasco 97634dddd5 This kind of problems make my heard hurt~
This reverts commit 403d1f51bb.
2013-03-02 21:49:48 -03:00
fossfreedom 7ead18fc5d use sort fields in preference to actual fields - issue #161 2013-03-02 15:08:47 +00:00
fossfreedom 3fbc91fb57 Update README.md 2013-02-25 19:26:21 +00:00
fossfreedom 8c8cfcdc6d bug fix - ensure queuing is sorted first by disc number and then by track number - issue #160 2013-02-25 16:29:41 +00:00
fossfreedom dc9d0332c3 lets ensure both plugins are installed 2013-02-24 16:44:01 +00:00
fossfreedom a63c513423 remove duplication of code with helper functions - issue #149 2013-02-23 23:30:51 +00:00
fossfreedom 334976832b move theme logic to toolbar from toolbar manager - issue #149 2013-02-23 17:49:36 +00:00
fossfreedom ff5ccf49f7 add new signal key for buttons to update image - issue #149 2013-02-23 17:39:47 +00:00
fossfreedom 5827b32e54 move theme control to the Toolbar manager rather than on the controller - issue #149 2013-02-23 17:02:38 +00:00
fossfreedom 677fb805a9 update button theme when user changes - issue #149 2013-02-23 07:48:21 +00:00
fossfreedom 7de5cb2940 few tidyups from icon merge 2013-02-22 09:40:17 +00:00
fossfreedom 94e639ef6c Merge pull request #162 from jrbastien/master
Enhancements to standar icons (issue #149)
2013-02-22 00:00:18 -08:00
jrbastien b7a6347a5f Issue #149. New light and dark team + minor tweak to the sprites genre on standard theme. 2013-02-21 21:23:30 -05:00
fossfreedom 403d1f51bb reset 2013-02-19 16:25:33 +00:00
fossfreedom 28aae8aad1 Merge branch 'cover_search_queue' 2013-02-19 16:18:58 +00:00
fossfreedom f6dc74e7d2 reworked from dialog to window and narrowed buttons with image arrows - issue #126 2013-02-18 21:16:56 +00:00
Agustin Carrasco 4c6b3b12ab PanedCollapsible: set options to the current expander 2013-02-18 13:17:54 -03:00
Agustin Carrasco a1eeaaa037 PanedCollapsible: fixed typo and save expander as object variable 2013-02-18 13:11:22 -03:00
Agustin Carrasco 3ce9a8dc85 OptionsListViewWidget: cleaned code formatting 2013-02-18 13:05:11 -03:00
Agustin Carrasco c9fd8cf418 CoverRequester: the queue was working like a stack actually... 2013-02-18 12:59:25 -03:00
fossfreedom e498d3dd92 add button click pageup/down and focus stuff -- issue #126 2013-02-18 15:58:45 +00:00
fossfreedom 3e19f0555a autoscroll - issue #126 2013-02-17 23:39:35 +00:00
fossfreedom 8d56ad73a8 change from selection to button_release to allow hover selection - issue #126 2013-02-17 22:43:52 +00:00
Agustin Carrasco 01e3762c86 ProxyPopupButton: increase the min fixed limit of options to 25 2013-02-17 16:06:57 -03:00
Agustin Carrasco c99eff7af7 CoverManager: reimplemented the requesting mechanism into a different class that makes use of a queue instead of independent process. This gives the requesting process more flexibility 2013-02-17 03:27:08 -03:00
Agustín Carrasco e5e3d935ae Merge pull request #158 from jrbastien/master
Enhancements to standard icons
2013-02-16 19:36:51 -08:00
Jean-Rene Bastien 2853dc507d Update img/popups.xml
Enhancements to standard icons.  Issue #149
2013-02-16 21:26:17 -05:00
jrbastien 02231061d6 Improvements to calendar icons and default genre icon look. 2013-02-16 21:18:18 -05:00
fossfreedom 594a399b0c add theme support - issue #149 2013-02-13 22:13:01 +00:00
Agustin Carrasco 6ca4a5d3a2 PanedCollapsible: initial implementation of the Paned container with a collapsible child. 2013-02-11 20:07:47 -03:00
Agustin Carrasco 41d3cd8cfc CoverManager: fix bug on the timeout routine for the cover search 2013-02-10 04:51:55 -03:00
Agustin Carrasco 650c2320dc CoverManager: fix bug on the timeout routine for the cover search 2013-02-10 04:23:03 -03:00
Agustin Carrasco f2f4e75670 CoverManager: removed the timer module and replaced the timeout managment from the Source with an internal timemout on the search cover routine 2013-02-10 03:22:55 -03:00
Agustin Carrasco 42ddff79e1 Merge branch 'enhanced-iconview'
Introduced the EnhancedIconview widget, which enhances the iconview capability in some senses:
* Adds a signal for a single click on a icon.
* Fixes column reallocation on width change.
* Adds a popup to the widget, that gets fired when right clicking on an icon.
* Adds a new property to indicate the column of the model that contains the actual object to which the icon and other info belongs to. Through this property, a new method get_selected_objects allows to retrieve those objects corresponding to the current selection on the iconview.
* Adds a method to select and scroll to a given path on the iconview.
2013-02-09 22:18:26 -03:00
Agustin Carrasco 30a8c8322e EnhancedIconview: refactored the code to select and scroll to a path on the iconview.
* Moved the code to select and scroll to the a path from the source to the iconview.
* Refactored the AlbumQuickSearchController to use this new capability directly insted of through the source.
2013-02-09 22:16:41 -03:00
Agustin Carrasco 01c2f57f3b CoverartSource: replaced the get_selected_albums method for the new get_selected_objects method of the EnhancedIconview class 2013-02-07 23:55:32 -03:00
Agustin Carrasco 8942dac647 EnhancedIconview: fixed the get_selected_objects methods, it wasn't managing the model correctly 2013-02-07 23:54:50 -03:00
Agustin Carrasco 7888186e0c CoverartSource: fixed a issue with the second click bottom pane expand. It wasn't working correctly after a shift or ctrl multi selection 2013-02-07 23:52:34 -03:00
Agustin Carrasco 161eb5714b Removed a signal that isn't used anymore 2013-02-07 23:40:53 -03:00
Agustin Carrasco 20388f54a7 Gonna start moving up those methods that got refactored to make it easier to identify those that still need to be looked at 2013-02-07 23:29:28 -03:00
Agustin Carrasco b5d9a53d44 Removed old, unused method 2013-02-07 22:27:00 -03:00
Agustin Carrasco b00e3e6949 CoverartSource: refactored the click callback using the new capabilities of the EnhancedIconview.
Selecting the path on right click and showing the popup is taken care on the new iconview, the only left to do in the source is the second click detection to show/hide the bottom pane.
2013-02-07 22:20:43 -03:00
Agustin Carrasco 61b7c6386e EnhancedIconview: made some fixes on the do_button_press_event method.
* The item-clicked event now send the event two
* The call to the parent class do_button_press_event is made after our custom behavior
* Fixed typo (variable brought from the old code).
2013-02-07 22:18:55 -03:00
fossfreedom e1a68c4acb Merge branch 'master' of https://github.com/fossfreedom/coverart-browser
Conflicts:
	coverart_widgets.py
2013-02-06 22:55:53 +00:00
fossfreedom 4f0251902a open popup at cursor position - issue #144 2013-02-06 22:47:30 +00:00
Agustin Carrasco f31dc04af1 Added a timeout to the columns reallocation to avoid innecesary reallocations while the user is adjusting the pane size 2013-02-06 00:04:40 -03:00
Agustin Carrasco dd58b8a5e3 Removed the update_iconview_callback method from the source since it's not needed anymore 2013-02-05 23:57:58 -03:00
Agustin Carrasco d172061280 Use the available variables instead of saving redundant information 2013-02-05 23:57:03 -03:00
Agustin Carrasco 01696eaa2d Had to call to the superclass' do_button_press_event too, otherwise the selection wouldn't work 2013-02-05 23:47:12 -03:00
Agustin Carrasco 82859ceb2f Added the object_column property to the EnhancedIconView on the source ui 2013-02-05 23:36:28 -03:00
Agustin Carrasco 79bf0a5f79 Replaced the IconView for the EnhancedIconView as the cover_view on the source ui 2013-02-05 23:21:53 -03:00
Agustin Carrasco d382e30c2f Did some fixes on the EnhancedIconView
* The do_size_allocate wasn't calling the superclass behavior, and the allocation wasn't being correctly made.
* Added a signal to inform when a single click is made on a icon.
2013-02-05 23:20:14 -03:00
Agustin Carrasco c24fd22f45 Moved the EnhancedIconView class to the end of file to keep it organized 2013-02-05 22:22:40 -03:00
Agustin Carrasco 6f5783f8ba Organized the Toolbar tab; added some margins and stuff to make it look pretty (blame Glade for the other code that got moved around) 2013-02-05 21:32:51 -03:00
fossfreedom 86e7c4a99f Merge branch 'master' of https://github.com/fossfreedom/coverart-browser 2013-02-05 20:02:57 +00:00
fossfreedom 2a3d455838 add button style for toolbar - issue #149 2013-02-05 20:02:11 +00:00
fossfreedom 96e425aee9 Update README.md 2013-02-04 23:03:49 +00:00
fossfreedom 47ff73ba09 Update README.md 2013-02-04 23:02:38 +00:00
fossfreedom 1097077104 add dimensions to sprites - issue #149 2013-02-04 14:45:13 +00:00
fossfreedom 18ba48f572 Merge branch 'source-refactoring' 2013-02-04 14:23:14 +00:00
Agustin Carrasco 448663cfef Removed some unused code too. That call wasn't really doing anything. 2013-02-03 20:58:30 -03:00
fossfreedom b52b7ec8dd remove more unneeded code 2013-02-03 23:45:45 +00:00
fossfreedom 83e587405f merge listviewwindow with optionslistviewwidget 2013-02-03 23:41:03 +00:00
fossfreedom 401ae1bd60 Update README.md 2013-02-03 22:57:26 +00:00
fossfreedom cdd681a8e5 Merge branch 'source-refactoring' 2013-02-03 22:51:14 +00:00
Agustin Carrasco 3079af48bc Fixed the issue when the genre ListWindow would get destroyed after pressing [ESC] (issue #123). 2013-02-03 18:45:48 -03:00
Agustin Carrasco 67f22db97a Modified the ListWindow and the OptionsListViewWidget to recreate the liststore only when necesary (issue #123).
* Removed the activate method from the ListWindow and moved the relevant code to other places to help with the flow of the OptionsWidget.
* Correctly implemented the update_options and update_current_key callbacks to do what they should (recreate the list and select the currently selected item respectively)
* TODO: maybe the ListWindow should be merged with OptionListViewWidget? Right now they seem like two layers of the same thing, maybe it could be simplified by merging.
* TODO: the "mouse" flag for the window position seems to put the center of the windo on the mouse, instead of the corner (like the poups). Should look into that.
2013-02-03 17:15:46 -03:00
Agustin Carrasco b0a87d9d53 Adapted some syntax to PEP8 2013-02-03 17:15:46 -03:00
fossfreedom 16be637fd0 latest translations - also translates separate search providers plugin 2013-02-03 18:15:40 +00:00
Agustin Carrasco 784de34081 Fixed some stuff on the "Cover" tab of the bottom pane:
* Fixed the hiding of the show more results links. Now it hides when it haves to.
* Floated the Google branding div to the right. This way the show more link is more visible and easily accesible, while the branding is still there but doesn't obstruct the rest of the interface.
2013-02-03 11:39:42 -03:00
Agustin Carrasco 82545062b2 Optimized search for album with a given ext_db_key to only try to match against an album with the corresponding name. 2013-02-03 11:19:30 -03:00
fossfreedom 9c6d37a285 refact search providers into separate plugin - issue #152 2013-02-03 11:32:20 +00:00
fossfreedom 37da774bd6 rework to use selection changed event - issue #144 2013-02-01 16:47:12 +00:00
fossfreedom a6e3a42fab oops change 12 to 25 for when to display listwindow - issue #144 2013-02-01 15:37:27 +00:00
fossfreedom 028a38570a remove cancel button and close when lose focus - issue #144 2013-02-01 15:35:34 +00:00
fossfreedom 718d9b6b61 Merge branch 'source-refactoring' of https://github.com/fossfreedom/coverart-browser into source-refactoring 2013-02-01 15:18:13 +00:00
fossfreedom 4229614129 refactor listview to be separate widget/button - issue #144 2013-01-31 22:42:21 +00:00
fossfreedom c08be9f2fc add coverart archive to preferences - issue #69 2013-01-25 23:00:55 +00:00
fossfreedom 623894d5a6 add translations for search providers plugin - issue #145 2013-01-25 22:42:37 +00:00
fossfreedom b2485b739a Merge branch 'master' into source-refactoring 2013-01-25 22:32:46 +00:00
fossfreedom ebb93e6098 Merge branch 'master' into source-refactoring 2013-01-24 23:34:57 +00:00
fossfreedom 440ff178b6 Merge branch 'master' into source-refactoring 2013-01-24 12:51:39 +00:00
Agustin Carrasco 0e4d722f2d Initial implementation of an enhanced icon view that manages it's own popup and haves a method to retrieve the selected objects, given that the column number of the object that represent each models row is setted 2013-01-20 18:29:09 -03:00
272 arquivos alterados com 108190 adições e 13243 exclusões
+3
Ver Arquivo
@@ -7,6 +7,7 @@ dist
build
eggs
parts
.idea
bin
var
sdist
@@ -25,3 +26,5 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
img/usericons/popups.xml
+621
Ver Arquivo
@@ -0,0 +1,621 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/}
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
+26 -3
Ver Arquivo
@@ -13,17 +13,40 @@ clean:
install:
install -d $(DESTDIR)$(SUBDIR)
install -m 644 *.py $(DESTDIR)$(SUBDIR)
install -m 644 LICENSE.txt $(DESTDIR)$(SUBDIR)
install -d $(DESTDIR)$(DATADIR)img
install -m 644 img/*.png $(DESTDIR)$(DATADIR)img/
install -m 644 img/*.svg $(DESTDIR)$(DATADIR)img/
install -m 644 img/*.xml $(DESTDIR)$(DATADIR)img/
install -d $(DESTDIR)$(DATADIR)img/darker
install -m 644 img/darker/*.png $(DESTDIR)$(DATADIR)img/darker
install -d $(DESTDIR)$(DATADIR)img/dark
install -m 644 img/dark/*.png $(DESTDIR)$(DATADIR)img/dark
install -d $(DESTDIR)$(DATADIR)img/light
install -m 644 img/light/*.png $(DESTDIR)$(DATADIR)img/light
install -d $(DESTDIR)$(DATADIR)img/lighter
install -m 644 img/lighter/*.png $(DESTDIR)$(DATADIR)img/lighter
install -d $(DESTDIR)$(DATADIR)img/standard
install -m 644 img/standard/*.png $(DESTDIR)$(DATADIR)img/standard
install -d $(DESTDIR)$(DATADIR)img/links
install -m 644 img/links/*.png $(DESTDIR)$(DATADIR)img/links
install -d $(DESTDIR)$(DATADIR)template
install -m 644 template/*.xml $(DESTDIR)$(DATADIR)template
install -d $(DESTDIR)$(DATADIR)ui
install -m 644 ui/*.ui $(DESTDIR)$(DATADIR)ui/
install -m 644 ui/*.xml $(DESTDIR)$(DATADIR)ui/
install -m 644 coverart_browser.plugin $(DESTDIR)$(SUBDIR)
install -m 644 coverart_search_providers.plugin $(DESTDIR)$(SUBDIR)
install -m 644 ui/*.css $(DESTDIR)$(DATADIR)ui/
install -d $(DESTDIR)$(DATADIR)coverflow
install -d $(DESTDIR)$(DATADIR)coverflow/img
install -m 644 coverflow/*.css $(DESTDIR)$(DATADIR)coverflow/
install -m 644 coverflow/*.js $(DESTDIR)$(DATADIR)coverflow/
install -m 644 coverflow/*.html $(DESTDIR)$(DATADIR)coverflow/
install -m 644 coverflow/LICENSE $(DESTDIR)$(DATADIR)coverflow/
install -m 644 coverflow/img/* $(DESTDIR)$(DATADIR)coverflow/img/
install -m 644 coverart_browser.plugin* $(DESTDIR)$(SUBDIR)
install -d $(DESTDIR)$(DATADIR)tmpl
install -m 644 tmpl/* $(DESTDIR)$(DATADIR)tmpl/
install -d $(DESTDIR)$(GLIB_DIR)
install -m 644 schema/$(GLIB_SCHEME) $(DESTDIR)$(GLIB_DIR)
cd po;./lang.sh $(DESTDIR)$(LOCALEDIR)
cd po;./install_all.sh $(DESTDIR)$(LOCALEDIR)
+67 -85
Ver Arquivo
@@ -1,137 +1,119 @@
coverart-browser v0.7
================
#coverart-browser - v2.2 development (Colonel K)
Browse your coverart albums in Rhythmbox v2.96 and later
Browse your coverart albums in Rhythmbox v3 and later.
![Imgur](http://i.imgur.com/yXYmcOt.png)
If you have reached here looking for the stable version of the plugin please read the README files for
- rhythmbox 2.96 - 2.99: https://github.com/fossfreedom/coverart-browser/tree/release-1.2
- rhythmbox 3.0+: https://github.com/fossfreedom/coverart-browser/tree/release-2.1
![Imgur](http://i.imgur.com/tTnHbE1.png)
-----------
**Please help out with translating - skip to the end for details**
##Authors
Summary: whats new in this release
- asermax <asermax@gmail.com>, website - https://github.com/asermax
- find & display embedded covers in MP3, M4A, FLAC & Ogg files
- Optional coverart search from Discogs internet service
- new filter by decade
- configurable shadow effect behind cover display
- Iconised filter & sort buttons that change icon depending upon option chosen
- popup menu selection for filters
- brand new icons designed explicitly to the coverart browser plugin
- one click open and close track view for a cover
- plugin translated into many more languages
- plugin code refactored - much faster to start and display without any flashing effects
- code has been completely documented using doxygen: http://fossfreedom.github.com/coverart-browser/classes.html
- covers displayed using rhythmbox natural sort i.e. ascending numbers
- search filter matches upper & lower-case as well as ignoring characters such as accents
- display covers for play-queue/music library & playlists from within the coverart-view
- column header sort in track view
- revamped plugin preferences
[![Flattr Button](http://api.flattr.com/button/button-compact-static-100x17.png "Flattr This!")](http://flattr.com/thing/1262052/asermax-on-GitHub "asermax")
- fossfreedom <foss.freedom@gmail.com>, website - https://github.com/fossfreedom
[![Flattr Button](http://api.flattr.com/button/button-compact-static-100x17.png "Flattr This!")](http://flattr.com/thing/1811704/ "fossfreedom") [![paypaldonate](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KBV682WJ3BDGL)
-----------
##Summary: whats new in this release
- Support for Alternative-Toolbar & Headerbar - add view switcher to headerbar and remove from source toolbars
- Support for Alternative-Toolbar - Toggle between views and sources in sidepane
- Export and Embed dialog remembers field values between openings
- Quicker startup of plugin
- chosen playlist are persistent between rhythmbox sessions
- Add CoverArt Playlist to the same menu-button as other views
- Remove separate CoverArt Playlist icon on track-view
- Rework CoverArt Playlist so that don't have to start CoverArt to play the last saved playlist
- various icons display correctly for both light and dark themes
- Translated into 25 languages and locales
- for developers - doxygen documentation: http://fossfreedom.github.io/coverart-browser/classes.html
*How it works:*
- Click the new CoverArt source button (left hand side of screen)
- Click the new CoverArt Browser source button (left hand side of screen)
- Albums are displayed as clickable buttons containing their album cover
- Right click menu option to play, queue & search for coverart for an album.
- Download Album & artist artwork via the properties toolbar button
- https://github.com/fossfreedom/coverart-browser/wiki/how-to-for-version-2.0
- https://github.com/fossfreedom/coverart-browser/wiki/Screenshots
- https://github.com/fossfreedom/coverart-browser/wiki/How-the-plugin-works
*How to install - Rhythmbox 3.0 and later:*
*How to install:*
N.B. for earlier Rhythmbox versions use version 1.x
for debian & debian-based distros such as Ubuntu & Mint
Prerequisite is to use a distribution supporting GTK 3.10 or later - for example, Ubuntu 14.04, Arch or Fedora 20
sudo apt-get install git gettext python-mako python-mutagen python-requests python-lxml
for Debian & Debian-based distros such as Ubuntu & Mint:
for fedora and similar:
sudo apt-get install git gettext python3-mako python3-lxml python3-gi-cairo python3-cairo gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good gstreamer1.0-plugins-bad rhythmbox-plugins
yum install git gettext python-mako python-mutagen python-requests python-lxml
for Fedora and similar:
Then install the plugin:
sudo yum install git gettext python3-mako python3-lxml python3-cairo
NOTE: it is assumed that you have separately installed the patent encumbered codecs found in the good/bad & ugly packages
To install the plugin:
<pre>
rm -rf ~/.local/share/rhythmbox/plugins/coverart_browser
git clone https://github.com/fossfreedom/coverart-browser.git
git clone https://github.com/fossfreedom/coverart-browser.git -b master
cd coverart-browser
sh ./install.sh
./install.sh
</pre>
*For Ubuntu 12.04 & 12.10:*
To uninstall the plugin:
This is now available in my rhythmbox PPA - installation instructions in this AskUbuntu Q&A:
<pre>
cd coverart-browser
./install.sh --uninstall
</pre>
Note 1 - the CoverArt Browser plugin also requires installing the following plugin:
- https://github.com/fossfreedom/coverart-search-providers
*For Ubuntu 14.04 and later:*
V2.0 is now available in my rhythmbox PPA - installation instructions in this AskUbuntu Q&A:
http://askubuntu.com/questions/147942/how-do-i-install-third-party-rhythmbox-plugins
**IMPORTANT NOTE**
For Ubuntu 12.04 users that have upgraded to Rhythmbox v2.98 using the webupd8 PPA, this version
of rhythmbox crashes when used with python plugins such as coverart-browser and replaygain.
It is strongly recommended that you either upgrade to 12.10 where v2.98 works great, or
downgrade to v2.96 or v2.97 as per:
- http://askubuntu.com/questions/201093/how-do-i-downgrade-rhythmbox-v2-98
*installation for embedded coverart*
The plugin makes use of the package `python-mutagen`. For most distros, the default package is v1.20 which was released in 2010.
Since then, lots of bug fixes have been resolved. If you know that there is coverart embedded, but is not displayed
in our plugin, then you should install the very latest package:
<pre>
hg clone https://code.google.com/p/mutagen/
</pre>
Then following the instructions in the README (slightly modified)
<pre>
./setup.py build
sudo su
./setup.py install
</pre>
Note - installing the package `rhythmbox-plugin-coverart-browser` will also install `rhythmbox-plugin-coverart-search`
**Please help out with translating**
We need you to help us translate the english text to your native language.
Don't worry - it is easier that you think.
Just visit:
Don't worry - it is easier that you think. Just visit:
- https://translations.launchpad.net/coverartbrowser
Remember to set your preferred language and then just submit your translation.
Instructions are in the file TRANSLATE_README. Post a link to the file as a new issue, or
if you are feeling generous - fork and push a pull-request. Thanks!
If they look scary - just email me (foss dot freedom at gmail dot com) and I'll send you the
file that needs to be translated - it is less than 20 text strings so it should only take a
few minutes.
When emailing - tell me your locale & language. You can find these by typing:
echo $LANG
echo $LANGUAGE
-------
Authors:
The authors of this plugin are fossfreedom <foss.freedom@gmail.com>, Agustín Carrasco <asermax@gmail.com>
-------
Credits:
- thanks to Luqman Aden <laden@uwaterloo.ca> for the coverart-search plugin which our cover-search pane is based upon
- thanks to Canonical for the Star widget which the ratings capabilities use
- our Translators: Launchpad Translation team, jrbastien (fr_CA), asermax (es), mateuswetah (pt_BR), jrbastien & lannic (fr.po)
- Button Icons - jrbastien for the new iconset
- our Translators: Launchpad Translation team - individual credits for each locale is shown in the plugin preferences dialog
- Button Icons - [jrbastien](https://github.com/jrbastien) for the five toolbar icon-sets
- Flow view is based upon [Contentflow](http://jacksasylum.eu/ContentFlow)
Licenses:
This plugin code is released under the GPL3+ license.
Contentflow source is released under the MIT license
All translations are released under the BSD license
+17 -14
Ver Arquivo
@@ -5,19 +5,25 @@
# Section 1: Developers Only
#+++++++++++++++++++++++++++
#for each ui file run the following to create translation .h files
intltool-extract --type=gettext/glade ui/coverart_album_search_prefs.ui
intltool-extract --type=gettext/glade ui/coverart_browser_prefs.ui
intltool-extract --type=gettext/glade ui/coverart_browser.ui
intltool-extract --type=gettext/glade ui/coverart_entryview.ui
intltool-extract --type=gettext/glade ui/coverart_sidebar.ui
intltool-extract --type=gettext/glade ui/coverart_topbar.ui
intltool-extract --type=gettext/glade ui/toolbar_popup.ui
intltool-extract --type=gettext/xml img/popups.xml.in
intltool-extract --local --type=gettext/glade ui/coverart_artistview.ui
intltool-extract --local --type=gettext/glade ui/coverart_artist_pop_rb3.ui
intltool-extract --local --type=gettext/glade ui/coverart_browser_pop_rb3.ui
intltool-extract --local --type=gettext/glade ui/coverart_browser_prefs.ui
intltool-extract --local --type=gettext/glade ui/coverart_browser.ui
intltool-extract --local --type=gettext/glade ui/coverart_entryview_compact_pop_rb3.ui
intltool-extract --local --type=gettext/glade ui/coverart_entryview_full_pop_rb3.ui
intltool-extract --local --type=gettext/glade ui/coverart_exportembed.ui
intltool-extract --local --type=gettext/glade ui/coverart_leftsidebar.ui
intltool-extract --local --type=gettext/glade ui/coverart_listwindow.ui
intltool-extract --local --type=gettext/glade ui/coverart_play_pop_rb3.ui
intltool-extract --local --type=gettext/glade ui/coverart_rightsidebar.ui
intltool-extract --local --type=gettext/glade ui/coverart_topbar.ui
intltool-extract --local --type=gettext/xml img/popups.xml.in
#create a new template file called po/package.pot by running below
#create a template file for the mako templates
pybabel extract -F babel.cfg -o po/coverartbrowser.pot .
pybabel extract -F babel.cfg -o po/coverartbrowser.pot -c "TRANSLATORS:" .
# po/files_to_be_translated are all the .h files generated by intltool
xgettext -c -a -j -f po/files_to_be_translated -o po/coverartbrowser.pot
@@ -25,16 +31,13 @@ xgettext -c -a -j -f po/files_to_be_translated -o po/coverartbrowser.pot
# po/py_files_to_be_translated are all the python files to be translated
xgettext -c -j -f po/py_files_to_be_translated -o po/coverartbrowser.pot
#now combine the pot files
cd po
#now update existing po's with changes in the template file package.pot
cd po
./update_all_po.sh
#cleanup
cd ..
rm img/*.h
rm ui/*.h
rm -rf tmp
# Section 2: Translators
#+++++++++++++++++++++++
+756 -550
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-346
Ver Arquivo
@@ -1,346 +0,0 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
## adapted from artsearch plugin - Copyright (C) 2012 Jonathan Matthew
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# The Rhythmbox authors hereby grant permission for non-GPL compatible
# GStreamer plugins to be used and distributed together with GStreamer
# and Rhythmbox. This permission is above and beyond the permissions granted
# by the GPL license by which Rhythmbox is covered. If you modify this code
# you may extend this exception to your version of the code, but you are not
# obligated to do so. If you do not wish to do so, delete this exception
# statement from your version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import RB
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Gio
import os, time,re, urllib
import threading
import discogs_client as discogs
import json
import rb
from gi.repository import RB
# coverartarchive URL
COVERARTARCHIVE_RELEASE_URL = "http://coverartarchive.org/release/%s/"
ITEMS_PER_NOTIFICATION = 10
IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms')
REPEAT_SEARCH_PERIOD = 86400 * 7
DISC_NUMBER_REGEXS = (
"\(disc *[0-9]+\)",
"\(cd *[0-9]+\)",
"\[disc *[0-9]+\]",
"\[cd *[0-9]+\]",
" - disc *[0-9]+$",
" - cd *[0-9]+$",
" disc *[0-9]+$",
" cd *[0-9]+$")
def file_root (f_name):
return os.path.splitext (f_name)[0].lower ()
class CoverSearch(object):
def __init__(self, store, key, last_time, searches):
self.store = store
self.key = key.copy()
self.last_time = last_time
self.searches = searches
def next_search(self):
print "next search"
if len(self.searches) == 0:
print "no more searches"
key = RB.ExtDBKey.create_storage("album", self.key.get_field("album"))
key.add_field("artist", self.key.get_field("artist"))
self.store.store(key, RB.ExtDBSourceType.NONE, None)
print "end of next_search False"
return False
search = self.searches.pop(0)
print "calling search"
search.search(self.key, self.last_time, self.store, self.search_done, None)
print "end of next_search TRUE"
return True
def search_done(self, args):
self.next_search()
class CoverAlbumSearch:
def __init__ (self):
pass
def finished(self, results):
parent = self.file.get_parent()
base = file_root (self.file.get_basename())
for f_name in results:
if file_root (f_name) == base:
uri = parent.resolve_relative_path(f_name).get_parse_name()
found = self.get_embedded_image(uri)
if found:
break
self.callback(self.callback_args)
def _enum_dir_cb(self, fileenum, result, results):
try:
files = fileenum.next_files_finish(result)
if files is None or len(files) == 0:
print "okay, done; got %d files" % len(results)
self.finished(results)
return
for f in files:
ct = f.get_attribute_string("standard::content-type")
# assume readable unless told otherwise
readable = True
if f.has_attribute("access::can-read"):
readable = f.get_attribute_boolean("access::can-read")
if ct is not None and ct.startswith("audio/") and readable:
print "_enum_dir_cb %s " % f.get_name()
results.append(f.get_name())
fileenum.next_files_async(ITEMS_PER_NOTIFICATION, GLib.PRIORITY_DEFAULT, None, self._enum_dir_cb, results)
except Exception, e:
print "okay, probably done: %s" % e
import sys
sys.excepthook(*sys.exc_info())
self.finished(results)
def _enum_children_cb(self, parent, result, data):
try:
enumfiles = parent.enumerate_children_finish(result)
enumfiles.next_files_async(ITEMS_PER_NOTIFICATION, GLib.PRIORITY_DEFAULT, None, self._enum_dir_cb, [])
except Exception, e:
print "okay, probably done: %s" % e
import sys
sys.excepthook(*sys.exc_info())
self.callback(self.callback_args)
def search (self, key, last_time, store, callback, args):
# ignore last_time
print "calling search"
location = key.get_info("location")
if location is None:
print "not searching, we don't have a location"
callback(args)
return
self.file = Gio.file_new_for_uri(location)
if self.file.get_uri_scheme() in IGNORED_SCHEMES:
print 'not searching for local art for %s' % (self.file.get_uri())
callback(args)
return
self.album = key.get_field("album")
self.artists = key.get_field_values("artist")
self.store = store
self.callback = callback
self.callback_args = args
print 'searching for local art for %s' % (self.file.get_uri())
parent = self.file.get_parent()
enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", 0, 0, None, self._enum_children_cb, None)
def get_embedded_image(self, search):
print "get_embedded_image"
import tempfile
imagefilename = tempfile.NamedTemporaryFile(delete=False)
key = RB.ExtDBKey.create_storage("album", self.album)
key.add_field("artist", self.artists[0])
parent = self.file.get_parent()
print parent
print "possible mp4"
try:
from mutagen.mp4 import MP4
mp = MP4(search)
if len(mp['covr']) >= 1:
imagefilename.write(mp['covr'][0])
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
imagefilename.close()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
except:
pass
print "possible flac"
try:
#flac
from mutagen import File
music = File(search)
imagefilename.write(music.pictures[0].data)
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
except:
pass
print "possible ogg"
try:
from mutagen.oggvorbis import OggVorbis
o = OggVorbis(search)
try:
pic=o['COVERART'][0]
except:
pic=o['METADATA_BLOCK_PICTURE'][0]
y=pic.decode('base64','strict')
imagefilename.write(y)
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
except:
pass
print "possible mp3"
try:
from mutagen.id3 import ID3
i = ID3(search)
apic = i.getall('APIC')[0]
imagefilename.write(apic.data)
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
except:
pass
print "dont know"
imagefilename.delete=True
imagefilename.close()
return False
class DiscogsSearch (object):
def __init__(self):
discogs.user_agent = 'CoverartBrowserSearch/0.7alpha +https://github.com/fossfreedom/coverart-browser'
def search_url (self, artist, album):
# Remove variants of Disc/CD [1-9] from album title before search
orig_album = album
for exp in DISC_NUMBER_REGEXS:
p = re.compile (exp, re.IGNORECASE)
album = p.sub ('', album)
album.strip()
url = "%s/%s" % (artist,album)
print "discogs url = %s" % url
return url
def get_release_cb(self, store, searches, cbargs, callback):
last_url = ""
for search in searches:
album = search[1]
artist = search[0]
url = self.search_url(album, artist)
print "album %s artist %s url %s" % (album, artist, url)
if url == last_url:
continue
last_url = url
try:
s = discogs.Search(url)
url = s.results()[0].data['images'][0]['uri150']
current_key = RB.ExtDBKey.create_storage("album", album)
current_key.add_field("artist", artist)
store.store_uri(current_key, RB.ExtDBSourceType.SEARCH, url)
print "got something"
break
except:
pass
self.callback(cbargs)
return False
def search(self, key, last_time, store, callback, args):
if last_time > (time.time() - REPEAT_SEARCH_PERIOD):
callback (args)
return
album = key.get_field("album")
artists = key.get_field_values("artist")
artists = filter(lambda x: x not in (None, "", _("Unknown")), artists)
if album in ("", _("Unknown")):
album = None
if album == None or len(artists) == 0:
callback (args)
return
self.searches = []
for a in artists:
self.searches.append([a, album])
self.searches.append(["Various Artists", album])
self.callback = callback
self.callback_args = args
threading.Thread( target=self.get_release_cb, args=(store, self.searches, args, callback)).start()
class CoverartArchiveSearch(object):
def get_release_cb (self, data, args):
(key, store, callback, cbargs) = args
if data is None:
print "coverartarchive release request returned nothing"
callback(*cbargs)
return
try:
resp = json.loads(data)
image_url = resp['images'][0]['image']
print image_url
storekey = RB.ExtDBKey.create_storage('album', key.get_field('album'))
storekey.add_field("artist", key.get_field("artist"))
store.store_uri(storekey, RB.ExtDBSourceType.SEARCH, image_url)
callback(*cbargs)
except Exception, e:
print "exception parsing coverartarchive response: %s" % e
callback(*cbargs)
def search(self, key, last_time, store, callback, *args):
key = key.copy() # ugh
album_id = key.get_info("musicbrainz-albumid")
if album_id is None:
print "no musicbrainz release ID for this track"
callback(*args)
return
url = COVERARTARCHIVE_RELEASE_URL % (album_id)
print url
loader = rb.Loader()
loader.get_url(url, self.get_release_cb, (key, store, callback, args))
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+31 -8
Ver Arquivo
@@ -1,36 +1,59 @@
[Plugin]
Loader=python
Loader=python3
Module=coverart_browser
IAge=2
Depends=rb
Depends=rb;coverart_search_providers
Name=CoverArt Browser
Name[ast]=Navegador de portaes
Name[az]=CoverArt gəzgini
Name[bg]=CoverArt Browser
Name[ca]=Navegador de Portades
Name[cs]=Prohlížeč CoverArt
Name[de]=CoverArt Browser
Name[en_AU]=CoverArt Browser
Name[en_GB]=CoverArt Browser
Name[en_US]=CoverArt Browser
Name[es]=Navegador de Portadas
Name[es]=Navegador de carátulas
Name[fi]=Kansikuvaselain
Name[fr]=Navigateur de jaquettes
Name[fr_CA]=Navigateur de pochettes
Name[gl]=Navegador de cubertas
Name[hr]=CoverArt Preglednik
Name[it]=Gestore di CoverArt
Name[ko]=앨범 표지 찾아보기
Name[ms]=Pelayar CoverArt
Name[pl]=Przeglądarka okładek
Name[pt]=Arte das Capas
Name[pt_BR]=Arte das Capas
Name[ro]=Navigator CoverArt
Name[ru]=Браузер обложек
Name[zh_CN]=封面浏览器
Description=Browse and play your albums through their covers
Description[ast]=Navegar y reproducir los álbumes per aciu de les portaes
Description[az]=Üz şəklinə görə albomlara bax və oxud
Description[bg]=Търсете и слушайте албумите си посредством обложките им
Description[ca]=Navega i reprodueix els àlbums mitjançant les seues portades
Description[de]=Durchsuchen und geben Sie Ihre Alben über deren Cover wieder
Description[en_AU]=Browse and play your albums through their covers
Description[en_GB]=Browse and play your albums through their covers
Description[es]=Navega y reproduce tu librería de álbums a través de sus portadas
Description[en_US]=Browse and play your albums through their covers
Description[es]=Navegue y reproduzca sus álbumes mediante sus carátulas
Description[fi]=Selaa ja toista levyjäsi kansikuvien kautta
Description[fr]=Parcourir les jaquettes de vos albums et les jouer
Description[fr_CA]=Parcourir les pochettes de vos albums et les jouer
Description[gl]=Buscar e reproducir os álbums a través das cubertas
Description[hr]=Pregledavajte i slušajte albume preko omota
Description[it]=Sfoglia e riproduce gli album per copertina
Description[ko]=앨범을 표지로 찾아보고 연주합니다.
Description[ms]=Layar dan mainkan album anda menerusi kulit album mereka
Description[pl]=Przeglądaj i odtwarzaj Twoje albumy według ich okładek
Description[pt]=Navegue e toque seus álbums através de suas capas
Description[pt_BR]=Navegue e toque seus álbums através de suas capas
Description[ro]=Parcurge și redă albumele prin intermediul coperților lor
Description[ru]=Просматривайте и воспроизводите альбомы по обложкам
Description[zh_CN]=浏览封面并播放专辑
Authors=fossfreedom <foss.freedom@gmail.com>, Agustín Carrasco <asermax@gmail.com>
Copyright=© 2012 fossfreedom, Agustín Carrasco © 2007 Alexandre Rosenfeld
Website=http://github.com/fossfreedom
[RB]
InitiallyEnabled=true
Website=http://github.com/fossfreedom/coverart-browser
Help=https://github.com/fossfreedom/coverart-browser/blob/release-2.0/README.md
Version=2.0
+5 -6
Ver Arquivo
@@ -1,13 +1,12 @@
[Plugin]
Loader=python
Loader=python3
Module=coverart_browser
IAge=2
Depends=rb
Depends=rb;coverart_search_providers
_Name=CoverArt Browser
_Description=Browse and play your albums through their covers
Authors=fossfreedom <foss.freedom@gmail.com>, Agustín Carrasco <asermax@gmail.com>
Copyright=© 2012 fossfreedom, Agustín Carrasco © 2007 Alexandre Rosenfeld
Website=http://github.com/fossfreedom
[RB]
InitiallyEnabled=true
Website=http://github.com/fossfreedom/coverart-browser
Help=https://github.com/fossfreedom/coverart-browser/blob/release-2.0/README.md
Version=2.0
+412 -39
Ver Arquivo
@@ -18,26 +18,31 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
# define plugin
import rb
import locale
import gettext
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import RB
from gi.repository import GdkPixbuf
from gi.repository import Peas
from gi.repository import Gio
from gi.repository import GLib
from coverart_browser_prefs import Preferences
import rb
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import CoverLocale
from coverart_browser_prefs import Preferences
from coverart_browser_source import CoverArtBrowserSource
from coverart_listview import ListView
from coverart_queueview import QueueView
from coverart_playsourceview import PlaySourceView
from coverart_toolbar import TopToolbar
from coverart_play_source import CoverArtPlaySource
class CoverArtBrowserEntryType(RB.RhythmDBEntryType):
'''
Entry type for our source.
'''
def __init__(self):
'''
Initializes the entry type.
@@ -52,13 +57,13 @@ class CoverArtBrowserPlugin(GObject.Object, Peas.Activatable):
'''
__gtype_name = 'CoverArtBrowserPlugin'
object = GObject.property(type=GObject.Object)
def __init__(self):
'''
Initialises the plugin object.
'''
GObject.Object.__init__(self)
GObject.threads_init()
self._externalmenu = None
def do_activate(self):
'''
@@ -67,60 +72,82 @@ class CoverArtBrowserPlugin(GObject.Object, Peas.Activatable):
preferences.
'''
#define .plugin text strings used for translation
plugin = _('CoverArt Browser')
desc = _('Browse and play your albums through their covers')
print "CoverArtBrowser DEBUG - do_activate"
print("CoverArtBrowser DEBUG - do_activate")
self.shell = self.object
self.db = self.shell.props.db
try:
entry_type = CoverArtBrowserEntryType()
self.db.register_entry_type(entry_type)
except NotImplementedError:
entry_type = self.db.entry_register_type(
'CoverArtBrowserEntryType')
self.entry_type = CoverArtBrowserEntryType()
self.db.register_entry_type(self.entry_type)
# we do some specific functionality when working with alternative toolbar
# variables defined by the externalpluginmenu
self.using_alternative_toolbar = False
self.using_headerbar = False
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
entry_type.category = RB.RhythmDBEntryCategory.NORMAL
# load plugin icon
theme = Gtk.IconTheme.get_default()
rb.append_plugin_source_path(theme, '/icons')
what, width, height = Gtk.icon_size_lookup(Gtk.IconSize.LARGE_TOOLBAR)
pxbf = GdkPixbuf.Pixbuf.new_from_file_at_size(
rb.find_plugin_file(self, 'img/covermgr.png'), width, height)
self.entry_type.category = RB.RhythmDBEntryCategory.NORMAL
group = RB.DisplayPageGroup.get_by_id('library')
theme = Gtk.IconTheme.get_default()
theme.append_search_path(rb.find_plugin_file(self, 'img'))
iconfile = Gio.ThemedIcon(name = 'coverart-icon-symbolic')
self.source = CoverArtBrowserSource(shell=self.shell,
name=_("CoverArt"), entry_type=entry_type,
plugin=self, pixbuf=pxbf,
# our plugin model shared between sources
self.source_query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
self.source = CoverArtBrowserSource(
shell=self.shell,
name=_("CoverArt"),
entry_type=self.entry_type,
plugin=self,
icon=iconfile,
query_model=self.shell.props.library_source.props.base_query_model)
self.shell.register_entry_type_for_source(self.source, entry_type)
self.shell.register_entry_type_for_source(self.source, self.entry_type)
self.source.props.visibility = False
self.shell.append_display_page(self.source, group)
self.source.props.query_model.connect('complete', self.load_complete)
self.playlist_source = GObject.new(
CoverArtPlaySource,
name=_("CoverArt Playlist"),
shell=self.shell,
plugin=self,
entry_type=self.entry_type)
print "CoverArtBrowser DEBUG - end do_activate"
self.shell.append_display_page(self.playlist_source, self.source)
self.shell.props.db.connect('load-complete', self.load_complete)
# GLib.timeout_add_seconds(3, self.load_complete) # kludge - if plugin activated after RB has loaded then do stuff
def delayed(*args):
if self.shell.props.selected_page:
self._externalmenu = ExternalPluginMenu(self)
return False
else:
return True
GLib.timeout_add(100, delayed)
cl.switch_locale(cl.Locale.RB)
print("CoverArtBrowser DEBUG - end do_activate")
def do_deactivate(self):
'''
Called by Rhythmbox when the plugin is deactivated. It makes sure to
free all the resources used by the plugin.
'''
print "CoverArtBrowser DEBUG - do_deactivate"
print("CoverArtBrowser DEBUG - do_deactivate")
self.source.delete_thyself()
if self._externalmenu:
self._externalmenu.cleanup(full_cleanup=True)
del self.shell
del self.db
del self.source
print "CoverArtBrowser DEBUG - end do_deactivate"
print("CoverArtBrowser DEBUG - end do_deactivate")
def load_complete(self, *args, **kwargs):
'''
@@ -128,9 +155,355 @@ class CoverArtBrowserPlugin(GObject.Object, Peas.Activatable):
Used to automatically switch to the browser if the user
has set in the preferences
'''
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
if setting[gs.PluginKey.AUTOSTART]:
GObject.idle_add(self.shell.props.display_page_tree.select,
self.source)
self._externalmenu.autostart_source()
def _translation_helper(self):
'''
a method just to help out with translation strings
it is not meant to be called by itself
'''
# define .plugin text strings used for translation
plugin = _('CoverArt Browser')
desc = _('Browse and play your albums through their covers')
# . TRANSLATORS: This is the icon-grid view that the user sees
tile = _('Tiles')
#. TRANSLATORS: This is the cover-flow view the user sees - they can swipe album covers from side-to-side
artist = _('Flow')
#. TRANSLATORS: percentage size that the image will be expanded
scale = _('Scale by %:')
# stop PyCharm removing the Preference import on optimisation
pref = Preferences()
class ExternalPluginMenu(GObject.Object):
toolbar_pos = GObject.property(type=str, default=TopToolbar.name)
def __init__(self, plugin):
super(ExternalPluginMenu, self).__init__()
self.plugin = plugin
self.shell = plugin.shell
self.source = plugin.source
self.app_id = None
self.locations = ['library-toolbar', 'queue-toolbar', 'playsource-toolbar']
from coverart_browser_source import Views
self._views = Views(self.shell)
self._use_standard_control = True
self.plugin.using_alternative_toolbar = hasattr(self.shell, 'alternative_toolbar')
if self.plugin.using_alternative_toolbar:
from alttoolbar_type import AltToolbarHeaderBar
self.plugin.using_headerbar = isinstance(self.shell.alternative_toolbar.toolbar_type, AltToolbarHeaderBar)
if self.plugin.using_headerbar:
self._use_standard_control = False
# register with headerbar to complete the setup for coverart-browser
print ("registering")
self.shell.alternative_toolbar.toolbar_type.setup_completed_async(self._headerbar_toolbar_completed)
if self._use_standard_control:
# ... otherwise just use the standard menubutton approach
self.source.props.visibility = True # make the source visible
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
setting.bind(gs.PluginKey.TOOLBAR_POS, self, 'toolbar_pos',
Gio.SettingsBindFlags.GET)
self.connect('notify::toolbar-pos', self._on_notify_toolbar_pos)
self.shell.props.display_page_tree.connect(
"selected", self.on_page_change
)
self._create_menu()
def autostart_source(self):
self.source.props.visibility = True
if self._use_standard_control:
GLib.timeout_add(1000, self.shell.props.display_page_tree.select,
self.source)
else:
# mimic user clicking category button and cover switch
self.shell.alternative_toolbar.toolbar_type.library_browser_radiobutton.set_active(True)
self.shell.alternative_toolbar.toolbar_type.stack.set_visible_child_name("coverview")
def _headerbar_toolbar_completed(self, *args):
print ("headerbar_toolbar_completed")
# if we are using the alternative_toolbar and headerbar then setup the switch
# which will control access to the various views
self._sh_hcc = self.shell.alternative_toolbar.toolbar_type.connect('song-category-clicked',
self._headerbar_category_clicked)
self._add_coverart_header_switch()
sources = { self.shell.props.queue_source,
self.shell.props.library_source,
self.source }
for source in sources:
self.shell.alternative_toolbar.toolbar_type.add_always_visible_source(source)
def _on_notify_toolbar_pos(self, *args):
# for standard menu control ... when moving the toolbar position reposition the menubutton
if self.toolbar_pos == TopToolbar.name:
self._create_menu()
else:
self.cleanup()
def cleanup(self, full_cleanup = False):
# for standard menu control, cleanup where necessary
if self.app_id:
app = Gio.Application.get_default()
for location in self.locations:
app.remove_plugin_menu_item(location, self.app_id)
self.app_id = None
if not self._use_standard_control and full_cleanup:
self.shell.alternative_toolbar.toolbar_type.stack.disconnect(self._sh_stack_id)
self.shell.alternative_toolbar.toolbar_type.stack.remove(self._box_coverview)
self.shell.alternative_toolbar.toolbar_type.disconnect(self._sh_hcc)
self.shell.alternative_toolbar.toolbar_type.headerbar.remove(self.stack_switcher)
self.stack_switcher = None
self._sh_stack_id = None
self._sh_hcc = None
def _create_menu(self):
# for the standard menu control button add the button
# to all supported view types
app = Gio.Application.get_default()
self.app_id = 'coverart-browser'
action_name = 'coverart-browser-views'
self.action = Gio.SimpleAction.new_stateful(
action_name, GLib.VariantType.new('s'),
self._views.get_action_name(ListView.name)
)
self.action.connect("activate", self.view_change_cb)
app.add_action(self.action)
menu_item = Gio.MenuItem()
section = Gio.Menu()
menu = Gio.Menu()
toolbar_item = Gio.MenuItem()
for view_name in self._views.get_view_names():
menu_item.set_label(self._views.get_menu_name(view_name))
menu_item.set_action_and_target_value(
'app.' + action_name, self._views.get_action_name(view_name)
)
section.append_item(menu_item)
menu.append_section(None, section)
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
toolbar_item.set_label('')
cl.switch_locale(cl.Locale.RB)
toolbar_item.set_submenu(menu)
for location in self.locations:
app.add_plugin_menu_item(location, self.app_id, toolbar_item)
def _add_coverart_header_switch(self):
# define the header switch control + stack control for coverart
self._box_coverview = Gtk.Box()
image_name = 'view-cover-symbolic'
stack = self.shell.alternative_toolbar.toolbar_type.stack
stack.add_named(self._box_coverview, "coverview")
stack.child_set_property(self._box_coverview, "icon-name", image_name)
self.stack_switcher = Gtk.StackSwitcher()
self.stack_switcher.set_stack(stack)
self.stack_switcher.show_all()
self.stack_switcher.set_sensitive(False)
self.shell.alternative_toolbar.toolbar_type.headerbar.pack_start(self.stack_switcher)
# create a treeview and store for all views coverart supports
self._store = Gtk.ListStore(str, str)
for view_name in self._views.get_view_names():
self._store.append([self._views.get_menu_name(view_name), view_name])
tree = Gtk.TreeView(self._store)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(_("CoverArt"), renderer, text=0)
tree.append_column(column)
tree.connect('button-press-event', self._tree_row_click)
self.tree = tree
self._box_coverview.pack_start(tree, True, True, 0)
self._sh_stack_id = stack.connect('notify::visible-child-name', self._change_stack)
stack.show_all()
self.stack = stack
self._current_tree_view = None
def _change_stack(self, widget, value):
print ("changed stack")
child_name = self.stack.get_visible_child_name()
print (child_name)
if child_name == "listview":
self.source.props.visibility = False
# if we've toggled to listview then we are no longer in coverart so reset back to songview
self._current_tree_view = None
self._select_view(ListView.name)
if self.shell.alternative_toolbar.toolbar_type.library_song_radiobutton.get_active():
self.stack_switcher.set_sensitive(False)
return
self.source.props.visibility = True
# so we are in coverview so we need to reset the coverview to what was last selected when in this mode
selection = self.tree.get_selection()
liststore, list_iter = selection.get_selected()
if not list_iter:
# nothing was selected to set the view back to what was remembered
self._current_tree_view = self._select_view(None)
treeiter = liststore.get_iter_first()
while treeiter != None:
if liststore[treeiter][1] == self._current_tree_view:
print ("about to set treeview")
print (treeiter)
path = liststore.get_path(treeiter)
print (path)
#self.tree.row_activated(liststore.get_path(treeiter), 0)
self.tree.set_cursor(path)
break
treeiter = liststore.iter_next(treeiter)
else:
# we have been here before so set the view correctly
path = liststore.get_path(list_iter)
self._current_tree_view = liststore[path][1]
self._select_view(liststore[path][1])
def _headerbar_category_clicked(self, headerbar, song_category):
print ("clicked headerbar song-category buttons")
if self.stack.get_visible_child_name() == 'coverview' and song_category:
# if we've clicked song when in coverview then we disable the switcher
# and set the view back to song
#self.stack.set_visible_child_name('listview')
#if self.shell.props.display_page_tree.select != self.shell.props.library_source:
# self._select_view(ListView.name)
#self.stack_switcher.set_sensitive(not song_category)
#self.stack_switcher.set_sensitive(False)
self.source.props.visibility = True
self._select_view(ListView.name)
if self.stack.get_visible_child_name() == 'listview' and not song_category:
# if we've clicked category when in listview then we enable the switcher
self.stack_switcher.set_sensitive(True)
self.source.props.visibility = False
if self.stack.get_visible_child_name() == 'listview' and song_category:
# if we've clicked song when in listview then we disable the switcher
self.stack_switcher.set_sensitive(False)
self.source.props.visibility = False
if self.stack.get_visible_child_name() == 'coverview' and not song_category:
# if we've clicked category when in coverview then we move to the last coverart view
# and ensure the switcher is still enabled
self.source.props.visibility = True
self._select_view(None)
self.stack_switcher.set_sensitive(True)
def _tree_row_click(self, widget, event):
'''
event called when clicking on a row in the header treeview
'''
print('_tree_row_click')
try:
treepath, treecolumn, cellx, celly = widget.get_path_at_pos(event.x, event.y)
except:
return
print (self._store[treepath][1])
self._current_tree_view = self._store[treepath][1]
self._select_view(self._store[treepath][1])
def on_page_change(self, display_page_tree, page):
'''
standard menubutton - Called when the display page changes. Grabs query models and sets the
active view.
'''
print ("on_page_change")
if page == self.shell.props.library_source:
self.action.set_state(self._views.get_action_name(ListView.name))
elif page == self.shell.props.queue_source:
self.action.set_state(self._views.get_action_name(QueueView.name))
elif page == self.plugin.playlist_source:
self.action.set_state(self._views.get_action_name(PlaySourceView.name))
def view_change_cb(self, action, current):
'''
standard menubutton - Called when the view state on a page is changed. Sets the new
state.
'''
print ("view_change_cb")
action.set_state(current)
view_name = self._views.get_view_name_for_action(current)
self._select_view(view_name)
def _select_view(self, view_name):
'''
with the view_name decide which view to be displayed
or if view_name is None then use the last remembered view_name
return view_name
'''
if not self.shell.props.display_page_tree:
return
print ("_select_view")
print (view_name)
if view_name != ListView.name and \
view_name != QueueView.name and \
view_name != PlaySourceView.name:
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
if view_name:
setting[gs.PluginKey.VIEW_NAME] = view_name
else:
view_name = setting[gs.PluginKey.VIEW_NAME]
player = self.shell.props.shell_player
player.set_selected_source(self.source) #.playlist_source)
GLib.idle_add(self.shell.props.display_page_tree.select,
self.source)
elif view_name == ListView.name:
GLib.idle_add(self.shell.props.display_page_tree.select,
self.shell.props.library_source)
elif view_name == QueueView.name:
GLib.idle_add(self.shell.props.display_page_tree.select,
self.shell.props.queue_source)
elif view_name == PlaySourceView.name:
GLib.idle_add(self.shell.props.display_page_tree.select,
self.plugin.playlist_source)
return view_name
+511 -40
Ver Arquivo
@@ -16,17 +16,34 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import locale
import gettext
import os
import shutil
import webbrowser
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import PeasGtk
from gi.repository import Peas
from gi.repository import RB
from gi.repository import Gdk
from gi.repository import GLib
import rb
import locale
import gettext
from stars import ReactiveStar
from stars import StarSize
import coverart_rb3compat as rb3compat
def webkit_support():
'''
function that returns True/False if webkit technology is supported
'''
gs = GSetting()
settings = gs.get_setting(gs.Path.PLUGIN)
return settings[gs.PluginKey.WEBKIT]
class CoverLocale:
@@ -41,7 +58,7 @@ class CoverLocale:
# below public variables and methods that can be called for CoverLocale
def __init__(self):
'''
Initializes the singleton interface, asigning all the constants
Initializes the singleton interface, assigning all the constants
used to access the plugin's settings.
'''
self.Locale = self._enum(
@@ -94,7 +111,7 @@ class CoverLocale:
class GSetting:
'''
This class manages the differentes settings that the plugins haves to
This class manages the different settings that the plugin has to
access to read or write.
'''
# storage for the instance reference
@@ -105,7 +122,7 @@ class GSetting:
# below public variables and methods that can be called for GSetting
def __init__(self):
'''
Initializes the singleton interface, asigning all the constants
Initializes the singleton interface, assigning all the constants
used to access the plugin's settings.
'''
self.Path = self._enum(
@@ -116,8 +133,9 @@ class GSetting:
self.PluginKey = self._enum(
CUSTOM_STATUSBAR='custom-statusbar',
DISPLAY_BOTTOM='display-bottom',
DISPLAY_TEXT='display-text',
DISPLAY_TEXT_POS='display-text-pos',
RANDOM='random-queue',
DISPLAY_TEXT_LOADING='display-text-loading',
DISPLAY_TEXT_ELLIPSIZE='display-text-ellipsize',
DISPLAY_TEXT_ELLIPSIZE_LENGTH='display-text-ellipsize-length',
@@ -128,12 +146,34 @@ class GSetting:
PANED_POSITION='paned-position',
SORT_BY='sort-by',
SORT_ORDER='sort-order',
SORT_BY_ARTIST='sort-by-artist',
SORT_ORDER_ARTIST='sort-order-artist',
RATING='rating-threshold',
AUTOSTART='autostart',
TOOLBAR_POS='toolbar-pos',
EMBEDDED_SEARCH='embedded-search',
DISCOGS_SEARCH='discogs-search',
COVERARTARCHIVE_SEARCH='coverartarchive-search')
BUTTON_RELIEF='button-relief',
THEME='theme',
NEW_GENRE_ICON='new-genre-icon',
ICON_PADDING='icon-padding',
ICON_SPACING='icon-spacing',
ICON_AUTOMATIC='icon-automatic',
VIEW_NAME='view-name',
FLOW_APPEARANCE='flow-appearance',
FLOW_HIDE_CAPTION='flow-hide-caption',
FLOW_SCALE='flow-scale',
FLOW_BACKGROUND_COLOUR='flow-background-colour',
FLOW_AUTOMATIC='flow-automatic',
FLOW_WIDTH='flow-width',
FLOW_MAX='flow-max-albums',
WEBKIT='webkit-support',
ARTIST_PANED_POSITION='artist-paned-pos',
USE_FAVOURITES='use-favourites',
ARTIST_INFO_PANED_POSITION='artist-info-paned-pos',
LAST_GENRE_FOLDER='last-genre-folder',
ENTRY_VIEW_MODE='entry-view-mode',
FOLLOWING='following',
ACTIVATIONS='activations',
TEXT_ALIGNMENT='text-alignment')
self.setting = {}
@@ -144,7 +184,7 @@ class GSetting:
try:
setting = self.setting[path]
except:
self.setting[path] = Gio.Settings(path)
self.setting[path] = Gio.Settings.new(path)
setting = self.setting[path]
return setting
@@ -194,6 +234,9 @@ class Preferences(GObject.Object, PeasGtk.Configurable):
__gtype_name__ = 'CoverArtBrowserPreferences'
object = GObject.property(type=GObject.Object)
GENRE_POPUP = 1
GENRE_LIST = 2
def __init__(self):
'''
Initialises the preferences, getting an instance of the settings saved
@@ -203,63 +246,138 @@ class Preferences(GObject.Object, PeasGtk.Configurable):
gs = GSetting()
self.settings = gs.get_setting(gs.Path.PLUGIN)
self._first_run = True
self._cover_size = 0
self._cover_size_delay = 0
def do_create_configure_widget(self):
'''
Creates the plugin's preferences dialog
'''
return self._create_display_contents(self)
def display_preferences_dialog(self, plugin):
print("DEBUG - display_preferences_dialog")
if self._first_run:
self._first_run = False
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self._dialog = Gtk.Dialog(modal=True, destroy_with_parent=True)
self._dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self._dialog.set_title(_('Browser Preferences'))
content_area = self._dialog.get_content_area()
content_area.pack_start(self._create_display_contents(plugin), True, True, 0)
helpbutton = self._dialog.add_button(Gtk.STOCK_HELP, Gtk.ResponseType.HELP)
helpbutton.connect('clicked', self._display_help)
self._dialog.show_all()
print("shown")
while True:
response = self._dialog.run()
print("and run")
if response != Gtk.ResponseType.HELP:
break
self._dialog.hide()
print("DEBUG - display_preferences_dialog end")
def _display_help(self, *args):
peas = Peas.Engine.get_default()
uri = peas.get_plugin_info('coverart_browser').get_help_uri()
webbrowser.open(uri)
def _create_display_contents(self, plugin):
print("DEBUG - create_display_contents")
# create the ui
self._first_run = True
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
builder = Gtk.Builder()
builder.add_from_file(rb.find_plugin_file(self,
'ui/coverart_browser_prefs.ui'))
builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
builder.add_from_file(rb.find_plugin_file(plugin,
'ui/coverart_browser_prefs.ui'))
self.launchpad_button = builder.get_object('show_launchpad')
self.launchpad_label = builder.get_object('launchpad_label')
builder.connect_signals(self)
# . TRANSLATORS: Do not translate this string.
translators = _('translator-credits')
if translators != "translator-credits":
self.launchpad_label.set_text(translators)
else:
self.launchpad_button.set_visible(False)
gs = GSetting()
# bind the toggles to the settings
toggle_statusbar = builder.get_object('custom_statusbar_checkbox')
self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR,
toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT)
toggle_bottom = builder.get_object('display_bottom_checkbox')
self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom,
'active', Gio.SettingsBindFlags.DEFAULT)
toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT)
toggle_text = builder.get_object('display_text_checkbox')
self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active',
Gio.SettingsBindFlags.DEFAULT)
Gio.SettingsBindFlags.DEFAULT)
box_text = builder.get_object('display_text_box')
self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive',
Gio.SettingsBindFlags.GET)
Gio.SettingsBindFlags.GET)
self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS]
self.display_text_under_radiobutton = builder.get_object('display_text_under_radiobutton')
self.display_text_within_radiobutton = builder.get_object('display_text_within_radiobutton')
if self.display_text_pos:
self.display_text_under_radiobutton.set_active(True)
else:
self.display_text_within_radiobutton.set_active(True)
random_scale = builder.get_object('random_adjustment')
self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value',
Gio.SettingsBindFlags.DEFAULT)
toggle_text_ellipsize = builder.get_object(
'display_text_ellipsize_checkbox')
self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE,
toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT)
toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT)
box_text_ellipsize_length = builder.get_object(
'display_text_ellipsize_length_box')
self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE,
box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET)
box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET)
spinner_text_ellipsize_length = builder.get_object(
'display_text_ellipsize_length_spin')
self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH,
spinner_text_ellipsize_length, 'value',
Gio.SettingsBindFlags.DEFAULT)
spinner_text_ellipsize_length, 'value',
Gio.SettingsBindFlags.DEFAULT)
spinner_font_size = builder.get_object(
'display_font_spin')
self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE,
spinner_font_size, 'value',
Gio.SettingsBindFlags.DEFAULT)
spinner_font_size, 'value',
Gio.SettingsBindFlags.DEFAULT)
cover_size_scale = builder.get_object('cover_size_adjustment')
self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value',
Gio.SettingsBindFlags.DEFAULT)
#self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value',
# Gio.SettingsBindFlags.DEFAULT)
self._cover_size = self.settings[gs.PluginKey.COVER_SIZE]
cover_size_scale.set_value(self._cover_size)
cover_size_scale.connect('value-changed', self.on_cover_size_scale_changed)
add_shadow = builder.get_object('add_shadow_checkbox')
self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active',
Gio.SettingsBindFlags.DEFAULT)
Gio.SettingsBindFlags.DEFAULT)
rated_box = builder.get_object('rated_box')
self.stars = ReactiveStar(size=StarSize.BIG)
@@ -274,34 +392,387 @@ class Preferences(GObject.Object, PeasGtk.Configurable):
autostart = builder.get_object('autostart_checkbox')
self.settings.bind(gs.PluginKey.AUTOSTART,
autostart, 'active', Gio.SettingsBindFlags.DEFAULT)
embedded_search = builder.get_object('embedded_checkbox')
self.settings.bind(gs.PluginKey.EMBEDDED_SEARCH,
embedded_search, 'active', Gio.SettingsBindFlags.DEFAULT)
discogs_search = builder.get_object('discogs_checkbox')
self.settings.bind(gs.PluginKey.DISCOGS_SEARCH,
discogs_search, 'active', Gio.SettingsBindFlags.DEFAULT)
autostart, 'active', Gio.SettingsBindFlags.DEFAULT)
toolbar_pos_combo = builder.get_object('show_in_combobox')
renderer = Gtk.CellRendererText()
toolbar_pos_combo.pack_start(renderer, True)
toolbar_pos_combo.add_attribute(renderer, 'text', 1)
self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo,
'active-id', Gio.SettingsBindFlags.DEFAULT)
'active-id', Gio.SettingsBindFlags.DEFAULT)
light_source_combo = builder.get_object('light_source_combobox')
renderer = Gtk.CellRendererText()
light_source_combo.pack_start(renderer, True)
light_source_combo.add_attribute(renderer, 'text', 1)
self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo,
'active-id', Gio.SettingsBindFlags.DEFAULT)
'active-id', Gio.SettingsBindFlags.DEFAULT)
combo_liststore = builder.get_object('combo_liststore')
from coverart_utils import Theme
for theme in Theme(self).themes:
combo_liststore.append([theme, theme])
theme_combo = builder.get_object('theme_combobox')
renderer = Gtk.CellRendererText()
theme_combo.pack_start(renderer, True)
theme_combo.add_attribute(renderer, 'text', 1)
self.settings.bind(gs.PluginKey.THEME, theme_combo,
'active-id', Gio.SettingsBindFlags.DEFAULT)
button_relief = builder.get_object('button_relief_checkbox')
self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active',
Gio.SettingsBindFlags.DEFAULT)
# create user data files
cachedir = RB.user_cache_dir() + "/coverart_browser/usericons"
if not os.path.exists(cachedir):
os.makedirs(cachedir)
popup = cachedir + "/popups.xml"
temp = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml')
# lets see if there is a legacy file - if necessary copy it to the cache dir
if os.path.isfile(temp) and not os.path.isfile(popup):
shutil.copyfile(temp, popup)
if not os.path.isfile(popup):
template = rb.find_plugin_file(plugin, 'template/popups.xml')
folder = os.path.split(popup)[0]
if not os.path.exists(folder):
os.makedirs(folder)
shutil.copyfile(template, popup)
# now prepare the genre tab
from coverart_utils import GenreConfiguredSpriteSheet
from coverart_utils import get_stock_size
self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size())
self.alt_liststore = builder.get_object('alt_liststore')
self.alt_user_liststore = builder.get_object('alt_user_liststore')
self._iters = {}
for key in list(self._sheet.keys()):
store_iter = self.alt_liststore.append([key, self._sheet[key]])
self._iters[(key, self.GENRE_POPUP)] = store_iter
for key, value in self._sheet.genre_alternate.items():
if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER:
store_iter = self.alt_user_liststore.append([key.name,
self._sheet[self._sheet.genre_alternate[key]],
self._sheet.genre_alternate[key]])
self._iters[(key.name, self.GENRE_LIST)] = store_iter
self.amend_mode = False
self.blank_iter = None
self.genre_combobox = builder.get_object('genre_combobox')
self.genre_entry = builder.get_object('genre_entry')
self.genre_view = builder.get_object('genre_view')
self.save_button = builder.get_object('save_button')
self.filechooserdialog = builder.get_object('filechooserdialog')
last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER]
if last_genre_folder != "":
self.filechooserdialog.set_current_folder(last_genre_folder)
padding_scale = builder.get_object('padding_adjustment')
self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value',
Gio.SettingsBindFlags.DEFAULT)
spacing_scale = builder.get_object('spacing_adjustment')
self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value',
Gio.SettingsBindFlags.DEFAULT)
icon_automatic = builder.get_object('icon_automatic_checkbox')
self.settings.bind(gs.PluginKey.ICON_AUTOMATIC,
icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT)
#flow tab
flow_combo = builder.get_object('flow_combobox')
renderer = Gtk.CellRendererText()
flow_combo.pack_start(renderer, True)
flow_combo.add_attribute(renderer, 'text', 1)
self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo,
'active-id', Gio.SettingsBindFlags.DEFAULT)
flow_hide = builder.get_object('hide_caption_checkbox')
self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION,
flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT)
flow_scale = builder.get_object('cover_scale_adjustment')
self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value',
Gio.SettingsBindFlags.DEFAULT)
flow_width = builder.get_object('cover_width_adjustment')
self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value',
Gio.SettingsBindFlags.DEFAULT)
flow_max = builder.get_object('flow_max_adjustment')
self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value',
Gio.SettingsBindFlags.DEFAULT)
flow_automatic = builder.get_object('automatic_checkbox')
self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC,
flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT)
self.background_colour = self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR]
self.white_radiobutton = builder.get_object('white_radiobutton')
self.black_radiobutton = builder.get_object('black_radiobutton')
if self.background_colour == 'W':
self.white_radiobutton.set_active(True)
else:
self.black_radiobutton.set_active(True)
self.text_alignment = self.settings[gs.PluginKey.TEXT_ALIGNMENT]
self.text_alignment_left_radiobutton = builder.get_object('left_alignment_radiobutton')
self.text_alignment_centre_radiobutton = builder.get_object('centre_alignment_radiobutton')
self.text_alignment_right_radiobutton = builder.get_object('right_alignment_radiobutton')
if self.text_alignment == 0:
self.text_alignment_left_radiobutton.set_active(True)
elif self.text_alignment == 1:
self.text_alignment_centre_radiobutton.set_active(True)
else:
self.text_alignment_right_radiobutton.set_active(True)
# return the dialog
self._first_run = False
print("end create dialog contents")
return builder.get_object('main_notebook')
def on_cover_size_scale_changed(self, scale):
self._cover_size = scale.get_value()
def delay(*args):
print('delay')
print(self._cover_size_delay)
self._cover_size_delay = self._cover_size_delay + 1
if self._cover_size_delay >= 8:
gs = GSetting()
self.settings[gs.PluginKey.COVER_SIZE] = self._cover_size
self._cover_size_delay = 0
return False
return True
if self._cover_size_delay == 0:
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100, delay, None)
else:
self._cover_size_delay = 1
def on_flow_combobox_changed(self, combobox):
current_val = combobox.get_model()[combobox.get_active()][0]
gs = GSetting()
if self.settings[gs.PluginKey.FLOW_APPEARANCE] != current_val:
if current_val == 'flow-vert':
default_size = 150
else:
default_size = 600
self.settings[gs.PluginKey.FLOW_WIDTH] = default_size
if current_val == 'carousel':
self.settings[gs.PluginKey.FLOW_HIDE_CAPTION] = True
def on_background_radio_toggled(self, button):
if button.get_active():
gs = GSetting()
if button == self.white_radiobutton:
self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'W'
else:
self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'B'
def on_display_text_pos_radio_toggled(self, button):
if self._first_run:
return
if button.get_active():
gs = GSetting()
if button == self.display_text_under_radiobutton:
self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True
else:
self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = False
self.settings[gs.PluginKey.ADD_SHADOW] = False
def on_text_alignment_radiobutton_toggled(self, button):
if self._first_run:
return
if button.get_active():
gs = GSetting()
if button == self.text_alignment_left_radiobutton:
self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 0
elif button == self.text_alignment_centre_radiobutton:
self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 1
else:
self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 2
def on_add_shadow_checkbox_toggled(self, button):
if button.get_active():
# gs = GSetting()
#self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True
self.display_text_under_radiobutton.set_active(True)
def rating_changed_callback(self, stars):
print "rating_changed_callback"
print("rating_changed_callback")
gs = GSetting()
self.settings[gs.PluginKey.RATING] = self.stars.get_rating()
def on_save_button_clicked(self, button):
'''
action when genre edit area is saved
'''
entry_value = self.genre_entry.get_text()
treeiter = self.genre_combobox.get_active_iter()
icon_value = self.alt_liststore[treeiter][0]
# model 0 is the icon name, model 1 is the pixbuf
if self.amend_mode:
key = self._sheet.amend_genre_info(self.current_genre,
entry_value, icon_value)
self.alt_user_liststore[self._iters[(self.current_genre,
self.GENRE_LIST)]][1] = self._sheet[self._sheet.genre_alternate[key]]
self.alt_user_liststore[self._iters[(self.current_genre,
self.GENRE_LIST)]][0] = key.name
store_iter = self._iters[(self.current_genre, self.GENRE_LIST)]
del self._iters[(self.current_genre, self.GENRE_LIST)]
self._iters[(key.name, self.GENRE_LIST)] = store_iter
else:
self.amend_mode = True
key = self._sheet.amend_genre_info('',
entry_value, icon_value)
self.current_genre = key.name
store_iter = self.alt_user_liststore.append([key.name,
self._sheet[self._sheet.genre_alternate[key]],
self._sheet.genre_alternate[key]])
self._iters[(key.name, self.GENRE_LIST)] = store_iter
selection = self.genre_view.get_selection()
selection.select_iter(store_iter)
self.save_button.set_sensitive(False)
self._toggle_new_genre_state()
def on_genre_filechooserbutton_file_set(self, filechooser):
'''
action when genre new icon button is pressed
'''
key = self._sheet.add_genre_icon(self.filechooserdialog.get_filename())
store_iter = self.alt_liststore.append([key.name, self._sheet[key.name]])
self._iters[(key.name, self.GENRE_POPUP)] = store_iter
gs = GSetting()
last_genre_folder = self.filechooserdialog.get_current_folder()
print(last_genre_folder)
print(self.filechooserdialog.get_filename())
if last_genre_folder:
self.settings[gs.PluginKey.LAST_GENRE_FOLDER] = last_genre_folder
def on_genre_view_selection_changed(self, view):
'''
action when user selects a row in the list of genres
'''
model, genre_iter = view.get_selected()
if genre_iter:
self.genre_entry.set_text(model[genre_iter][0])
index = model[genre_iter][2]
if index != '':
self.genre_combobox.set_active_iter(self._iters[(index, self.GENRE_POPUP)])
self.amend_mode = True
self.current_genre = rb3compat.unicodestr(model[genre_iter][0], 'utf-8')
else:
self.genre_entry.set_text('')
self.genre_combobox.set_active_iter(None)
self.amend_mode = False
if self.blank_iter and self.amend_mode:
try:
index = model[self.blank_iter][0]
if index == '':
model.remove(self.blank_iter)
self.blank_iter = None
except:
self.blank_iter = None
def on_add_button_clicked(self, button):
'''
action when a new genre is added to the table
'''
self.genre_entry.set_text('')
self.genre_combobox.set_active(-1)
self.amend_mode = False
self.blank_iter = self.alt_user_liststore.append(['', None, ''])
selection = self.genre_view.get_selection()
selection.select_iter(self.blank_iter)
def on_delete_button_clicked(self, button):
'''
action when a genre is to be deleted
'''
selection = self.genre_view.get_selection()
model, genre_iter = selection.get_selected()
if genre_iter:
index = rb3compat.unicodestr(model[genre_iter][0], 'utf-8')
model.remove(genre_iter)
if index:
del self._iters[(index, self.GENRE_LIST)]
self._sheet.delete_genre(index)
self._toggle_new_genre_state()
def set_save_sensitivity(self, _):
'''
action to toggle the state of the save button depending
upon the values entered in the genre edit fields
'''
entry_value = self.genre_entry.get_text()
treeiter = self.genre_combobox.get_active_iter()
entry_value = rb3compat.unicodestr(entry_value, 'utf-8')
enable = False
try:
test = self._iters[(entry_value, self.GENRE_LIST)]
if RB.search_fold(self.current_genre) == RB.search_fold(entry_value):
# if the current entry is the same then could save
enable = True
except:
# reach here if this is a brand new entry
enable = True
if treeiter == None or entry_value == None or entry_value == "":
# no icon chosen, or no entry value then nothing to save
enable = False
self.save_button.set_sensitive(enable)
def _toggle_new_genre_state(self):
'''
fire an event - uses gsettings and an object such as a
controller connects to receive the signal that a new or amended
genre has been made
'''
gs = GSetting()
test = self.settings[gs.PluginKey.NEW_GENRE_ICON]
if test:
test = False
else:
test = True
self.settings[gs.PluginKey.NEW_GENRE_ICON] = test
def on_show_launchpad_toggled(self, button):
self.launchpad_label.set_visible(button.get_active())
+1045 -867
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+470 -146
Ver Arquivo
@@ -16,12 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import GdkPixbuf
from datetime import date
from collections import OrderedDict
from collections import namedtuple
from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import RB
import rb
from gi.repository import Gio
from gi.repository import GLib
from coverart_browser_prefs import CoverLocale
from coverart_browser_prefs import GSetting
@@ -30,15 +34,23 @@ from coverart_utils import GenreConfiguredSpriteSheet
from coverart_utils import ConfiguredSpriteSheet
from coverart_utils import get_stock_size
from coverart_utils import CaseInsensitiveDict
from coverart_utils import Theme
import rb
MenuNodeT = namedtuple('MenuNode', 'label menutype typevalue')
def MenuNode(label, menutype=None, typevalue=None):
return MenuNodeT(label, menutype, typevalue)
from datetime import date
from collections import OrderedDict
class OptionsController(GObject.Object):
# properties
options = GObject.property(type=object, default=None)
current_key = GObject.property(type=str, default=None)
update_image = GObject.property(type=bool, default=False)
enabled = GObject.property(type=bool, default=True)
def __init__(self):
super(OptionsController, self).__init__()
@@ -66,8 +78,33 @@ class OptionsController(GObject.Object):
def get_current_description(self):
return self.current_key
class PlaylistPopupController(OptionsController):
def update_images(self, *args):
pass
def create_spritesheet(self, plugin, sheet, typestr):
'''
helper function to create a specific spritesheet
'''
if sheet:
del sheet
return ConfiguredSpriteSheet(plugin, typestr, get_stock_size())
def create_button_image(self, plugin, image, icon_name):
'''
helper function to create a button image
'''
if image:
del image
path = 'img/' + Theme(self.plugin).current + '/'
return create_pixbuf_from_file_at_size(
rb.find_plugin_file(self.plugin, path + icon_name),
*get_stock_size())
class PlaylistPopupController(OptionsController):
def __init__(self, plugin, album_model):
super(PlaylistPopupController, self).__init__()
@@ -85,27 +122,31 @@ class PlaylistPopupController(OptionsController):
if " (" in self._queue_name:
self._queue_name = self._queue_name[0:self._queue_name.find(" (")]
# configure the sprite sheet
self._spritesheet = ConfiguredSpriteSheet(plugin, 'playlist',
get_stock_size())
self._spritesheet = None
self._update_options(shell)
# get the playlist manager and it's model
playlist_manager = shell.props.playlist_manager
playlist_model = playlist_manager.props.display_page_model
# get the playlist model so we can monitor changes
playlist_model = shell.props.display_page_model
# connect signals to update playlists
playlist_model.connect('row-inserted', self._update_options, shell)
playlist_model.connect('row-deleted', self._update_options, shell)
playlist_model.connect('row-changed', self._update_options, shell)
# generate initial options
self._update_options(shell)
def update_images(self, *args):
self._spritesheet = self.create_spritesheet(self.plugin,
self._spritesheet, 'playlist')
if args[-1]:
self.update_image = True
def _update_options(self, *args):
shell = args[-1]
self.update_images(False)
playlist_manager = shell.props.playlist_manager
still_exists = self.current_key == self._library_name or\
self.current_key == self._queue_name
still_exists = self.current_key == self._library_name or \
self.current_key == self._queue_name
# retrieve the options
values = OrderedDict()
@@ -123,11 +164,11 @@ class PlaylistPopupController(OptionsController):
values[name] = playlist
still_exists = still_exists or name == self.current_key
self.values = values
self.options = values.keys()
self.current_key = self.current_key if still_exists else\
self.values = values
self.options = list(values.keys())
self.current_key = self.current_key if still_exists else \
self._library_name
def do_action(self):
@@ -137,7 +178,7 @@ class PlaylistPopupController(OptionsController):
self._album_model.remove_filter('model')
else:
self._album_model.replace_filter('model',
playlist.get_query_model())
playlist.get_query_model())
def get_current_image(self):
playlist = self.values[self.current_key]
@@ -155,13 +196,15 @@ class PlaylistPopupController(OptionsController):
class GenrePopupController(OptionsController):
# properties
new_genre_icon = GObject.property(type=bool, default=False)
def __init__(self, plugin, album_model):
super(GenrePopupController, self).__init__()
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self._album_model = album_model
shell = plugin.shell
@@ -169,34 +212,57 @@ class GenrePopupController(OptionsController):
# create a new property model for the genres
genres_model = RB.RhythmDBPropertyModel.new(shell.props.db,
RB.RhythmDBPropType.GENRE)
RB.RhythmDBPropType.GENRE)
query = shell.props.library_source.props.base_query_model
genres_model.props.query_model = query
# initial genre
self._initial_genre = _('All Genres')#genres_model[0][0]
self._initial_genre = _('All Genres')
# initialise the button spritesheet and other images
self._spritesheet = GenreConfiguredSpriteSheet(plugin, 'genre',
get_stock_size())
self._default_image = create_pixbuf_from_file_at_size(
rb.find_plugin_file(plugin, 'img/default_genre.png'),
*get_stock_size())
self._unrecognised_image = create_pixbuf_from_file_at_size(
rb.find_plugin_file(plugin, 'img/unrecognised_genre.png'),
*get_stock_size())
self._spritesheet = None
self._default_image = None
self._unrecognised_image = None
# connect signals to update genres
query.connect('row-inserted', self._update_options, genres_model)
query.connect('row-deleted', self._update_options, genres_model)
query.connect('row-changed', self._update_options, genres_model)
self._connect_properties()
self._connect_signals(query, genres_model)
# generate initial popup
self._update_options(genres_model)
def update_images(self, *args):
if self._spritesheet:
del self._spritesheet
self._spritesheet = GenreConfiguredSpriteSheet(self.plugin,
'genre', get_stock_size())
self._default_image = self.create_button_image(self.plugin,
self._default_image, 'default_genre.png')
self._unrecognised_image = self.create_button_image(self.plugin,
self._unrecognised_image, 'unrecognised_genre.png')
if args[-1]:
self.update_image = True
def _connect_signals(self, query, genres_model):
# connect signals to update genres
self.connect('notify::new-genre-icon', self._update_options, genres_model)
query.connect('row-inserted', self._update_options, genres_model)
query.connect('row-deleted', self._update_options, genres_model)
query.connect('row-changed', self._update_options, genres_model)
def _connect_properties(self):
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
setting.bind(gs.PluginKey.NEW_GENRE_ICON, self, 'new_genre_icon',
Gio.SettingsBindFlags.GET)
def _update_options(self, *args):
genres_model = args[-1]
self.update_images(False)
still_exists = False
# retrieve the options
@@ -204,18 +270,20 @@ class GenrePopupController(OptionsController):
row_num = 0
for row in genres_model:
if row_num == 0:
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
genre = _('All Genres')
row_num = row_num + 1
else:
genre = row[0]
options.append(genre)
still_exists = still_exists or genre == self.current_key
self.options = options
self.current_key = self.current_key if still_exists else\
self.current_key = self.current_key if still_exists else \
self._initial_genre
def do_action(self):
@@ -233,58 +301,77 @@ class GenrePopupController(OptionsController):
if test_genre == self._initial_genre.lower():
image = self._default_image
elif test_genre in self._spritesheet:
image = self._spritesheet[test_genre]
else:
image = self._find_alternates(test_genre)
if image == self._unrecognised_image and \
test_genre in self._spritesheet:
image = self._spritesheet[test_genre]
return image
def _find_alternates(self, test_genre):
# the following genre checks are required
# 1. if we have locale specific genres check first
# 2. then check locale specific alternates
# 3. then check if we have default genres
# 4. then check if we have default alternates
# 1. if we have user defined genres
# 2. then check locale specific system genres
# 3. then check local specific alternates
# 4. then check if we system genres
# first check if any of the locale genres are a substring
# where necessary check if any of the genres are a substring
# of test_genre - check in reverse order so that we
# test largest strings first (prevents spurious matches with
# short strings)
# N.B. we use RB.search_fold since the strings can be
# in a mixture of cases, both unicode (normalized or not) and str
# and as usual python cannot mix and match these types.
test_genre = RB.search_fold(test_genre)
ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_USER)
if ret:
return sprite
for genre in sorted(self._spritesheet.locale_names,
key=lambda b: (-len(b), b)):
if RB.search_fold(genre) in RB.search_fold(test_genre):
key=lambda b: (-len(b), b)):
if RB.search_fold(genre) in test_genre:
return self._spritesheet[self._spritesheet.locale_names[genre]]
# next check locale alternates
case_search = CaseInsensitiveDict(self._spritesheet.locale_alternate)
if RB.search_fold(test_genre) in case_search:
return self._spritesheet[case_search[RB.search_fold(test_genre)]]
ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_LOCALE)
if ret:
return sprite
ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_SYSTEM)
if ret:
return sprite
# check if any of the default genres are a substring
# of test_genre - check in reverse order so that we
# test largest strings first (prevents spurious matches with
# short strings)
for genre in sorted(self._spritesheet.names,
key=lambda b: (-len(b), b)):
if RB.search_fold(genre) in RB.search_fold(test_genre):
key=lambda b: (-len(b), b)):
if RB.search_fold(genre) in test_genre:
return self._spritesheet[genre]
# next check alternates
case_search = CaseInsensitiveDict(self._spritesheet.alternate)
if RB.search_fold(test_genre) in case_search:
return self._spritesheet[case_search[RB.search_fold(test_genre)]]
# if no matches then default to unrecognised image
return self._unrecognised_image
def _match_genres(self, test_genre, genre_type):
case_search = CaseInsensitiveDict(
dict((k.name, v) for k, v in self._spritesheet.genre_alternate.items()
if k.genre_type == genre_type))
if test_genre in case_search:
return (True, self._spritesheet[case_search[test_genre]])
else:
return (False, None)
def get_current_description(self):
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
if self.current_key == self._initial_genre:
return _('All Genres')
else:
@@ -292,35 +379,40 @@ class GenrePopupController(OptionsController):
class SortPopupController(OptionsController):
def __init__(self, plugin, album_model):
def __init__(self, plugin, viewmgr):
super(SortPopupController, self).__init__()
self._album_model = album_model
# initialise spritesheet
self._spritesheet = ConfiguredSpriteSheet(plugin, 'sort',
get_stock_size())
self._viewmgr = viewmgr
self.plugin = plugin
# sorts dictionary
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.values = OrderedDict([(_('Sort by album name'), 'name'),
(_('Sort by album artist'), 'artist'),
(_('Sort by year'), 'year'),
(_('Sort by rating'), 'rating')])
(_('Sort by album artist'), 'artist'),
(_('Sort by year'), 'year'),
(_('Sort by rating'), 'rating')])
self.options = self.values.keys()
self.options = list(self.values.keys())
# get the current sort key and initialise the superclass
gs = GSetting()
source_settings = gs.get_setting(gs.Path.PLUGIN)
value = source_settings[gs.PluginKey.SORT_BY]
self.current_key = self.values.keys()[
self.values.values().index(value)]
self._spritesheet = None
self.update_images(False)
self.current_key = list(self.values.keys())[
list(self.values.values()).index(value)]
def update_images(self, *args):
self._spritesheet = self.create_spritesheet(self.plugin,
self._spritesheet, 'sort')
if args[-1]:
self.update_image = True
def do_action(self):
sort = self.values[self.current_key]
@@ -328,55 +420,191 @@ class SortPopupController(OptionsController):
settings = gs.get_setting(gs.Path.PLUGIN)
settings[gs.PluginKey.SORT_BY] = sort
self._album_model.sort(sort)
self._viewmgr.current_view.get_default_manager().emit('sort', "album")
def get_current_image(self):
sort = self.values[self.current_key]
sort = self.values[self.current_key]
return self._spritesheet[sort]
class DecadePopupController(OptionsController):
class ArtistSortPopupController(OptionsController):
def __init__(self, plugin, viewmgr):
super(ArtistSortPopupController, self).__init__()
self._viewmgr = viewmgr
self.plugin = plugin
# sorts dictionary
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.values = OrderedDict([(_('Sort by album name'), 'name_artist'),
(_('Sort by year'), 'year_artist'),
(_('Sort by rating'), 'rating_artist')])
self.options = list(self.values.keys())
# get the current sort key and initialise the superclass
gs = GSetting()
source_settings = gs.get_setting(gs.Path.PLUGIN)
value = source_settings[gs.PluginKey.SORT_BY_ARTIST]
if value not in list(self.values.values()):
print("here")
value = 'name_artist'
source_settings[gs.PluginKey.SORT_BY_ARTIST] = value
self._spritesheet = None
self.update_images(False)
self.current_key = list(self.values.keys())[
list(self.values.values()).index(value)]
print(self.current_key)
def update_images(self, *args):
self._spritesheet = self.create_spritesheet(self.plugin,
self._spritesheet, 'sort_artist')
if args[-1]:
self.update_image = True
def do_action(self):
sort = self.values[self.current_key]
gs = GSetting()
settings = gs.get_setting(gs.Path.PLUGIN)
settings[gs.PluginKey.SORT_BY_ARTIST] = sort
self._viewmgr.current_view.get_default_manager().emit('sort', "artist")
def get_current_image(self):
sort = self.values[self.current_key]
return self._spritesheet[sort]
class PropertiesMenuController(OptionsController):
favourites = GObject.property(type=bool, default=False)
follow = GObject.property(type=bool, default=False)
def __init__(self, plugin, source):
super(PropertiesMenuController, self).__init__()
self._source = source
self.plugin = plugin
self._connect_properties()
# sorts dictionary
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
# options
self.values = OrderedDict()
self.values[MenuNode(_('Download all covers'))] = 'download'
self.values[MenuNode(_('Play random album'))] = 'random'
self.values[MenuNode(_('Follow playing song'), 'check',
(True if self.follow else False))] = 'follow'
self.values[MenuNode('separator1', 'separator')] = ''
self.values[MenuNode(_('Use favourites only'), 'check',
(True if self.favourites else False))] = 'favourite'
self.values[MenuNode('separator2', 'separator')] = ''
self.values[MenuNode(_('Browser Preferences'))] = 'browser prefs'
self.values[MenuNode(_('Search Preferences'))] = 'search prefs'
self.options = list(self.values.keys())
self.update_images(False)
if self.favourites:
self._source.propertiesbutton_callback('favourite')
if self.follow:
self._source.propertiesbutton_callback('follow')
self.current_key = None
def _connect_properties(self):
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
setting.bind(
gs.PluginKey.USE_FAVOURITES,
self,
'favourites',
Gio.SettingsBindFlags.DEFAULT)
setting.bind(
gs.PluginKey.FOLLOWING,
self,
'follow',
Gio.SettingsBindFlags.DEFAULT)
def _change_key(self, dict, old, new):
for i in range(len(dict)):
k, v = dict.popitem(False)
dict[new if old == k else k] = v
def update_images(self, *args):
self._image = self.create_button_image(self.plugin,
None, 'properties.png')
if args[-1]:
self.update_image = True
def do_action(self):
if self.current_key:
key = [node for node in self.values if node.label == self.current_key]
if self.current_key == _('Use favourites only'):
self.favourites = not self.favourites
if self.current_key == _('Follow playing song'):
self.follow = not self.follow
self._source.propertiesbutton_callback(self.values[key[0]])
self.current_key = None
def get_current_image(self):
return self._image
def get_current_description(self):
return _('Properties')
class DecadePopupController(OptionsController):
def __init__(self, plugin, album_model):
super(DecadePopupController, self).__init__()
self._album_model = album_model
self.plugin = plugin
# initialize spritesheet
self._spritesheet = ConfiguredSpriteSheet(plugin, 'decade',
get_stock_size())
self._spritesheet = None
# decade options
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.values = OrderedDict()
self.values[ _('All Decades') ] = [-1, 'All Decades']
#'20s' as in the decade 2010
self.values[ _('20s') ] = [2020, '20s']
#'10s' as in the decade 2010
self.values[ _('10s') ] = [2010, '10s']
#'00s' as in the decade 2000
self.values[ _('00s') ] = [2000, '00s']
#'90s' as in the decade 1990
self.values[ _('90s') ] = [1990, '90s']
#'80s' as in the decade 1980
self.values[ _('80s') ] = [1980, '80s']
#'70s' as in the decade 1970
self.values[ _('70s') ] = [1970, '70s']
#'60s' as in the decade 1960
self.values[ _('60s') ] = [1960, '60s']
#'50s' as in the decade 1950
self.values[ _('50s') ] = [1950, '50s']
#'40s' as in the decade 1940
self.values[ _('40s') ] = [1940, '40s']
#'30s' as in the decade 1930
self.values[ _('30s') ] = [1930, '30s']
#'Older' as in 'older than the year 1930'
self.values[ _('Older') ] = [-1, 'Older']
self.options = self.values.keys()
self.values[_('All Decades')] = [-1, 'All Decades']
# '20s' as in the decade 2010
self.values[_('20s')] = [2020, '20s']
#'10s' as in the decade 2010
self.values[_('10s')] = [2010, '10s']
#'00s' as in the decade 2000
self.values[_('00s')] = [2000, '00s']
#'90s' as in the decade 1990
self.values[_('90s')] = [1990, '90s']
#'80s' as in the decade 1980
self.values[_('80s')] = [1980, '80s']
#'70s' as in the decade 1970
self.values[_('70s')] = [1970, '70s']
#'60s' as in the decade 1960
self.values[_('60s')] = [1960, '60s']
#'50s' as in the decade 1950
self.values[_('50s')] = [1950, '50s']
#'40s' as in the decade 1940
self.values[_('40s')] = [1940, '40s']
#'30s' as in the decade 1930
self.values[_('30s')] = [1930, '30s']
#'Older' as in 'older than the year 1930'
self.values[_('Older')] = [-1, 'Older']
self.options = list(self.values.keys())
# if we aren't on the 20s yet, remove it
if date.today().year < 2020:
@@ -384,14 +612,23 @@ class DecadePopupController(OptionsController):
# define a initial decade an set the initial key
self._initial_decade = self.options[0]
self.update_images(False)
self.current_key = self._initial_decade
def update_images(self, *args):
self._spritesheet = self.create_spritesheet(self.plugin,
self._spritesheet, 'decade')
if args[-1]:
self.update_image = True
def do_action(self):
if self.current_key == self._initial_decade:
self._album_model.remove_filter('decade')
else:
self._album_model.replace_filter('decade',
self.values[self.current_key][0])
self.values[self.current_key][0])
def get_current_image(self):
decade = self.values[self.current_key][1]
@@ -402,49 +639,66 @@ class DecadePopupController(OptionsController):
class SortOrderToggleController(OptionsController):
toolbar_type = "album"
def __init__(self, plugin, album_model):
def __init__(self, plugin, viewmgr):
super(SortOrderToggleController, self).__init__()
self._album_model = album_model
self._viewmgr = viewmgr
self.plugin = plugin
# options
self.values = OrderedDict([(_('Sort in descending order'), False),
(_('Sort in ascending order'), True)])
self.options = self.values.keys()
(_('Sort in ascending order'), True)])
self.options = list(self.values.keys())
# initialize images
self._images = []
self._images.append(GdkPixbuf.Pixbuf.new_from_file_at_size(
rb.find_plugin_file(plugin, 'img/arrow_down.png'),
*get_stock_size()))
self._images.append(GdkPixbuf.Pixbuf.new_from_file_at_size(
rb.find_plugin_file(plugin, 'img/arrow_up.png'),
*get_stock_size()))
# set the current key
self.gs = GSetting()
self.settings = self.gs.get_setting(self.gs.Path.PLUGIN)
sort_order = self.settings[self.gs.PluginKey.SORT_ORDER]
self.key = self.get_key()
sort_order = self.settings[self.key]
self.current_key = list(self.values.keys())[
list(self.values.values()).index(sort_order)]
self.update_images(False)
self.current_key = self.values.keys()[
self.values.values().index(sort_order)]
def get_key(self):
return self.gs.PluginKey.SORT_ORDER
def update_images(self, *args):
# initialize images
if len(self._images) > 0:
del self._images[:]
self._images.append(self.create_button_image(self.plugin,
None, 'arrow_down.png'))
self._images.append(self.create_button_image(self.plugin,
None, 'arrow_up.png'))
if args[-1]:
self.update_image = True
def do_action(self):
sort_order = self.values[self.current_key]
if not sort_order or\
sort_order != self.settings[self.gs.PluginKey.SORT_ORDER]:
self._album_model.sort(reverse=True)
self.settings[self.gs.PluginKey.SORT_ORDER] = sort_order
self.settings[self.key] = sort_order
self._viewmgr.current_view.get_default_manager().emit('sort', self.toolbar_type)
def get_current_image(self):
return self._images[self.get_current_key_index()]
class AlbumSearchEntryController(OptionsController):
class ArtistSortOrderToggleController(SortOrderToggleController):
toolbar_type = "artist"
def __init__(self, plugin, model):
super(ArtistSortOrderToggleController, self).__init__(plugin, model)
def get_key(self):
return self.gs.PluginKey.SORT_ORDER_ARTIST
class AlbumSearchEntryController(OptionsController):
# properties
search_text = GObject.property(type=str, default='')
@@ -459,22 +713,47 @@ class AlbumSearchEntryController(OptionsController):
self.values[_('Search all fields')] = 'all'
self.values[_('Search album artists')] = 'album_artist'
self.values[_('Search track artists')] = 'artist'
self.values[_('Search composers')] = 'composers'
self.values[_('Search albums')] = 'album_name'
self.values[_('Search tracks')] = 'track'
self.values[_('Search titles')] = 'track'
self.options = self.values.keys()
self.current_key = self.values.keys()[0]
self.options = list(self.values.keys())
self.current_key = list(self.values.keys())[0]
self._typing = False
self._typing_counter = 0
self._current_search_text = ""
def do_action(self):
# remove old filter
self._album_model.remove_filter(self._filter_type, False)
# asign the new filter
# assign the new filter
self._filter_type = self.values[self.current_key]
self.do_search(self.search_text, True)
def _search_typing(self, *args):
self._typing_counter = self._typing_counter + 1
if self._typing_counter >= 4 and self._typing:
self._typing = False
self._change_filter(self._current_search_text, False)
return self._typing
def _change_filter(self, search_text, force):
# self.search_text = search_text
self._current_search_text = search_text
if search_text:
self._album_model.replace_filter(self._filter_type,
search_text)
elif not force:
self._album_model.remove_filter(self._filter_type)
def do_search(self, search_text, force=False):
'''
if self.search_text != search_text or force:
self.search_text = search_text
@@ -484,11 +763,29 @@ class AlbumSearchEntryController(OptionsController):
elif not force:
self._album_model.remove_filter(self._filter_type)
'''
# self.search_text = search_text
if force:
self._typing_counter = 99
self._typing = False
self._change_filter(search_text, force)
return
if self._current_search_text != search_text:
#self.search_text = search_text
self._current_search_text = search_text
self._typing_counter = 0
if not self._typing:
self._typing = True
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100,
self._search_typing)
class AlbumQuickSearchController(object):
def __init__(self, source, album_manager):
self._source = source
def __init__(self, album_manager):
self._album_manager = album_manager
def connect_quick_search(self, quick_search):
@@ -498,15 +795,14 @@ class AlbumQuickSearchController(object):
def _on_quick_search(self, quick_search, search_text, *args):
album = self._album_manager.model.find_first_visible('album_name',
search_text)
search_text)
if album:
self._source.select_album(album)
#self._album_manager.cover_view.select_album(album)
path = self._album_manager.model.get_path(album)
self._album_manager.current_view.select_and_scroll_to_path(path)
def _on_arrow_pressed(self, quick_search, key, *args):
current = self._source.get_selected_albums()[0]
#current = self.album_manager.cover_view.get_selected_albums()[0]
current = self._album_manager.current_view.get_selected_objects()[0]
search_text = quick_search.get_text()
album = None
@@ -518,8 +814,36 @@ class AlbumQuickSearchController(object):
'album_name', search_text, current)
if album:
self._source.select_album(album)
#self._album_manager.cover_view.select_album(album)
path = self._album_manager.model.get_path(album)
self._album_manager.current_view.select_and_scroll_to_path(path)
def _on_hide(self, quick_search, *args):
self._album_manager.cover_view.grab_focus()
self._album_manager.current_view.grab_focus()
class ViewController(OptionsController):
def __init__(self, shell, viewmgr):
super(ViewController, self).__init__()
self._viewmgr = viewmgr
from coverart_browser_source import Views
views = Views(shell)
self.values = OrderedDict()
for view_name in views.get_view_names():
self.values[views.get_menu_name(view_name)] = view_name
print(view_name)
self.options = list(self.values.keys())
viewmgr.connect('new-view', self.on_notify_view_name)
def on_notify_view_name(self, *args):
for key in self.options:
if self.values[key] == self._viewmgr.view_name:
self.current_key = key
def do_action(self):
if self._viewmgr.view_name != self.values[self.current_key]:
self._viewmgr.view_name = self.values[self.current_key]
+434
Ver Arquivo
@@ -0,0 +1,434 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import json
import os
from xml.sax.saxutils import escape
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import webkit_support
from coverart_widgets import AbstractView
from coverart_widgets import PanedCollapsible
import rb
class FlowShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, flow_view):
super(FlowShowingPolicy, self).__init__()
self._flow_view = flow_view
self.counter = 0
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._has_initialised = True
self._album_manager = album_manager
self._model = album_manager.model
class CoverFlowView(AbstractView):
__gtype_name__ = "CoverFlowView"
name = 'coverflowview'
# properties
flow_background = GObject.property(type=str, default='W')
flow_automatic = GObject.property(type=bool, default=False)
flow_scale = GObject.property(type=int, default=100)
flow_hide = GObject.property(type=bool, default=False)
flow_width = GObject.property(type=int, default=600)
flow_appearance = GObject.property(type=str, default='coverflow')
flow_max = GObject.property(type=int, default=100)
panedposition = PanedCollapsible.Paned.EXPAND
def __init__(self):
super(CoverFlowView, self).__init__()
self.show_policy = FlowShowingPolicy(self)
if webkit_support():
from gi.repository import WebKit
self.view = WebKit.WebView()
else:
self.view = None
self._last_album = None
self._has_initialised = False
self._filter_changed_inprogress = False
self._on_first_use = True
def _connect_properties(self):
gs = GSetting()
settings = gs.get_setting(gs.Path.PLUGIN)
settings.bind(gs.PluginKey.FLOW_APPEARANCE, self,
'flow_appearance', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, self,
'flow_hide', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_SCALE, self,
'flow_scale', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_AUTOMATIC, self,
'flow_automatic', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_BACKGROUND_COLOUR, self,
'flow_background', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_WIDTH, self,
'flow_width', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_MAX, self,
'flow_max', Gio.SettingsBindFlags.GET)
def _connect_signals(self, source):
self.connect('notify::flow-background',
self.filter_changed)
self.connect('notify::flow-scale',
self.filter_changed)
self.connect('notify::flow-hide',
self.filter_changed)
self.connect('notify::flow-width',
self.filter_changed)
self.connect('notify::flow-appearance',
self.filter_changed)
self.connect('notify::flow-max',
self.filter_changed)
def filter_changed(self, *args):
# we can get several filter_changed calls per second
# lets simplify the processing & potential flickering when the
# call to this method has slowed stopped
self._filter_changed_event = True
if self._filter_changed_inprogress:
return
self._filter_changed_inprogress = True
def filter_events(*args):
if not self._filter_changed_event:
self._filter_changed()
self._filter_changed_inprogress = False
else:
self._filter_changed_event = False
return True
GLib.timeout_add(250, filter_events, None)
def _filter_changed(self, *args):
path = rb.find_plugin_file(self.plugin, 'coverflow/index.html')
f = open(path)
string = f.read()
f.close()
if self.flow_background == 'W':
background_colour = 'white'
if len(self.album_manager.model.store) <= self.flow_max:
foreground_colour = 'white'
else:
foreground_colour = 'black'
else:
background_colour = 'black'
if len(self.album_manager.model.store) <= self.flow_max:
foreground_colour = 'black'
else:
foreground_colour = 'white'
string = string.replace('#BACKGROUND_COLOUR', background_colour)
string = string.replace('#FOREGROUND_COLOUR', foreground_colour)
string = string.replace('#FACTOR', str(float(self.flow_scale) / 100))
if self.flow_hide:
caption = ""
else:
caption = '<div class="globalCaption"></div>'
string = string.replace('#GLOBAL_CAPTION', caption)
addon = background_colour
if self.flow_appearance == 'flow-vert':
addon += " vertical"
elif self.flow_appearance == 'carousel':
addon += " carousel"
elif self.flow_appearance == 'roundabout':
addon += " roundabout"
string = string.replace('#ADDON', addon)
string = string.replace('#WIDTH', str(self.flow_width))
identifier = self.flow.get_identifier(self.last_album)
if not identifier:
identifier = "'start'"
else:
identifier = str(identifier)
string = string.replace('#START', identifier)
#TRANSLATORS: for example 'Number of covers limited to 150'
display_message = _("Number of covers limited to %d") % self.flow_max
string = string.replace('#MAXCOVERS',
'<p>' + display_message + '</p>')
items = self.flow.initialise(self.album_manager.model, self.flow_max)
string = string.replace('#ITEMS', items)
base = os.path.dirname(path) + "/"
#Gdk.threads_enter()
print(string)
self.view.load_string(string, "text/html", "UTF-8", "file://" + base)
#Gdk.threads_leave()
if self._on_first_use:
self._on_first_use = False
GLib.timeout_add(250, self.source.show_hide_pane, (self.last_album, PanedCollapsible.Paned.EXPAND))
def get_view_icon_name(self):
return "flowview.png"
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
super(CoverFlowView, self).initialise(source)
self.album_manager = source.album_manager
self.ext_menu_pos = 6
self._connect_properties()
self._connect_signals(source)
# lets check that all covers have finished loading before
# initialising the flowcontrol and other signals
if not self.album_manager.cover_man.has_finished_loading:
self.album_manager.cover_man.connect('load-finished', self._covers_loaded)
else:
self._covers_loaded()
def _covers_loaded(self, *args):
self.flow = FlowControl(self)
self.view.connect("notify::title", self.flow.receive_message_signal)
#self.album_manager.model.connect('album-updated', self.flow.update_album, self.view)
#self.album_manager.model.connect('visual-updated', self.flow.update_album, self.view)
self.album_manager.model.connect('album-updated', self.filter_changed)
self.album_manager.model.connect('visual-updated', self.filter_changed)
self.album_manager.model.connect('filter-changed', self.filter_changed)
self.filter_changed()
@property
def last_album(self):
return self._last_album
@last_album.setter
def last_album(self, new_album):
if self._last_album != new_album:
self._last_album = new_album
self.source.click_count = 0
self.selectionchanged_callback()
def item_rightclicked_callback(self, album):
self.last_album = album
self.popup.popup(self.source, 'popup_menu', 3, Gtk.get_current_event_time())
def item_clicked_callback(self, album):
'''
Callback called when the user clicks somewhere on the flow_view.
Along with source "show_hide_pane", takes care of showing/hiding the bottom
pane after a second click on a selected album.
'''
# to expand the entry view
if self.flow_automatic:
self.source.click_count += 1
self.last_album = album
if self.source.click_count == 1:
GLib.timeout_add(250, self.source.show_hide_pane, album)
def item_activated_callback(self, album):
'''
Callback called when the flow view is double clicked. It plays the selected album
'''
self.last_album = album
self.source.play_selected_album()
return True
def item_drop_callback(self, album, webpath):
'''
Callback called when something is dropped onto the flow view - hopefully a webpath
to a picture
'''
print("item_drop_callback %s" % webpath)
print("dropped on album %s" % album)
self.album_manager.cover_man.update_cover(album, uri=webpath)
def get_selected_objects(self):
if self.last_album:
return [self.last_album]
else:
return []
def select_and_scroll_to_path(self, path):
album = self.source.album_manager.model.get_from_path(path)
self.flow.scroll_to_album(album, self.view)
self.item_clicked_callback(album)
def switch_to_view(self, source, album):
self.initialise(source)
self.show_policy.initialise(source.album_manager)
self.last_album = album
self.scroll_to_album(self.last_album)
def grab_focus(self):
self.view.grab_focus()
def scroll_to_album(self, album):
self.flow.scroll_to_album(album, self.view)
class FlowControl(object):
def __init__(self, callback_view):
self.callback_view = callback_view
self.album_identifier = {}
def get_identifier(self, album):
index = -1
for row in self.album_identifier:
if self.album_identifier[row] == album:
index = row
break
if index == -1:
return None
else:
return row
def update_album(self, model, album_path, album_iter, webview):
album = model.get_from_path(album_path)
index = -1
for row in self.album_identifier:
if self.album_identifier[row] == album:
index = row
break
if index == -1:
return
obj = {}
obj['filename'] = album.cover.original
obj['title'] = album.artist
obj['caption'] = album.name
obj['identifier'] = str(index)
webview.execute_script("update_album('%s')" % json.dumps(obj))
def receive_message_signal(self, webview, param):
# this will be key to passing stuff back and forth - need
# to develop some-sort of message protocol to distinguish "events"
title = webview.get_title()
if (not title) or (title == '"clear"'):
return
args = json.loads(title)
try:
signal = args["signal"]
except:
print("unhandled: %s " % title)
return
if signal == 'clickactive':
self.callback_view.item_clicked_callback(self.album_identifier[int(args['param'][0])])
elif signal == 'rightclickactive':
self.callback_view.item_rightclicked_callback(
self.album_identifier[int(args['param'][0])])
elif signal == 'doubleclickactive':
self.callback_view.item_activated_callback(self.album_identifier[int(args['param'][0])])
elif signal == 'dropactive':
self.callback_view.item_drop_callback(self.album_identifier[int(args['param'][0])],
args['param'][1])
else:
print("unhandled signal: %s" % signal)
def scroll_to_album(self, album, webview):
for row in self.album_identifier:
if self.album_identifier[row] == album:
webview.execute_script("scroll_to_identifier('%s')" % str(row))
break
def initialise(self, model, max_covers):
album_col = model.columns['album']
index = 0
items = ""
self.album_identifier = {}
def html_elements(fullfilename, title, caption, identifier):
return '<div class="item"><img class="content" src="' + \
escape(fullfilename) + '" title="' + \
escape(title) + '" identifier="' + \
identifier + '"/> <div class="caption">' + \
escape(caption) + '</div> </div>'
for row in model.store:
cover = row[album_col].cover.original
cover = cover.replace(
'rhythmbox-missing-artwork.svg',
'rhythmbox-missing-artwork.png') # # need a white vs black when we change the background colour
self.album_identifier[index] = row[album_col]
items += html_elements(
fullfilename=cover,
caption=row[album_col].name,
title=row[album_col].artist,
identifier=str(index))
index += 1
if index == max_covers:
break
if index != 0:
# self.callback_view.last_album = self.album_identifier[0]
pass
else:
self.callback_view.last_album = None
return items
+764
Ver Arquivo
@@ -0,0 +1,764 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import gettext
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Pango
from gi.repository import PangoCairo
from gi.repository import GdkPixbuf
from coverart_widgets import EnhancedIconView
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import CoverLocale
from coverart_album import AlbumsModel
from coverart_widgets import AbstractView
from coverart_widgets import PanedCollapsible
import rb
PLAY_SIZE_X = 30
PLAY_SIZE_Y = 30
class CellRendererThumb(Gtk.CellRendererPixbuf):
markup = GObject.property(type=str, default="")
def __init__(self, font_description, cell_area_source):
super(CellRendererThumb, self).__init__()
self.font_description = font_description
self.cell_area_source = cell_area_source
def do_render(self, cr, widget,
background_area,
cell_area,
flags):
x_offset = cell_area.x + 1
y_offset = cell_area.y + 1
# first paint the cover
pixbuf = self.props.pixbuf.scale_simple(cell_area.width - 2, cell_area.height - 2,
GdkPixbuf.InterpType.NEAREST)
Gdk.cairo_set_source_pixbuf(cr, pixbuf, x_offset, y_offset)
cr.paint()
alpha = 0.40
if ((flags & Gtk.CellRendererState.PRELIT) == Gtk.CellRendererState.PRELIT):
# if the cursor is over the cell then slightly dim
alpha -= 0.15
if self.cell_area_source.hover_pixbuf:
# if a hover pixbuf is given then paint this as well either just above the cover album info
# of at the bottom of the cell area if album info is not within the cover area
full, calc_x_offset, calc_y_offset = self.cell_area_source.calc_play_icon_offset(x_offset, y_offset)
Gdk.cairo_set_source_pixbuf(cr,
self.cell_area_source.hover_pixbuf,
calc_x_offset,
calc_y_offset - PLAY_SIZE_Y)
cr.paint()
if not (self.cell_area_source.display_text and self.cell_area_source.display_text_pos == False):
return
# the rest of the routine paints the contents of text within a cover if specified
# PANGO LAYOUT
layout_width = cell_area.width - 2
pango_layout = PangoCairo.create_layout(cr)
pango_layout.set_markup(self.markup, -1)
pango_layout.set_alignment(self.cell_area_source.text_alignment)
pango_layout.set_font_description(self.font_description)
pango_layout.set_width(int(layout_width * Pango.SCALE))
pango_layout.set_wrap(Pango.WrapMode.WORD_CHAR)
wi, he = pango_layout.get_pixel_size()
rect_offset = y_offset + (int((2.0 * self.cell_area_source.cover_size) / 3.0))
rect_height = int(self.cell_area_source.cover_size / 3.0)
if he > rect_height:
pango_layout.set_ellipsize(Pango.EllipsizeMode.END)
pango_layout.set_height(int((self.cell_area_source.cover_size / 3.0) * Pango.SCALE))
wi, he = pango_layout.get_pixel_size()
# RECTANGLE
cr.set_source_rgba(0.0, 0.0, 0.0, alpha)
cr.set_line_width(0)
cr.rectangle(x_offset,
rect_offset,
cell_area.width - 1,
rect_height - 1)
cr.fill()
# DRAW FONT
cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
cr.move_to(x_offset,
y_offset
+ 2.0 * self.cell_area_source.cover_size / 3.0
+ (((self.cell_area_source.cover_size / 3.0) - he) / 2.0)
)
PangoCairo.show_layout(cr, pango_layout)
class AlbumArtCellArea(Gtk.CellAreaBox):
font_family = GObject.property(type=str, default="Sans")
font_size = GObject.property(type=int, default=10)
cover_size = GObject.property(type=int, default=0)
display_text_pos = GObject.property(type=bool, default=False)
display_text = GObject.property(type=bool, default=False)
add_shadow = GObject.property(type=bool, default=False)
hover_pixbuf = GObject.property(type=object, default=None)
text_alignment = GObject.property(type=int, default=1)
def __init__(self, ):
super(AlbumArtCellArea, self).__init__()
self.font_description = Pango.FontDescription.new()
self.font_description.set_family(self.font_family)
self.font_description.set_size(int(self.font_size * Pango.SCALE))
self._connect_properties()
# Add own cellrenderer
renderer_thumb = CellRendererThumb(self.font_description, self)
self.pack_start(renderer_thumb, False, False, False)
self.attribute_connect(renderer_thumb, "pixbuf", AlbumsModel.columns['pixbuf'])
self.attribute_connect(renderer_thumb, "markup", AlbumsModel.columns['markup'])
self.props.spacing = 2
def _connect_properties(self):
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
setting.bind(gs.PluginKey.COVER_SIZE, self, 'cover-size',
Gio.SettingsBindFlags.GET)
setting.bind(gs.PluginKey.DISPLAY_TEXT_POS, self, 'display-text-pos',
Gio.SettingsBindFlags.GET)
setting.bind(gs.PluginKey.DISPLAY_TEXT, self, 'display-text',
Gio.SettingsBindFlags.GET)
setting.bind(gs.PluginKey.ADD_SHADOW, self, 'add-shadow',
Gio.SettingsBindFlags.GET)
setting.bind(gs.PluginKey.TEXT_ALIGNMENT, self, 'text-alignment',
Gio.SettingsBindFlags.GET)
def calc_play_icon_offset(self, initial_x_offset, initial_y_offset):
'''
calculates the x & y offset for the play hover icon
:param initial_x_offset: current x_offset
:param initial_y_offset: current y_offset
:return: bool, x & y offset where bool is the full cover position
'''
full_cover = False
if not (self.display_text and self.display_text_pos == False):
y_offset = initial_y_offset + self.cover_size - 10
full_cover = True
else:
y_offset = initial_y_offset + (int((2.0 * self.cover_size) / 3.0))
x_offset = initial_x_offset
if self.add_shadow:
x_offset = initial_x_offset + 10
y_offset = y_offset - 10
return full_cover, x_offset, y_offset
class AlbumShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, cover_view):
super(AlbumShowingPolicy, self).__init__()
self._cover_view = cover_view # this will need to be reworked for all views
self._visible_paths = None
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._album_manager = album_manager
self._model = album_manager.model
self._connect_signals()
self._has_initialised = True
def _connect_signals(self):
self._cover_view.props.vadjustment.connect('value-changed',
self._viewport_changed)
self._model.connect('album-updated', self._album_updated)
self._model.connect('visual-updated', self._album_updated)
def _viewport_changed(self, *args):
visible_range = self._cover_view.get_visible_range()
if visible_range:
init, end = visible_range
# i have to use the tree iter instead of the path to iterate since
# for some reason path.next doesn't work with the filtermodel
tree_iter = self._model.store.get_iter(init)
self._visible_paths = []
while init and init != end:
self._visible_paths.append(init)
tree_iter = self._model.store.iter_next(tree_iter)
init = self._model.store.get_path(tree_iter)
self._visible_paths.append(end)
def _album_updated(self, model, album_path, album_iter):
# get the currently showing paths
if not self._visible_paths:
self._viewport_changed()
if (album_path and self._visible_paths) and album_path in self._visible_paths:
# if our path is on the viewport, emit the signal to update it
self._cover_view.queue_draw()
class CoverIconView(EnhancedIconView, AbstractView):
__gtype_name__ = "CoverIconView"
icon_spacing = GObject.property(type=int, default=0)
icon_padding = GObject.property(type=int, default=0)
icon_automatic = GObject.property(type=bool, default=True)
display_text_enabled = GObject.property(type=bool, default=False)
display_text_pos = GObject.property(type=bool, default=False)
name = 'coverview'
panedposition = PanedCollapsible.Paned.COLLAPSE
text_alignment = GObject.property(type=int, default=1)
__gsignals__ = {
'update-toolbar': (GObject.SIGNAL_RUN_LAST, None, ())
}
def __init__(self, *args, **kwargs):
super(CoverIconView, self).__init__(cell_area=AlbumArtCellArea(), *args, **kwargs)
self.gs = GSetting()
# custom text renderer
self._text_renderer = None
self.show_policy = AlbumShowingPolicy(self)
self.view = self
self._has_initialised = False
self._last_path = None
self._calc_motion_step = 0
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
self.object_column = AlbumsModel.columns['album']
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
self.view_name = "covers_view"
super(CoverIconView, self).initialise(source)
self.shell = source.shell
self.album_manager = source.album_manager
# setup iconview drag&drop support
# first drag and drop on the coverart view to receive coverart
self.enable_model_drag_dest([], Gdk.DragAction.COPY)
self.drag_dest_add_image_targets()
self.drag_dest_add_text_targets()
self.connect('drag-drop', self.on_drag_drop)
self.connect('drag-data-received',
self.on_drag_data_received)
self.source.paned.connect("expanded", self.bottom_expander_expanded_callback)
# lastly support drag-drop from coverart to devices/nautilus etc
self.connect('drag-begin', self.on_drag_begin)
self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
[], Gdk.DragAction.COPY)
# targets = Gtk.TargetList.new([Gtk.TargetEntry.new("application/x-rhythmbox-entry", 0, 0),
# Gtk.TargetEntry.new("text/uri-list", 0, 1) ])
targets = Gtk.TargetList.new([Gtk.TargetEntry.new("text/uri-list", 0, 0)])
# N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c
targets.add_uri_targets(1)
self.drag_source_set_target_list(targets)
self.connect("drag-data-get", self.on_drag_data_get)
# set the model to the view
# self.set_pixbuf_column(AlbumsModel.columns['pixbuf'])
self.set_model(self.album_manager.model.store)
# setup view to monitor mouse movements
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
self.hover_pixbufs = {
'button_play': None,
'button_play_hover': None,
'button_playpause': None,
'button_playpause_hover': None,
'button_queue': None,
'button_queue_hover': None,
}
for pixbuf_type in self.hover_pixbufs:
filename = 'img/' + pixbuf_type + '.png'
filename = rb.find_plugin_file(self.plugin, filename)
self.hover_pixbufs[pixbuf_type] = GdkPixbuf.Pixbuf.new_from_file_at_size(filename,
PLAY_SIZE_X,
PLAY_SIZE_Y)
self._connect_properties()
self._connect_signals()
self._activate_markup()
self.on_notify_icon_padding()
self.on_notify_icon_spacing()
def _connect_properties(self):
setting = self.gs.get_setting(self.gs.Path.PLUGIN)
setting.bind(
self.gs.PluginKey.ICON_SPACING,
self,
'icon_spacing',
Gio.SettingsBindFlags.GET)
setting.bind(
self.gs.PluginKey.ICON_PADDING,
self,
'icon_padding',
Gio.SettingsBindFlags.GET)
setting.bind(self.gs.PluginKey.DISPLAY_TEXT, self,
'display_text_enabled', Gio.SettingsBindFlags.GET)
setting.bind(self.gs.PluginKey.ICON_AUTOMATIC, self,
'icon_automatic', Gio.SettingsBindFlags.GET)
setting.bind(self.gs.PluginKey.DISPLAY_TEXT_POS, self,
'display-text-pos', Gio.SettingsBindFlags.GET)
setting.bind(self.gs.PluginKey.TEXT_ALIGNMENT, self,
'text-alignment', Gio.SettingsBindFlags.GET)
def _connect_signals(self):
self.connect("item-clicked", self.item_clicked_callback)
self.connect("selection-changed", self.selectionchanged_callback)
self.connect("item-activated", self.item_activated_callback)
self.connect('notify::icon-spacing',
self.on_notify_icon_spacing)
self.connect('notify::icon-padding',
self.on_notify_icon_padding)
self.connect('notify::display-text-enabled',
self._activate_markup)
self.connect('notify::display-text-pos',
self._activate_markup)
self.connect('notify::text-alignment',
self._create_and_configure_renderer)
self.connect("motion-notify-event", self.on_pointer_motion)
self.add_events(Gdk.EventMask.SCROLL_MASK)
self.connect("scroll-event", self.on_scroll_event)
def get_view_icon_name(self):
return "iconview.png"
def resize_icon(self, cover_size):
'''
Callback called when to resize the icon
[common to all views]
'''
self.set_item_width(cover_size)
def on_scroll_event(self, widget, scroll_event):
if scroll_event.state & Gdk.ModifierType.CONTROL_MASK:
settings = self.gs.get_setting(self.gs.Path.PLUGIN)
cover_size = settings[self.gs.PluginKey.COVER_SIZE]
if scroll_event.direction == Gdk.ScrollDirection.UP:
if cover_size <= 195:
settings[self.gs.PluginKey.COVER_SIZE] = cover_size + 5
elif scroll_event.direction == Gdk.ScrollDirection.DOWN:
if cover_size >= 55:
settings[self.gs.PluginKey.COVER_SIZE] = cover_size - 5
elif scroll_event.direction == Gdk.ScrollDirection.SMOOTH:
delta = scroll_event.delta_y
print (delta)
if delta < 0 and cover_size <= 195: # negative delta means scroll up
settings[self.gs.PluginKey.COVER_SIZE] = cover_size - int(delta * 5)
if delta > 0 and cover_size >= 55: # positive delta means scroll down
settings[self.gs.PluginKey.COVER_SIZE] = cover_size - int(delta * 5)
GLib.idle_add(self.queue_draw)
return True
def on_drag_drop(self, widget, context, x, y, time):
'''
Callback called when a drag operation finishes over the cover view
of the source. It decides if the dropped item can be processed as
an image to use as a cover.
'''
# stop the propagation of the signal (deactivates superclass callback)
widget.stop_emission_by_name('drag-drop')
# obtain the path of the icon over which the drag operation finished
path, pos = widget.get_dest_item_at_pos(x, y)
result = path is not None
if result:
target = self.drag_dest_find_target(context, None)
widget.drag_get_data(context, target, time)
return result
def on_drag_data_received(self, widget, drag_context, x, y, data, info,
time):
'''
Callback called when the drag source has prepared the data (pixbuf)
for us to use.
'''
# stop the propagation of the signal (deactivates superclass callback)
widget.stop_emission_by_name('drag-data-received')
# get the album and the info and ask the loader to update the cover
path, pos = widget.get_dest_item_at_pos(x, y)
album = widget.get_model()[path][2]
pixbuf = data.get_pixbuf()
if pixbuf:
self.album_manager.cover_man.update_cover(album, pixbuf)
else:
uri = data.get_text()
self.album_manager.cover_man.update_cover(album, uri=uri)
# call the context drag_finished to inform the source about it
drag_context.finish(True, False, time)
def on_drag_data_get(self, widget, drag_context, data, info, time):
'''
Callback called when the drag destination (playlist) has
requested what album (icon) has been dragged
'''
uris = []
for album in widget.get_selected_objects():
for track in album.get_tracks():
uris.append(track.location)
sel = data.set_uris(uris)
# stop the propagation of the signal (deactivates superclass callback)
widget.stop_emission_by_name('drag-data-get')
def on_drag_begin(self, widget, context):
'''
Callback called when the drag-drop from coverview has started
Changes the drag icon as appropriate
'''
album_number = len(widget.get_selected_objects())
if album_number == 1:
item = Gtk.STOCK_DND
else:
item = Gtk.STOCK_DND_MULTIPLE
widget.drag_source_set_icon_stock(item)
widget.stop_emission_by_name('drag-begin')
def _cover_play_hotspot(self, path, in_vacinity=False):
if path:
valid, rect = self.get_cell_rect(path, None) # rect of widget coords
cursor_x, cursor_y = self.get_pointer() # returns widget coords
c_x = cursor_x - rect.x - (self.icon_padding / 2) - (self.icon_spacing / 2)
c_y = cursor_y - rect.y - (self.icon_padding / 2) - (self.icon_spacing / 2)
sizing = (rect.width / 3) if in_vacinity else 0
full, x_offset, y_offset = self.props.cell_area.calc_play_icon_offset(0, 0)
if full and c_y > y_offset:
return False
y_offset = y_offset - PLAY_SIZE_Y
if (y_offset - PLAY_SIZE_Y) < 0:
return False
if c_x < (PLAY_SIZE_X + sizing + x_offset) and \
c_y < (PLAY_SIZE_Y + sizing + y_offset) and \
c_x > x_offset and \
c_y > (y_offset - sizing):
return True
# c_y 0 value at top - largest at bottom of the cover
return False
def on_pointer_motion(self, widget, event):
self._current_mouse_x = event.x
self._current_mouse_y = event.y
if self._calc_motion_step == 0:
self._calc_motion_step = 1
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100,
self._calculate_hotspot)
else:
path = self.get_path_at_pos(self._current_mouse_x,
self._current_mouse_y)
if not self._last_path or self._last_path != path:
self._display_icon(None, self._last_path)
def _display_icon(self, icon, path):
self.props.cell_area.hover_pixbuf = icon
if path and self.props.window:
valid, rect = self.get_cell_rect(path, None)
self.props.window.invalidate_rect(rect, True)
self.queue_draw()
def _calculate_hotspot(self, *args):
path = self.get_path_at_pos(self._current_mouse_x,
self._current_mouse_y)
# if the current path was not the same as the last path then
# reset the counter
if not self._last_path or self._last_path != path:
self._display_icon(None, self._last_path)
self._last_path = path
self._calc_motion_step = 0
return False
self._calc_motion_step = self._calc_motion_step + 1
# if havent yet reached the requisite number of steps then
# let the thread roll to the next increment
if self._calc_motion_step < 8:
return True
if not self._cover_play_hotspot(path, in_vacinity=True):
# we are not near the hot-spot so decrement the counter
# hoping next time around we are near
self._calc_motion_step = self._calc_motion_step - 1
self._display_icon(None, self._last_path)
return True
# from here on in, we are going to display a hotspot icon
# so lets decide which one
(_, playing) = self.shell.props.shell_player.get_playing()
calc_path = -1
if playing:
entry = self.shell.props.shell_player.get_playing_entry()
album = self.album_manager.model.get_from_dbentry(entry)
calc_path = self.album_manager.model.get_path(album)
if playing and calc_path == path:
icon = 'button_playpause'
elif playing:
icon = 'button_queue'
else:
icon = 'button_play'
# now we've got the icon - lets double check that we are
# actually hovering exactly on the hotspot because the icon will visually change
exact_hotspot = self._cover_play_hotspot(path)
if exact_hotspot:
icon = icon + '_hover'
hover = self.hover_pixbufs[icon]
self._display_icon(hover, path)
self._calc_motion_step = self._calc_motion_step - 1
return True
def item_clicked_callback(self, iconview, event, path):
'''
Callback called when the user clicks somewhere on the cover_view.
Along with source "show_hide_pane", takes care of showing/hiding the bottom
pane after a second click on a selected album.
'''
# first test if we've clicked on the cover-play icon
if self._cover_play_hotspot(path):
(_, playing) = self.shell.props.shell_player.get_playing()
# first see if anything is playing...
if playing:
entry = self.shell.props.shell_player.get_playing_entry()
album = self.album_manager.model.get_from_dbentry(entry)
# if the current playing entry corresponds to the album
# we are hovering over then we are requesting to pause
if self.album_manager.model.get_from_path(path) == album:
self._last_path = path
self.shell.props.shell_player.pause()
self.on_pointer_motion(self, event)
return
# this must be a new album so we are asking just
# to play this new album ... just need a short interval
# for the selection event to kick in first
def delay(*args):
if playing: # if we are playing then queue up the next album
self.source.queue_selected_album(None, self.source.favourites)
album = self.get_selected_objects()[0]
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
message = gettext.gettext('Album has added to list of playing albums')
self.display_notification(album.name,
message,
album.cover.original)
else: # otherwise just play it
self._last_path = path
self.source.play_selected_album(self.source.favourites)
icon = 'button_play_hover'
self.props.cell_area.hover_pixbuf = \
self.hover_pixbufs[icon]
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
delay, None)
return
# to expand the entry view
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
shift = event.state & Gdk.ModifierType.SHIFT_MASK
if self.icon_automatic:
self.source.click_count += 1 if not ctrl and not shift else 0
if self.source.click_count == 1:
album = self.album_manager.model.get_from_path(path) \
if path else None
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
self.source.show_hide_pane, album)
def item_activated_callback(self, iconview, path):
'''
Callback called when the cover view is double clicked or space-bar
is pressed. It plays the selected album
'''
self.source.play_selected_album(self.source.favourites)
return True
def on_notify_icon_padding(self, *args):
'''
Callback called when the icon-padding gsetting value is changed
'''
self.set_item_padding(self.icon_padding)
def on_notify_icon_spacing(self, *args):
'''
Callback called when the icon-spacing gsetting value is changed
'''
self.set_row_spacing(self.icon_spacing)
self.set_column_spacing(self.icon_spacing)
def _create_and_configure_renderer(self, *args):
if not self._text_renderer:
# Add own cellrenderer
self._text_renderer = Gtk.CellRendererText()
self._text_renderer.props.alignment = self.text_alignment
self._text_renderer.props.wrap_mode = Pango.WrapMode.WORD
if self.text_alignment == 1:
self._text_renderer.props.xalign = 0.5
elif self.text_alignment == 0:
self._text_renderer.props.xalign = 0
else:
self._text_renderer.props.xalign = 1
self._text_renderer.props.yalign = 0
self._text_renderer.props.width = \
self.album_manager.cover_man.cover_size
self._text_renderer.props.wrap_width = \
self.album_manager.cover_man.cover_size
def _activate_markup(self, *args):
'''
Utility method to activate/deactivate the markup text on the
cover view.
'''
if self.display_text_enabled and self.display_text_pos:
if not self._text_renderer:
# create and configure the custom cell renderer
self._create_and_configure_renderer()
# set the renderer
self.pack_end(self._text_renderer, False)
self.add_attribute(self._text_renderer,
'markup', AlbumsModel.columns['markup'])
elif self._text_renderer:
# remove the cell renderer
self.props.cell_area.remove(self._text_renderer)
if self.display_text_enabled:
self.set_tooltip_column(-1) # turnoff tooltips
else:
self.set_tooltip_column(AlbumsModel.columns['tooltip'])
def bottom_expander_expanded_callback(self, paned, expand):
'''
Callback connected to expanded signal of the paned GtkExpander
'''
if expand:
# accommodate the viewport if there's an album selected
if self.source.last_selected_album:
def scroll_to_album(*args):
# accommodate the viewport if there's an album selected
path = self.album_manager.model.get_path(
self.source.last_selected_album)
self.scroll_to_path(path, False, 0, 0)
return False
Gdk.threads_add_idle(GObject.PRIORITY_DEFAULT_IDLE,
scroll_to_album, None)
def switch_to_view(self, source, album):
self.initialise(source)
self.show_policy.initialise(source.album_manager)
self.scroll_to_album(album)
def grab_focus(self):
super(EnhancedIconView, self).grab_focus()
+863 -110
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+363
Ver Arquivo
@@ -0,0 +1,363 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of thie GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import shutil
import os
import sys
import subprocess
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import RB
from gi.repository import Gdk
from gi.repository import Peas
from gi.repository import Gst
from coverart_utils import NaturalString
import rb
import coverart_rb3compat as rb3compat
class CoverArtExport(GObject.Object):
'''
This class provides for various export routines
'''
TARGET_BITRATE = 128
def __init__(self, plugin, shell, album_manager):
self.plugin = plugin
self.shell = shell
self.album_manager = album_manager
self._gstreamer_has_initialised = False
self.has_opened_previously = False
self._values = {}
def is_search_plugin_enabled(self):
peas = Peas.Engine.get_default()
loaded_plugins = peas.get_loaded_plugins()
result = False
if 'coverart_search_providers' in loaded_plugins:
info = peas.get_plugin_info('coverart_search_providers')
version = info.get_version()
if NaturalString(version) >= "0.9":
result = True
return result
def embed_albums(self, selected_albums):
'''
method to export and embed coverart to chosen albums
:selected_albums: `Album` - array of albums
'''
self._initialise_gstreamer()
from coverart_search_tracks import CoverArtTracks
search_tracks = CoverArtTracks()
playlist_manager = self.shell.props.playlist_manager
playlists_entries = playlist_manager.get_playlists()
ui = Gtk.Builder()
ui.add_from_file(rb.find_plugin_file(self.plugin,
'ui/coverart_exportembed.ui'))
ui.connect_signals(self)
embeddialog = ui.get_object('exportembeddialog')
embeddialog.set_transient_for(self.shell.props.window)
folderchooserbutton = ui.get_object('folderchooserbutton')
use_album_name_checkbutton = ui.get_object('use_album_name_checkbutton')
open_filemanager_checkbutton = ui.get_object('open_filemanager_checkbutton')
convert_checkbutton = ui.get_object('convert_checkbutton')
bitrate_spinbutton = ui.get_object('bitrate_spinbutton')
resize_checkbutton = ui.get_object('resize_checkbutton')
resize_spinbutton = ui.get_object('resize_spinbutton')
# predefine values if not previously opened the dialog
if self.has_opened_previously:
print (self._values)
if not self._values['toresize']:
resize_spinbutton.set_value(128)
else:
resize_spinbutton.set_value(self._values['resize'])
if not self._values['convert']:
bitrate_spinbutton.set_value(self.TARGET_BITRATE)
else:
bitrate_spinbutton.set_value(self._values['bitrate'])
folderchooserbutton.set_current_folder(self._values['final_folder_store'])
use_album_name_checkbutton.set_active(self._values['use_album_name'])
open_filemanager_checkbutton.set_active(self._values['open_filemanager'])
convert_checkbutton.set_active(self._values['convert'])
resize_checkbutton.set_active(self._values['toresize'])
else:
bitrate_spinbutton.set_value(self.TARGET_BITRATE)
resize_spinbutton.set_value(128)
downloads_dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOWNLOAD)
folderchooserbutton.set_current_folder(downloads_dir)
response = embeddialog.run()
if response != Gtk.ResponseType.OK:
embeddialog.destroy()
return
self.has_opened_previously = True
# ok pressed - now fetch values from the dialog
final_folder_store = folderchooserbutton.get_current_folder()
use_album_name = use_album_name_checkbutton.get_active()
open_filemanager = open_filemanager_checkbutton.get_active()
convert = convert_checkbutton.get_active()
bitrate = bitrate_spinbutton.get_value()
toresize = resize_checkbutton.get_active()
if toresize:
resize = int(resize_spinbutton.get_value())
else:
resize = -1
self._values['bitrate'] = bitrate
self._values['resize'] = resize
self._values['final_folder_store'] = final_folder_store
self._values['use_album_name'] = use_album_name
self._values['open_filemanager'] = open_filemanager
self._values['convert'] = convert
self._values['toresize'] = toresize
print (self._values)
embeddialog.destroy()
albums = {}
total = 0
for album in selected_albums:
albums[album] = album.get_tracks()
total = total + len(albums[album])
self._track_count = 1
def complete():
self.album_manager.progress = 1
if open_filemanager:
#code taken from http://stackoverflow.com/questions/1795111/is-there-a-cross-platform-way-to-open-a-file-browser-in-python
if sys.platform == 'win32':
import winreg
path = r('SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon')
for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE):
try:
with winreg.OpenKey(root, path) as k:
value, regtype = winreg.QueryValueEx(k, 'Shell')
except WindowsError:
pass
else:
if regtype in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
shell = value
break
else:
shell = 'Explorer.exe'
subprocess.Popen([shell, final_folder_store])
elif sys.platform == 'darwin':
subprocess.Popen(['open', final_folder_store])
else:
subprocess.Popen(['xdg-open', final_folder_store])
self._albumiter = iter(albums)
self._tracknumber = 0
self._album = next(self._albumiter)
def idle_call(data):
exit_idle = True
track = albums[self._album][self._tracknumber]
if not process_track(self._album, track):
exit_idle = False
self._tracknumber = self._tracknumber + 1
if self._tracknumber >= len(albums[self._album]):
try:
self._tracknumber = 0
self._album = next(self._albumiter)
except StopIteration:
exit_idle = False
if not exit_idle:
complete()
return exit_idle
def process_track(album, track):
self.album_manager.progress = self._track_count / total
self._track_count = self._track_count + 1
key = album.create_ext_db_key()
finalPath = rb3compat.unquote(track.location)[7:]
album_name = RB.search_fold(album.name)
if use_album_name:
folder_store = final_folder_store + '/' + album_name
else:
folder_store = final_folder_store
try:
if not os.path.exists(folder_store):
os.makedirs(folder_store)
if convert:
self.convert_to_mp3(finalPath, folder_store, bitrate)
finalPath = self._calc_mp3_filename(finalPath, folder_store)
print(finalPath)
else:
shutil.copy(finalPath, folder_store)
except IOError as err:
print(err.args[0])
return False
dest = os.path.join(folder_store, os.path.basename(finalPath))
desturi = 'file://' + rb3compat.pathname2url(dest)
return search_tracks.embed(desturi, key, resize)
data = None
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, idle_call, data)
def _initialise_gstreamer(self):
if self._gstreamer_has_initialised:
return
self._gstreamer_has_initialised = True
Gst.init(None)
def on_new_decoded_pad(dbin, pad):
decode = pad.get_parent()
pipeline = decode.get_parent()
convert = pipeline.get_by_name('convert')
decode.link(convert)
# we are going to mimic the following
# gst-launch-1.0 filesrc location="02 - ABBA - Knowing Me, Knowing You.ogg" !
# decodebin ! audioconvert ! audioresample ! lamemp3enc target=bitrate bitrate=128 !
# xingmux ! id3v2mux ! filesink location="mytrack.mp3"
converter = Gst.Pipeline.new('converter')
source = Gst.ElementFactory.make('filesrc', None)
decoder = Gst.ElementFactory.make('decodebin', 'decoder')
convert = Gst.ElementFactory.make('audioconvert', 'convert')
sample = Gst.ElementFactory.make('audioresample', 'sample')
encoder = Gst.ElementFactory.make('lamemp3enc', 'encoder')
encoder.set_property('target', 'bitrate')
encoder.set_property('bitrate', self.TARGET_BITRATE)
xing = Gst.ElementFactory.make('xingmux', 'xing') # needed to make bitrate more accurate
mux = Gst.ElementFactory.make('id3v2mux', 'mux')
if not mux:
# use id3mux where not available
mux = Gst.ElementFactory.make('id3mux', 'mux')
sink = Gst.ElementFactory.make('filesink', 'sink')
converter.add(source)
converter.add(decoder)
converter.add(convert)
converter.add(sample)
converter.add(encoder)
converter.add(xing)
converter.add(mux)
converter.add(sink)
Gst.Element.link(source, decoder)
#note - a decodebin cannot be linked at compile since
#it doesnt have source-pads (http://stackoverflow.com/questions/2993777/gstreamer-of-pythons-gst-linkerror-problem)
decoder.connect("pad-added", on_new_decoded_pad)
Gst.Element.link(convert, sample)
Gst.Element.link(sample, encoder)
Gst.Element.link(encoder, xing)
Gst.Element.link(xing, mux)
Gst.Element.link(mux, sink)
self.converter = converter
self.source = source
self.sink = sink
self.encoder = encoder
def _calc_mp3_filename(self, filename, save_folder):
finalname = os.path.basename(filename)
finalname = finalname.rsplit('.')[0] + ".mp3"
return save_folder + "/" + finalname
def convert_to_mp3(self, filename, save_folder, bitrate):
self.source.set_property('location', filename)
self.sink.set_property('location', self._calc_mp3_filename(filename, save_folder))
print(bitrate)
if bitrate < 32:
bitrate = self.TARGET_BITRATE
self.encoder.set_property('bitrate', int(bitrate))
print(bitrate)
# Start playing
ret = self.converter.set_state(Gst.State.PLAYING)
if ret == Gst.StateChangeReturn.FAILURE:
print("Unable to set the pipeline to the playing state.", sys.stderr)
exit(-1)
# Wait until error or EOS
bus = self.converter.get_bus()
try:
msg = bus.timed_pop_filtered(
Gst.CLOCK_TIME_NONE, Gst.MessageType.ERROR | Gst.MessageType.EOS)
except:
# for some reason in ubuntu 12.04 Gst.CLOCK_TIME_NONE fails
msg = bus.timed_pop_filtered(
18446744073709551615, Gst.MessageType.ERROR | Gst.MessageType.EOS)
# Parse message
if (msg):
if msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
print("Error received from element %s: %s" % (
msg.src.get_name(), err), sys.stderr)
print("Debugging information: %s" % debug, sys.stderr)
elif msg.type == Gst.MessageType.EOS:
print("End-Of-Stream reached.")
else:
print("Unexpected message received.", sys.stderr)
# Free resources
self.converter.set_state(Gst.State.NULL)
+244
Ver Arquivo
@@ -0,0 +1,244 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of thie GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import Peas
from gi.repository import GObject
import lxml.etree as ET
import rb
from coverart_rb3compat import ActionGroup
from coverart_rb3compat import ApplicationShell
from coverart_utils import CaseInsensitiveDict
class ExternalPlugin(GObject.Object):
'''
class for all supported ExternalPlugins
'''
def __init__(self, **kargs):
super(ExternalPlugin, self).__init__(**kargs)
# dict of attributes associated with the external plugin
self.attributes = {}
self.attributes['is_album_menu'] = False
self.attributes['new_menu_name'] = ''
self.attributes['action_type'] = ''
self.attributes['action_group_name'] = ''
def appendattribute(self, key, val):
'''
append another attribute to the dict
:param key: `str` name of attribute
:param val: `str` value of attribute
'''
if key == 'is_album_menu':
if val == 'yes':
self.attributes[key] = True
else:
self.attributes[key] = False
else:
self.attributes[key] = val
def is_activated(self):
'''
method to test whether the plugin is actually loaded. Returns a bool
'''
peas = Peas.Engine.get_default()
loaded_plugins = peas.get_loaded_plugins()
if self.attributes['plugin_name'] in CaseInsensitiveDict(loaded_plugins):
print("found %s" % self.attributes['plugin_name'])
return True
print("search for %s" % self.attributes['plugin_name'])
print(loaded_plugins)
return False
def create_menu_item(self, menubar, section_name, at_position,
save_actiongroup, save_menu, for_album=False):
'''
method to create the menu item appropriate to the plugin.
A plugin can have many menu items - all menuitems are enclosed
in a section.
:param menubar: `str` name for the GtkMenu - ignored for RB2.99
:param section_name: `str` unique name of the section holding the menu items
:param at_position: `int` position within the GtkMenu to create menu - ignored for RB2.99
:param save_actiongroup: `ActionGroup` container for all menu-item Actions
:param save_menu: `Menu` whole popupmenu including sub-menus
:param for_album: `bool` create the menu for the album - if not given
then its assumed the menu item is appropriate just for tracks
'''
if for_album and not self.attributes['is_album_menu']:
return False
if not self.is_activated():
return False
action = ApplicationShell(save_menu.shell).lookup_action(self.attributes['action_group_name'],
self.attributes['action_name'],
self.attributes['action_type'])
if action:
self.attributes['action'] = action
if self.attributes['new_menu_name'] != '':
self.attributes['label'] = self.attributes['new_menu_name']
else:
self.attributes['label'] = action.label
# self.attributes['sensitive']=action.get_sensitive()
else:
print("action not found")
print(self.attributes)
return False
action = save_actiongroup.add_action(func=self.menuitem_callback,
action_name=self.attributes['action_name'], album=for_album,
shell=save_menu.shell, label=self.attributes['label'])
new_menu_item = save_menu.insert_menu_item(menubar, section_name,
at_position, action)
return new_menu_item
def do_deactivate(self):
pass
def set_entry_view_selected_entries(self, shell):
'''
method called just before the external plugin action is activated
Normally only called for album menus to mimic selecting all the
EntryView rows
'''
page = shell.props.selected_page
if not hasattr(page, "get_entry_view"):
return
page.get_entry_view().select_all()
def activate(self, shell):
'''
method called to initiate the external plugin action
the action is defined by defining the action_group_name, action_name and action_type
'''
action = ApplicationShell(shell).lookup_action(self.attributes['action_group_name'],
self.attributes['action_name'],
self.attributes['action_type'])
if action:
action.activate()
def menuitem_callback(self, action, param, args):
'''
method called when a menu-item is clicked. Basically, an Action
is activated by the user
:param action: `Gio.SimpleAction` or `Gtk.Action`
:param param: Not used
:param args: dict associated with the action
'''
for_album = args['album']
shell = args['shell']
if for_album:
self.set_entry_view_selected_entries(shell)
self.attributes['action'].activate()
class CreateExternalPluginMenu(GObject.Object):
'''
This is the key class called to initialise all supported plugins
:param section_name: `str` unique name of the section holding the menu items
:param at_position: `int` position within the GtkMenu to create menu - ignored for RB2.99
:param popup: `Menu` whole popupmenu including sub-menus
'''
def __init__(self, section_name, at_position, popup, **kargs):
super(CreateExternalPluginMenu, self).__init__(**kargs)
self.menu = popup
self.section_name = section_name
self.at_position = at_position
self._actiongroup = ActionGroup(popup.shell, section_name + '_externalplugins')
# all supported plugins will be defined in the following array by parsing
# the plugins XML file for the definition.
self.supported_plugins = []
extplugins = rb.find_plugin_file(popup.plugin, 'ui/coverart_external_plugins.xml')
root = ET.parse(open(extplugins)).getroot()
base = 'rb3/plugin'
for elem in root.xpath(base):
pluginname = elem.attrib['name']
basemenu = base + "[@name='" + pluginname + "']/menu"
for menuelem in root.xpath(basemenu):
ext = ExternalPlugin()
ext.appendattribute('plugin_name', pluginname)
label = menuelem.attrib['label']
if label != "":
ext.appendattribute('new_menu_name', label)
baseattrib = basemenu + "[@label='" + label + "']/attribute"
else:
baseattrib = basemenu + "/attribute"
for attribelem in root.xpath(baseattrib):
key = attribelem.attrib['name']
val = attribelem.text
ext.appendattribute(key, val)
self.supported_plugins.append(ext)
def create_menu(self, menu_name, for_album=False):
'''
method to create the menu items for all supported plugins
:param menu_name: `str` unique name (GtkMenu) id for the menu to create
:for_album: `bool` - create a menu applicable for Albums
by default a menu is assumed to be applicable to a track in an
EntryView
'''
self.menu_name = menu_name
self._actiongroup.remove_actions()
self.menu.remove_menu_items(self.menu_name, self.section_name)
items_added = False
for plugin in self.supported_plugins:
new_menu_item = plugin.create_menu_item(self.menu_name, self.section_name,
self.at_position, self._actiongroup, self.menu, for_album)
if (not items_added) and new_menu_item:
items_added = True
if items_added:
self.menu.insert_separator(self.menu_name, self.at_position)
+80
Ver Arquivo
@@ -0,0 +1,80 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import GObject
from gi.repository import GLib
from coverart_widgets import AbstractView
class ListShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, list_view):
super(ListShowingPolicy, self).__init__()
self.counter = 0
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._has_initialised = True
class ListView(AbstractView):
__gtype_name__ = "ListView"
name = 'listview'
use_plugin_window = False
def __init__(self):
super(ListView, self).__init__()
self.view = self
self._has_initialised = False
self.show_policy = ListShowingPolicy(self)
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
self.view_name = "list_view"
super(ListView, self).initialise(source)
# self.album_manager = source.album_manager
self.shell = source.shell
def switch_to_view(self, source, album):
self.initialise(source)
GLib.idle_add(self.shell.props.display_page_tree.select,
self.shell.props.library_source)
def get_selected_objects(self):
'''
finds what has been selected
returns an array of `Album`
'''
return []
+258
Ver Arquivo
@@ -0,0 +1,258 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
# define plugin
from gi.repository import Gtk
from gi.repository import RB
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gdk
from coverart_rb3compat import Menu
from coverart_external_plugins import CreateExternalPluginMenu
from coverart_entryview import CoverArtEntryView
from coverart_rb3compat import ActionGroup
from coverart_rb3compat import ApplicationShell
from coverart_browser_prefs import CoverLocale
from coverart_widgets import PressButton
from coverart_utils import create_button_image
import xml.etree.ElementTree as ET
import rb
import os
class CoverArtPlayEntryView(CoverArtEntryView):
__hash__ = GObject.__hash__
def __init__(self, shell, source):
'''
Initializes the entryview.
'''
super(CoverArtPlayEntryView, self).__init__(shell, source)
def define_menu(self):
popup = Menu(self.plugin, self.shell)
popup.load_from_file('N/A',
'ui/coverart_play_pop_rb3.ui')
signals = {
'remove_from_playlist_menu_item': self.remove_from_playlist_menu_item_callback
}
popup.connect_signals(signals)
popup.connect('pre-popup', self.pre_popup_menu_callback)
self.popup = popup
def pre_popup_menu_callback(self, *args):
'''
Callback when the popup menu is about to be displayed
'''
if not self.external_plugins:
self.external_plugins = \
CreateExternalPluginMenu("playlist_entry_view", 1, self.popup)
self.external_plugins.create_menu('play_popup_menu')
def remove_from_playlist_menu_item_callback(self, *args):
print("remove_from_playlist_menu_item_callback")
entries = self.get_selected_entries()
for entry in entries:
print(entry)
self.source.source_query_model.remove_entry(entry)
def do_show_popup(self, over_entry):
if over_entry:
print("CoverArtBrowser DEBUG - do_show_popup()")
self.popup.popup(self.source,
'play_popup_menu', 0, Gtk.get_current_event_time())
return over_entry
def play_track_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
selected = self.get_selected_entries()
entry = selected[0]
# Start the music
player = self.shell.props.shell_player
player.play_entry(entry, self.source)
print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
class CoverArtPlaySource(RB.BrowserSource):
def __init__(self, **kwargs):
'''
Initializes the source.
'''
super(CoverArtPlaySource, self).__init__(**kwargs)
#self.external_plugins = None
self.hasActivated = False
self.save_in_progress = False
self.save_interrupt = False
self.filename = RB.user_cache_dir() + "/coverart_browser/playlist.xml"
def do_selected(self):
'''
Called by Rhythmbox when the source is selected. It makes sure to
create the ui the first time the source is showed.
'''
print("CoverArtBrowser DEBUG - do_selected")
# first time of activation -> add graphical stuff
if not self.hasActivated:
self.do_impl_activate()
# indicate that the source was activated before
self.hasActivated = True
print("CoverArtBrowser DEBUG - end do_selected")
def do_impl_activate(self):
'''
Called by do_selected the first time the source is activated.
It creates all the source ui and connects the necessary signals for it
correct behavior.
'''
print('do_impl_activate')
self.plugin = self.props.plugin
self.shell = self.props.shell
player = self.shell.props.shell_player
player.set_playing_source(self)
player.set_selected_source(self)
# define a query model that we'll use for playing
self.source_query_model = self.plugin.source_query_model
grid = Gtk.Grid()
self.entryview = self.get_entry_view()
child = self.get_children()
print (child)
grid = child[0]
self.rbsourcetoolbar = grid.get_children()[1] # need to remember the reference to stop crashes when python cleans up unlinked objects
grid.remove(grid.get_children()[1])
self.get_entry_view().set_model(self.source_query_model)
'''
# enable sorting on the entryview
entryview.set_columns_clickable(True)
self.shell.props.library_source.get_entry_view().set_columns_clickable(
True)
'''
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
location = rb.find_plugin_file(self.plugin, 'ui/playsource-toolbar.ui')
ui = Gtk.Builder()
ui.set_translation_domain(cl.Locale.RB)
ui.add_from_file(location)
toolbar_menu = ui.get_object('playsource-toolbar')
app = self.shell.props.application
app.link_shared_menus(toolbar_menu)
self.toolbar = RB.ButtonBar.new(toolbar_menu, toolbar_menu)
self.toolbar.props.hexpand_set = False
grid.attach(self.toolbar, 0, 0, 1, 1)
grid.show_all()
appshell = ApplicationShell(self.shell)
action_group = ActionGroup(self.shell, 'PlaySourceActions')
action_group.add_action(func=self.clear_playsource,
action_name='playsource-clear', action_state=ActionGroup.STANDARD,
action_type='app')
action_group.add_action(func=self.shuffle_playsource,
action_name='playsource-shuffle', action_state=ActionGroup.STANDARD,
action_type='app')
appshell.insert_action_group(action_group)
# if the alternative-toolbar is loaded then lets connect to the toolbar-visibility signal
# to control our sources toolbar visibility
#if hasattr(self.shell, 'alternative_toolbar'):
# self.shell.alternative_toolbar.connect('toolbar-visibility', self._visibility)
self._load_model()
self.source_query_model.connect('row-inserted', self.save_changed_model)
self.source_query_model.connect('row-changed', self.save_changed_model)
self.source_query_model.connect('row-deleted', self.save_changed_model)
def _load_model(self):
if not os.path.isfile(self.filename):
return
parser = ET.XMLParser(encoding="utf-8")
tree = ET.parse(self.filename, parser=parser)
root = tree.getroot()
for child in root.findall('./entry/text'):
location = child.text
entry = self.shell.props.db.entry_lookup_by_location(location)
if entry:
self.source_query_model.add_entry(entry, -1)
self.props.query_model = self.source_query_model
def clear_playsource(self, *args):
for row in self.get_entry_view().props.model:
self.get_entry_view().props.model.remove_entry(row[0])
def shuffle_playsource(self, *args):
self.get_entry_view().props.model.shuffle_entries()
self._save_model()
def save_changed_model(self, *args):
if self.save_in_progress:
self.save_interrupt = True
return
self.save_in_progress = True
Gdk.threads_add_timeout_seconds(GLib.PRIORITY_DEFAULT_IDLE, 1, self._save_model, None)
def _save_model(self, *args):
if self.save_interrupt:
self.save_interrupt = False
return True
root = ET.Element('root')
element = ET.SubElement(root, 'entry')
for row in self.source_query_model:
location = row[0].get_string(RB.RhythmDBPropType.LOCATION)
subelement = ET.SubElement(element, 'text')
subelement.text = location
tree = ET.ElementTree(root)
tree.write(self.filename)
self.save_in_progress = False
return False
GObject.type_register(CoverArtPlayEntryView)
+377
Ver Arquivo
@@ -0,0 +1,377 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import urllib.parse
import json
import os
import random
from gi.repository import RB
from gi.repository import Gtk
from coverart_utils import idle_iterator
import rb
LOAD_CHUNK = 50
class WebPlaylist(object):
MAX_TRACKS_TO_ADD = 3 # number of tracks to add to a source for each fetch
MIN_TRACKS_TO_FETCH = 5 # number of tracks in source before a fetch will be required
TOTAL_TRACKS_REMEMBERED = 25 # total number of tracks for all artists before a fetch is allowed
MAX_TRACKS_PER_ARTIST = 3 # number of tracks allowed to be remembered per artist
def __init__(self, shell, source, playlist_name):
self.shell = shell
# lets fill up the queue with artists
self.candidate_artist = {}
self.shell.props.shell_player.connect('playing-song-changed', self.playing_song_changed)
self.source = source
self.search_entry = None
self.playlist_started = False
self.played_artist = {}
self.tracks_not_played = 0
# cache for artist information: valid for a month, can be used indefinitely
# if offline, discarded if unused for six months
self.info_cache = rb.URLCache(name=playlist_name,
path=os.path.join('coverart_browser', playlist_name),
refresh=30,
discard=180)
self.info_cache.clean()
def playing_song_changed(self, player, entry):
if not entry:
return
if player.get_playing_source() != self.source:
self.playlist_started = False
self.played_artist.clear()
self.tracks_not_played = 0
if self.playlist_started and len(self.source.props.query_model) < self.MIN_TRACKS_TO_FETCH:
self.start(entry)
def start(self, seed_entry, reinitialise=False):
artist = seed_entry.get_string(RB.RhythmDBPropType.ARTIST)
if reinitialise:
self.played_artist.clear()
self.tracks_not_played = 0
self.playlist_started = False
player = self.shell.props.shell_player
_, is_playing = player.get_playing()
if is_playing:
player.stop()
for row in self.source.props.query_model:
self.source.props.query_model.remove_entry(row[0])
if self.tracks_not_played > self.TOTAL_TRACKS_REMEMBERED:
print(("we have plenty of tracks to play yet - no need to fetch more %d", self.tracks_not_played))
self.add_tracks_to_source()
return
search_artist = urllib.parse.quote(artist.encode("utf8"))
if search_artist in self.played_artist:
print("we have already searched for that artist")
return
self.search_entry = seed_entry
self.played_artist[search_artist] = True
self.playlist_started = True
self._running = False
self._start_process()
def _start_process(self):
if not self._running:
self._running = True
self.search_website()
def search_website(self):
pass
def _clear_next(self):
self.search_artists = ""
self._running = False
@idle_iterator
def _load_albums(self):
def process(row, data):
entry = data['model'][row.path][0]
lookup = entry.get_string(RB.RhythmDBPropType.ARTIST_FOLDED)
lookup_title = entry.get_string(RB.RhythmDBPropType.TITLE_FOLDED)
if lookup in self.artist and \
lookup_title in \
self.artist[lookup]:
if lookup not in self.candidate_artist:
self.candidate_artist[lookup] = []
# N.B. every artist has an array of dicts with a known format of track & add-to-source elements
# the following extracts the track-title and add-to-source to form a dict of track-title and a value
# of the add-to-source
d = dict((i['track-title'], i['add-to-source']) for i in self.candidate_artist[lookup])
if len(d) < self.MAX_TRACKS_PER_ARTIST and lookup_title not in d:
# we only append a max of three tracks to each artist
self.candidate_artist[lookup].append({
'track': entry,
'add-to-source': False,
'track-title': lookup_title})
self.tracks_not_played = self.tracks_not_played + 1
def after(data):
# update the progress
pass
def error(exception):
print(('Error processing entries: ' + str(exception)))
def finish(data):
self.add_tracks_to_source()
self._clear_next()
return LOAD_CHUNK, process, after, error, finish
def display_error_message(self):
dialog = Gtk.MessageDialog(None,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.INFO,
Gtk.ButtonsType.OK,
_("No matching tracks have been found"))
dialog.run()
dialog.destroy()
def add_tracks_to_source(self):
entries = []
for artist in self.candidate_artist:
d = dict((i['track'], (self.candidate_artist[artist].index(i),
i['add-to-source'],
artist)) for i in self.candidate_artist[artist])
for entry, elements in d.items():
element_pos, add_to_source, artist = elements
if not add_to_source:
entries.append({entry: elements})
random.shuffle(entries)
count = 0
for row in entries:
print(row)
entry, elements = list(row.items())[0]
element_pos, add_to_source, artist = elements
self.source.add_entry(entry, -1)
self.candidate_artist[artist][element_pos]['add-to-source'] = True
count = count + 1
self.tracks_not_played = self.tracks_not_played - 1
if count == self.MAX_TRACKS_TO_ADD:
break
player = self.shell.props.shell_player
_, is_playing = player.get_playing()
if len(self.source.props.query_model) > 0 and not is_playing:
player.play_entry(self.source.props.query_model[0][0], self.source)
class LastFMTrackPlaylist(WebPlaylist):
def __init__(self, shell, source):
WebPlaylist.__init__(self, shell, source, "lastfm_trackplaylist")
def search_website(self):
# unless already cached - directly fetch from lastfm similar track information
apikey = "844353bce568b93accd9ca47674d6c3e"
url = "http://ws.audioscrobbler.com/2.0/?method=track.getsimilar&api_key={0}&artist={1}&track={2}&format=json"
artist = self.search_entry.get_string(RB.RhythmDBPropType.ARTIST)
title = self.search_entry.get_string(RB.RhythmDBPropType.TITLE)
artist = urllib.parse.quote(artist.encode("utf8"))
title = urllib.parse.quote(title.encode("utf8"))
formatted_url = url.format(urllib.parse.quote(apikey),
artist,
title)
print(formatted_url)
cachekey = "artist:%s:title:%s" % (artist, title)
self.info_cache.fetch(cachekey, formatted_url, self.similar_info_cb, None)
def similar_info_cb(self, data, _):
if not data:
print("nothing to do")
self.display_error_message()
self._clear_next()
return
similar = json.loads(data.decode('utf-8'))
# loop through the response and find all titles for the artists returned
self.artist = {}
if 'similartracks' not in similar:
print("No matching data returned from LastFM")
self.display_error_message()
self._clear_next()
return
for song in similar['similartracks']['track']:
name = RB.search_fold(song['artist']['name'])
if name not in self.artist:
self.artist[name] = []
self.artist[name].append(RB.search_fold(song['name']))
if len(self.artist) == 0:
print("no artists returned")
self._clear_next()
return
# loop through every track - see if the track contains the artist & title
# if yes then this is a candidate similar track to remember
query_model = self.shell.props.library_source.props.base_query_model
self._load_albums(iter(query_model), albums={}, model=query_model,
total=len(query_model), progress=0.)
class EchoNestPlaylist(WebPlaylist):
def __init__(self, shell, source):
WebPlaylist.__init__(self, shell, source, "echonest_playlist")
def search_website(self):
# unless already cached - directly fetch from echonest similar artist information
apikey = "N685TONJGZSHBDZMP"
url = "http://developer.echonest.com/api/v4/playlist/basic?api_key={0}&artist={1}&format=json&results=100&type=artist-radio&limited_interactivity=true"
artist = self.search_entry.get_string(RB.RhythmDBPropType.ARTIST)
artist = urllib.parse.quote(artist.encode("utf8"))
formatted_url = url.format(urllib.parse.quote(apikey),
artist)
print(formatted_url)
cachekey = "artist:%s" % artist
self.info_cache.fetch(cachekey, formatted_url, self.similar_info_cb, None)
def similar_info_cb(self, data, _):
if not data:
print("nothing to do")
self.display_error_message()
self._clear_next()
return
similar = json.loads(data.decode('utf-8'))
# loop through the response and find all titles for the artists returned
self.artist = {}
if 'songs' not in similar['response']:
print("No matching data returned from EchoNest")
self.display_error_message()
self._clear_next()
return
for song in similar['response']['songs']:
name = RB.search_fold(song['artist_name'])
if name not in self.artist:
self.artist[name] = []
self.artist[name].append(RB.search_fold(song['title']))
if len(self.artist) == 0:
print("no artists returned")
self._clear_next()
return
# loop through every track - see if the track contains the artist & title
# if yes then this is a candidate similar track to remember
query_model = self.shell.props.library_source.props.base_query_model
self._load_albums(iter(query_model), albums={}, model=query_model,
total=len(query_model), progress=0.)
class EchoNestGenrePlaylist(WebPlaylist):
def __init__(self, shell, source):
WebPlaylist.__init__(self, shell, source, "echonest_genre_playlist")
def search_website(self):
# unless already cached - directly fetch from echonest similar artist information
apikey = "N685TONJGZSHBDZMP"
url = "http://developer.echonest.com/api/v4/playlist/basic?api_key={0}&genre={1}&format=json&results=100&type=genre-radio&limited_interactivity=true"
genre = self.search_entry.get_string(RB.RhythmDBPropType.GENRE).lower()
genre = urllib.parse.quote(genre.encode("utf8"))
formatted_url = url.format(urllib.parse.quote(apikey),
genre)
print(formatted_url)
cachekey = "genre:%s" % genre
self.info_cache.fetch(cachekey, formatted_url, self.similar_info_cb, None)
def similar_info_cb(self, data, _):
if not data:
print("nothing to do")
self.display_error_message()
self._clear_next()
return
similar = json.loads(data.decode('utf-8'))
# loop through the response and find all titles for the artists returned
self.artist = {}
if 'songs' not in similar['response']:
print("No matching data returned from EchoNest")
self.display_error_message()
self._clear_next()
return
for song in similar['response']['songs']:
name = RB.search_fold(song['artist_name'])
if name not in self.artist:
self.artist[name] = []
self.artist[name].append(RB.search_fold(song['title']))
if len(self.artist) == 0:
print("no artists returned")
self._clear_next()
return
# loop through every track - see if the track contains the artist & title
# if yes then this is a candidate similar track to remember
query_model = self.shell.props.library_source.props.base_query_model
self._load_albums(iter(query_model), albums={}, model=query_model,
total=len(query_model), progress=0.)
+80
Ver Arquivo
@@ -0,0 +1,80 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import GObject
from gi.repository import GLib
from coverart_widgets import AbstractView
class PlaySourceShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, list_view):
super(PlaySourceShowingPolicy, self).__init__()
self.counter = 0
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._has_initialised = True
class PlaySourceView(AbstractView):
__gtype_name__ = "PlaySourceView"
name = 'playsourceview'
use_plugin_window = False
def __init__(self):
super(PlaySourceView, self).__init__()
self.view = self
self._has_initialised = False
self.show_policy = PlaySourceShowingPolicy(self)
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
self.view_name = "playsource_view"
super(PlaySourceView, self).initialise(source)
# self.album_manager = source.album_manager
self.shell = source.shell
def switch_to_view(self, source, album):
self.initialise(source)
GLib.idle_add(self.shell.props.display_page_tree.select,
source.plugin.playlist_source)
def get_selected_objects(self):
'''
finds what has been selected
returns an array of `Album`
'''
return []
+80
Ver Arquivo
@@ -0,0 +1,80 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import GObject
from gi.repository import GLib
from coverart_widgets import AbstractView
class QueueShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, list_view):
super(QueueShowingPolicy, self).__init__()
self.counter = 0
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._has_initialised = True
class QueueView(AbstractView):
__gtype_name__ = "QueueView"
name = 'queueview'
use_plugin_window = False
def __init__(self):
super(QueueView, self).__init__()
self.view = self
self._has_initialised = False
self.show_policy = QueueShowingPolicy(self)
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
self.view_name = "queue_view"
super(QueueView, self).initialise(source)
# self.album_manager = source.album_manager
self.shell = source.shell
def switch_to_view(self, source, album):
self.initialise(source)
GLib.idle_add(self.shell.props.display_page_tree.select,
self.shell.props.queue_source)
def get_selected_objects(self):
'''
finds what has been selected
returns an array of `Album`
'''
return []
+887
Ver Arquivo
@@ -0,0 +1,887 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# IMPORTANT - WHILST THIS MODULE IS USED BY SEVERAL OTHER PLUGINS
# THE MASTER AND MOST UP-TO-DATE IS FOUND IN THE COVERART BROWSER
# PLUGIN - https://github.com/fossfreedom/coverart-browser
# PLEASE SUBMIT CHANGES BACK TO HELP EXPAND THIS API
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import sys
import xml.etree.ElementTree as ET
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import RB
import rb
def gtk_version():
'''
returns float of the major and minor parts of the GTK version
e.g. return float(3.10)
'''
return float(str(Gtk.get_major_version())+"."+str(Gtk.get_minor_version()))
def pygobject_version():
'''
returns float of the major and minor parts of a pygobject version
e.g. version (3, 9, 5) return float(3.9)
'''
to_number = lambda t: ".".join(str(v) for v in t)
str_version = to_number(GObject.pygobject_version)
return float(str_version.rsplit('.', 1)[0])
def compare_pygobject_version(version):
'''
return True if version is less than pygobject_version
i.e. 3.9 < 3.11
'''
to_number = lambda t: ".".join(str(v) for v in t)
str_version = to_number(GObject.pygobject_version)
split = str_version.rsplit('.', 2)
split_compare = version.rsplit('.', 2)
if int(split_compare[0]) < int(split[0]):
return True
if int(split_compare[1]) < int(split[1]):
return True
return False
PYVER = sys.version_info[0]
if PYVER >= 3:
import urllib.request, urllib.parse, urllib.error
else:
import urllib
from urlparse import urlparse as rb2urlparse
if PYVER >= 3:
import http.client
else:
import httplib
def responses():
if PYVER >= 3:
return http.client.responses
else:
return httplib.responses
def unicodestr(param, charset):
if PYVER >= 3:
return param # str(param, charset)
else:
return unicode(param, charset)
def unicodeencode(param, charset):
if PYVER >= 3:
return param # str(param).encode(charset)
else:
return unicode(param).encode(charset)
def unicodedecode(param, charset):
if PYVER >= 3:
return param
else:
return param.decode(charset)
def urlparse(uri):
if PYVER >= 3:
return urllib.parse.urlparse(uri)
else:
return rb2urlparse(uri)
def url2pathname(url):
if PYVER >= 3:
return urllib.request.url2pathname(url)
else:
return urllib.url2pathname(url)
def urlopen(filename):
if PYVER >= 3:
return urllib.request.urlopen(filename)
else:
return urllib.urlopen(filename)
def pathname2url(filename):
if PYVER >= 3:
return urllib.request.pathname2url(filename)
else:
return urllib.pathname2url(filename)
def unquote(uri):
if PYVER >= 3:
return urllib.parse.unquote(uri)
else:
return urllib.unquote(uri)
def quote(uri, safe=None):
if PYVER >= 3:
if safe:
return urllib.parse.quote(uri, safe=safe)
else:
return urllib.parse.quote(uri)
else:
if safe:
return urllib.quote(uri, safe=safe)
else:
return urllib.quote(uri)
def quote_plus(uri):
if PYVER >= 3:
return urllib.parse.quote_plus(uri)
else:
return urllib.quote_plus(uri)
def is_rb3(*args):
if hasattr(RB.Shell.props, 'ui_manager'):
return False
else:
return True
class Menu(GObject.Object):
'''
Menu object used to create window popup menus
'''
__gsignals__ = {
'pre-popup': (GObject.SIGNAL_RUN_LAST, None, ())
}
def __init__(self, plugin, shell):
'''
Initializes the menu.
'''
super(Menu, self).__init__()
self.plugin = plugin
self.shell = shell
self._unique_num = 0
self._rbmenu_items = {}
self._rbmenu_objects = {}
def add_menu_item(self, menubar, section_name, action):
'''
add a new menu item to the popup
:param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
:param section_name: `str` is the name of the section to add the item to (RB2.99+)
:param action: `Action` to associate with the menu item
'''
return self.insert_menu_item(menubar, section_name, -1, action)
def insert_menu_item(self, menubar, section_name, position, action):
'''
add a new menu item to the popup
:param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
:param section_name: `str` is the name of the section to add the item to (RB2.99+)
:param position: `int` position to add to GtkMenu (ignored for RB2.99+)
:param action: `Action` to associate with the menu item
'''
label = action.label
if is_rb3(self.shell):
app = self.shell.props.application
item = Gio.MenuItem()
action.associate_menuitem(item)
item.set_label(label)
if not section_name in self._rbmenu_items:
self._rbmenu_items[section_name] = []
self._rbmenu_items[section_name].append(label)
app.add_plugin_menu_item(section_name, label, item)
else:
item = Gtk.MenuItem(label=label)
action.associate_menuitem(item)
self._rbmenu_items[label] = item
bar = self.get_menu_object(menubar)
if position == -1:
bar.append(item)
else:
bar.insert(item, position)
bar.show_all()
uim = self.shell.props.ui_manager
uim.ensure_update()
return item
def insert_separator(self, menubar, at_position):
'''
add a separator to the popup (only required for RB2.98 and earlier)
:param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
:param position: `int` position to add to GtkMenu (ignored for RB2.99+)
'''
if not is_rb3(self.shell):
menu_item = Gtk.SeparatorMenuItem().new()
menu_item.set_visible(True)
self._rbmenu_items['separator' + str(self._unique_num)] = menu_item
self._unique_num = self._unique_num + 1
bar = self.get_menu_object(menubar)
bar.insert(menu_item, at_position)
bar.show_all()
uim = self.shell.props.ui_manager
uim.ensure_update()
def remove_menu_items(self, menubar, section_name):
'''
utility function to remove all menuitems associated with the menu section
:param menubar: `str` is the name of the GtkMenu containing the menu items (ignored for RB2.99+)
:param section_name: `str` is the name of the section containing the menu items (for RB2.99+ only)
'''
if is_rb3(self.shell):
if not section_name in self._rbmenu_items:
return
app = self.shell.props.application
for menu_item in self._rbmenu_items[section_name]:
app.remove_plugin_menu_item(section_name, menu_item)
if self._rbmenu_items[section_name]:
del self._rbmenu_items[section_name][:]
else:
if not self._rbmenu_items:
return
uim = self.shell.props.ui_manager
bar = self.get_menu_object(menubar)
for menu_item in self._rbmenu_items:
bar.remove(self._rbmenu_items[menu_item])
bar.show_all()
uim.ensure_update()
def load_from_file(self, rb2_ui_filename, rb3_ui_filename):
'''
utility function to load the menu structure
:param rb2_ui_filename: `str` RB2.98 and below UI file
:param rb3_ui_filename: `str` RB2.99 and higher UI file
'''
self.builder = Gtk.Builder()
try:
from coverart_browser_prefs import CoverLocale
cl = CoverLocale()
self.builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
except:
pass
if is_rb3(self.shell):
ui_filename = rb3_ui_filename
else:
ui_filename = rb2_ui_filename
self.ui_filename = ui_filename
self.builder.add_from_file(rb.find_plugin_file(self.plugin,
ui_filename))
def _connect_rb3_signals(self, signals):
def _menu_connect(action_name, func):
action = Gio.SimpleAction(name=action_name)
action.connect('activate', func)
action.set_enabled(True)
self.shell.props.window.add_action(action)
for key, value in signals.items():
_menu_connect(key, value)
def _connect_rb2_signals(self, signals):
def _menu_connect(menu_item_name, func):
menu_item = self.get_menu_object(menu_item_name)
menu_item.connect('activate', func)
for key, value in signals.items():
_menu_connect(key, value)
def connect_signals(self, signals):
'''
connect all signal handlers with their menuitem counterparts
:param signals: `dict` key is the name of the menuitem
and value is the function callback when the menu is activated
'''
if is_rb3(self.shell):
self._connect_rb3_signals(signals)
else:
self._connect_rb2_signals(signals)
def get_gtkmenu(self, source, popup_name):
'''
utility function to obtain the GtkMenu from the menu UI file
:param popup_name: `str` is the name menu-id in the UI file
'''
if popup_name in self._rbmenu_objects:
return self._rbmenu_objects[popup_name]
item = self.builder.get_object(popup_name)
if is_rb3(self.shell):
app = self.shell.props.application
app.link_shared_menus(item)
popup_menu = Gtk.Menu.new_from_model(item)
popup_menu.attach_to_widget(source, None)
else:
popup_menu = item
self._rbmenu_objects[popup_name] = popup_menu
return popup_menu
def get_menu_object(self, menu_name_or_link):
'''
utility function returns the GtkMenuItem/Gio.MenuItem
:param menu_name_or_link: `str` to search for in the UI file
'''
if menu_name_or_link in self._rbmenu_objects:
return self._rbmenu_objects[menu_name_or_link]
item = self.builder.get_object(menu_name_or_link)
if is_rb3(self.shell):
if item:
popup_menu = item
else:
app = self.shell.props.application
popup_menu = app.get_plugin_menu(menu_name_or_link)
else:
popup_menu = item
print(menu_name_or_link)
self._rbmenu_objects[menu_name_or_link] = popup_menu
return popup_menu
def set_sensitive(self, menu_or_action_item, enable):
'''
utility function to enable/disable a menu-item
:param menu_or_action_item: `GtkMenuItem` or `Gio.SimpleAction`
that is to be enabled/disabled
:param enable: `bool` value to enable/disable
'''
if is_rb3(self.shell):
item = self.shell.props.window.lookup_action(menu_or_action_item)
item.set_enabled(enable)
else:
item = self.get_menu_object(menu_or_action_item)
item.set_sensitive(enable)
def popup(self, source, menu_name, button, time):
'''
utility function to show the popup menu
'''
self.emit('pre-popup')
menu = self.get_gtkmenu(source, menu_name)
menu.popup(None, None, None, None, button, time)
class ActionGroup(object):
'''
container for all Actions used to associate with menu items
'''
# action_state
STANDARD = 0
TOGGLE = 1
def __init__(self, shell, group_name):
'''
constructor
:param shell: `RBShell`
:param group_name: `str` unique name for the object to create
'''
self.group_name = group_name
self.shell = shell
self._actions = {}
if is_rb3(self.shell):
self.actiongroup = Gio.SimpleActionGroup()
else:
self.actiongroup = Gtk.ActionGroup(group_name)
uim = self.shell.props.ui_manager
uim.insert_action_group(self.actiongroup)
@property
def name(self):
return self.group_name
def remove_actions(self):
'''
utility function to remove all actions associated with the ActionGroup
'''
for action in self.actiongroup.list_actions():
self.actiongroup.remove_action(action)
def get_action(self, action_name):
'''
utility function to obtain the Action from the ActionGroup
:param action_name: `str` is the Action unique name
'''
return self._actions[action_name]
def add_action_with_accel(self, func, action_name, accel, **args):
'''
Creates an Action with an accelerator and adds it to the ActionGroup
:param func: function callback used when user activates the action
:param action_name: `str` unique name to associate with an action
:param accel: `str` accelerator
:param args: dict of arguments - this is passed to the function callback
Notes:
see notes for add_action
'''
args['accel'] = accel
return self.add_action(func, action_name, **args)
def add_action(self, func, action_name, **args):
'''
Creates an Action and adds it to the ActionGroup
:param func: function callback used when user activates the action
:param action_name: `str` unique name to associate with an action
:param args: dict of arguments - this is passed to the function callback
Notes:
key value of "label" is the visual menu label to display
key value of "action_type" is the RB2.99 Gio.Action type ("win" or "app")
by default it assumes all actions are "win" type
key value of "action_state" determines what action state to create
'''
if 'label' in args:
label = args['label']
else:
label = action_name
if 'accel' in args:
accel = args['accel']
else:
accel = None
state = ActionGroup.STANDARD
if 'action_state' in args:
state = args['action_state']
if is_rb3(self.shell):
if state == ActionGroup.TOGGLE:
action = Gio.SimpleAction.new_stateful(action_name, None,
GLib.Variant('b', False))
else:
action = Gio.SimpleAction.new(action_name, None)
action_type = 'win'
if 'action_type' in args:
if args['action_type'] == 'app':
action_type = 'app'
app = Gio.Application.get_default()
if action_type == 'app':
app.add_action(action)
else:
self.shell.props.window.add_action(action)
self.actiongroup.add_action(action)
if accel:
app.add_accelerator(accel, action_type + "." + action_name, None)
else:
if 'stock_id' in args:
stock_id = args['stock_id']
else:
stock_id = Gtk.STOCK_CLEAR
if state == ActionGroup.TOGGLE:
action = Gtk.ToggleAction(label=label,
name=action_name,
tooltip='', stock_id=stock_id)
else:
action = Gtk.Action(label=label,
name=action_name,
tooltip='', stock_id=stock_id)
if accel:
self.actiongroup.add_action_with_accel(action, accel)
else:
self.actiongroup.add_action(action)
act = Action(self.shell, action)
act.connect('activate', func, args)
act.label = label
act.accel = accel
self._actions[action_name] = act
return act
class ApplicationShell(object):
'''
Unique class that mirrors RB.Application & RB.Shell menu functionality
'''
# storage for the instance reference
__instance = None
class __impl:
""" Implementation of the singleton interface """
def __init__(self, shell):
self.shell = shell
if is_rb3(self.shell):
self._uids = {}
else:
self._uids = []
self._action_groups = {}
def insert_action_group(self, action_group):
'''
Adds an ActionGroup to the ApplicationShell
:param action_group: `ActionGroup` to add
'''
self._action_groups[action_group.name] = action_group
def lookup_action(self, action_group_name, action_name, action_type='app'):
'''
looks up (finds) an action created by another plugin. If found returns
an Action or None if no matching Action.
:param action_group_name: `str` is the Gtk.ActionGroup name (ignored for RB2.99+)
:param action_name: `str` unique name for the action to look for
:param action_type: `str` RB2.99+ action type ("win" or "app")
'''
if is_rb3(self.shell):
if action_type == "app":
action = self.shell.props.application.lookup_action(action_name)
else:
action = self.shell.props.window.lookup_action(action_name)
else:
uim = self.shell.props.ui_manager
ui_actiongroups = uim.get_action_groups()
actiongroup = None
for actiongroup in ui_actiongroups:
if actiongroup.get_name() == action_group_name:
break
action = None
if actiongroup:
action = actiongroup.get_action(action_name)
if action:
return Action(self.shell, action)
else:
return None
def add_app_menuitems(self, ui_string, group_name, menu='tools'):
'''
utility function to add application menu items.
For RB2.99 all application menu items are added to the "tools" section of the
application menu. All Actions are assumed to be of action_type "app".
For RB2.98 or less, it is added however the UI_MANAGER string
is defined.
:param ui_string: `str` is the Gtk UI definition. There is not an
equivalent UI definition in RB2.99 but we can parse out menu items since
this string is in XML format
:param group_name: `str` unique name of the ActionGroup to add menu items to
:param menu: `str` RB2.99 menu section to add to - nominally either
'tools' or 'view'
'''
if is_rb3(self.shell):
root = ET.fromstring(ui_string)
for elem in root.findall(".//menuitem"):
action_name = elem.attrib['action']
item_name = elem.attrib['name']
group = self._action_groups[group_name]
act = group.get_action(action_name)
item = Gio.MenuItem()
item.set_detailed_action('app.' + action_name)
item.set_label(act.label)
item.set_attribute_value("accel", GLib.Variant("s", act.accel))
app = Gio.Application.get_default()
index = menu + action_name
app.add_plugin_menu_item(menu,
index, item)
self._uids[index] = menu
else:
uim = self.shell.props.ui_manager
self._uids.append(uim.add_ui_from_string(ui_string))
uim.ensure_update()
def add_browser_menuitems(self, ui_string, group_name):
'''
utility function to add popup menu items to existing browser popups
For RB2.99 all menu items are are assumed to be of action_type "win".
For RB2.98 or less, it is added however the UI_MANAGER string
is defined.
:param ui_string: `str` is the Gtk UI definition. There is not an
equivalent UI definition in RB2.99 but we can parse out menu items since
this string is in XML format
:param group_name: `str` unique name of the ActionGroup to add menu items to
'''
if is_rb3(self.shell):
root = ET.fromstring(ui_string)
for elem in root.findall("./popup"):
popup_name = elem.attrib['name']
menuelem = elem.find('.//menuitem')
action_name = menuelem.attrib['action']
item_name = menuelem.attrib['name']
group = self._action_groups[group_name]
act = group.get_action(action_name)
item = Gio.MenuItem()
item.set_detailed_action('win.' + action_name)
item.set_label(act.label)
app = Gio.Application.get_default()
if popup_name == 'QueuePlaylistViewPopup':
plugin_type = 'queue-popup'
elif popup_name == 'BrowserSourceViewPopup':
plugin_type = 'browser-popup'
elif popup_name == 'PlaylistViewPopup':
plugin_type = 'playlist-popup'
elif popup_name == 'PodcastViewPopup':
plugin_type = 'podcast-episode-popup'
else:
print("unknown type %s" % plugin_type)
index = plugin_type + action_name
app.add_plugin_menu_item(plugin_type, index, item)
self._uids[index] = plugin_type
else:
uim = self.shell.props.ui_manager
self._uids.append(uim.add_ui_from_string(ui_string))
uim.ensure_update()
def cleanup(self):
'''
utility remove any menuitems created.
'''
if is_rb3(self.shell):
for uid in self._uids:
Gio.Application.get_default().remove_plugin_menu_item(self._uids[uid],
uid)
else:
uim = self.shell.props.ui_manager
for uid in self._uids:
uim.remove_ui(uid)
uim.ensure_update();
def __init__(self, shell):
""" Create singleton instance """
# Check whether we already have an instance
if ApplicationShell.__instance is None:
# Create and remember instance
ApplicationShell.__instance = ApplicationShell.__impl(shell)
# Store instance reference as the only member in the handle
self.__dict__['_ApplicationShell__instance'] = ApplicationShell.__instance
def __getattr__(self, attr):
""" Delegate access to implementation """
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)
class Action(object):
'''
class that wraps around either a Gio.Action or a Gtk.Action
'''
def __init__(self, shell, action):
'''
constructor.
:param shell: `RBShell`
:param action: `Gio.Action` or `Gtk.Action`
'''
self.shell = shell
self.action = action
self._label = ''
self._accel = ''
self._current_state = False
self._do_update_state = True
def connect(self, address, func, args):
self._connect_func = func
self._connect_args = args
if address == 'activate':
func = self._activate
if is_rb3(self.shell):
self.action.connect(address, func, args)
else:
self.action.connect(address, func, None, args)
def _activate(self, action, *args):
if self._do_update_state:
self._current_state = not self._current_state
self.set_state(self._current_state)
self._connect_func(action, None, self._connect_args)
@property
def label(self):
'''
get the menu label associated with the Action
for RB2.99+ actions dont have menu labels so this is managed
manually
'''
if not is_rb3(self.shell):
return self.action.get_label()
else:
return self._label
@label.setter
def label(self, new_label):
if not is_rb3(self.shell):
self.action.set_label(new_label)
self._label = new_label
@property
def accel(self):
'''
get the accelerator associated with the Action
'''
return self._accel
@accel.setter
def accel(self, new_accelerator):
if new_accelerator:
self._accel = new_accelerator
else:
self._accel = ''
def get_sensitive(self):
'''
get the sensitivity (enabled/disabled) state of the Action
returns boolean
'''
if is_rb3(self.shell):
return self.action.get_enabled()
else:
return self.action.get_sensitive()
def set_state(self, value):
'''
set the state of a stateful action - this is applicable only
to RB2.99+
'''
if is_rb3(self.shell) and self.action.props.state_type:
self.action.change_state(GLib.Variant('b', value))
def activate(self):
'''
invokes the activate signal for the action
'''
if is_rb3(self.shell):
self.action.activate(None)
else:
self.action.activate()
def set_active(self, value):
'''
activate or deactivate a stateful action signal
For consistency with earlier RB versions, this will fire the
activate signal for the action
:param value: `boolean` state value
'''
if is_rb3(self.shell):
self.action.change_state(GLib.Variant('b', value))
self._current_state = value
self._do_update_state = False
self.activate()
self._do_update_state = True
else:
self.action.set_active(value)
def get_active(self):
'''
get the state of the action
returns `boolean` state value
'''
if is_rb3(self.shell):
returnval = self._current_state
else:
returnval = self.action.get_active()
return returnval
def associate_menuitem(self, menuitem):
'''
links a menu with the action
'''
if is_rb3(self.shell):
menuitem.set_detailed_action('win.' + self.action.get_name())
else:
menuitem.set_related_action(self.action)
+83 -49
Ver Arquivo
@@ -18,67 +18,74 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import rb
from gi.repository import Gtk
from gi.repository import WebKit
from mako.template import Template
from coverart_album import AlbumManager
import rb
import coverart_rb3compat as rb3compat
from coverart_album import Album
from coverart_browser_prefs import webkit_support
class CoverSearchPane(Gtk.Box):
'''
This UI represents a pane where different album's covers can be presented
given an album to look for. It also allows to make custom image searchs,
This UI represents a pane where different covers can be presented
given an album or artist to look for. It also allows to make custom image searchs,
customize the default search and select covers from the pane and use them
as the album covers (either with a double click or draging them).
as the covers (either with a double click or dragging them).
'''
def __init__(self, plugin, album_manager, selection_color):
def __init__(self, plugin, selection_color):
'''
Initializes the pane, loading it's html templates and it's ui.
'''
super(CoverSearchPane, self).__init__()
self.set_orientation(Gtk.Orientation.VERTICAL)
self.album_manager = album_manager
self.selection_color = selection_color
self.file = ""
self.basepath = 'file://' + plugin.plugin_info.get_data_dir()
self.load_templates(plugin)
self.init_gui()
if webkit_support():
self.init_gui()
# init the pane with the empty template
self.clear()
# init the pane with the empty template
self.clear()
def load_templates(self, plugin):
'''
Loads the templates and stylesheets to be used by the pane.
'''
# input_encoding='utf-8',
# input_encoding='utf-8',
path = rb.find_plugin_file(plugin,
'tmpl/albumartsearch-tmpl.html')
'tmpl/albumartsearch-tmpl.html')
self.template = Template(filename=path,
default_filters=['decode.utf8'],
module_directory='/tmp/',
output_encoding='utf-8',
encoding_errors='replace')
default_filters=['decode.utf8'],
module_directory='/tmp/',
encoding_errors='replace')
path = rb.find_plugin_file(plugin,
'tmpl/albumartsearchempty-tmpl.html')
'tmpl/albumartsearchempty-tmpl.html')
self.empty_template = Template(filename=path,
default_filters=['decode.utf8'],
module_directory='/tmp/',
output_encoding='utf-8',
encoding_errors='replace')
default_filters=['decode.utf8'],
module_directory='/tmp/',
encoding_errors='replace')
path = rb.find_plugin_file(plugin,
'tmpl/artistartsearch-tmpl.html')
self.artist_template = Template(filename=path,
default_filters=['decode.utf8'],
module_directory='/tmp/',
encoding_errors='replace')
self.styles = rb.find_plugin_file(plugin, 'tmpl/main.css')
def init_gui(self):
'''
Initializes the pane ui.
'''
#---- set up webkit pane -----#
# ---- set up webkit pane -----#
from gi.repository import WebKit
self.webview = WebKit.WebView()
settings = self.webview.get_settings()
settings.set_property('enable-default-context-menu', False)
@@ -91,64 +98,91 @@ class CoverSearchPane(Gtk.Box):
self.show_all()
# connect the title changed signal
#self.webview.connect('title-changed', self.set_cover)
self.webview.connect('notify::title', self.set_cover)
def do_search(self, album):
def do_search(self, coverobject, callback):
'''
When this method is called, the webview gets refreshed with the info
of the album passed.
of the album or artist passed.
'''
if album is self.current_album:
print("coverart-search do_search")
if coverobject is self.current_searchobject:
return
self.current_album = album
self.current_searchobject = coverobject
self.callback = callback
artist = album.artist
album_name = album.name
if isinstance(coverobject, Album):
artist = coverobject.artist
album_name = coverobject.name
if album_name.upper() == "UNKNOWN":
album_name = ""
if album_name.upper() == "UNKNOWN":
album_name = ""
if artist.upper() == "UNKNOWN":
artist = ""
if artist.upper() == "UNKNOWN":
artist = ""
if not (album_name == "" and artist == ""):
artist = rb3compat.unicodestr(artist.replace('&', '&amp;'),
'utf-8')
album_name = rb3compat.unicodestr(album_name.replace('&', '&amp;'), 'utf-8')
self.render_album_art_search(artist, album_name)
else:
artist_name = coverobject.name
if artist_name.upper() == "UNKNOWN":
artist_name = ""
if not (artist_name == ""):
artist = rb3compat.unicodestr(artist_name.replace('&', '&amp;'),
'utf-8')
self.render_artist_art_search(artist)
if not(album_name == "" and artist == ""):
artist = unicode(artist.replace('&', '&amp;'),
'utf-8')
album_name = unicode(album_name.replace('&', '&amp;'), 'utf-8')
self.render_album_art_search(artist, album_name)
def render_album_art_search(self, artist, album_name):
'''
Renders the template on the webview.
'''
temp_file = self.template.render(artist=artist, album=album_name,
stylesheet=self.styles, selection_color=self.selection_color)
stylesheet=self.styles, selection_color=self.selection_color)
print("here")
self.webview.load_string(temp_file, 'text/html', 'utf-8',
self.basepath)
self.basepath)
def render_artist_art_search(self, artist):
'''
Renders the template on the webview.
'''
temp_file = self.artist_template.render(artist=artist,
stylesheet=self.styles, selection_color=self.selection_color)
print("here")
self.webview.load_string(temp_file, 'text/html', 'utf-8',
self.basepath)
def clear(self):
'''
Clears the webview of any album's specific info/covers.
Clears the webview of any specific info/covers.
'''
self.current_album = None
self.current_searchobject = None
temp_file = self.empty_template.render(stylesheet=self.styles)
self.webview.load_string(temp_file, 'text/html', 'utf-8',
self.basepath)
self.basepath)
def set_cover(self, webview, arg):
'''
Callback called when a image in the pane is double-clicked. It takes
care of asking the AlbumLoader to update the album's cover.
care of updating the searched object cover.
Some titles have spurious characters beginning with % - remove these
'''
# update the cover
title = webview.get_title()
print title
print(title)
if title:
self.album_manager.cover_man.update_cover(self.current_album,
uri=title)
# self.album_manager.cover_man.update_cover(self.current_searchobject,
# uri=title)
self.callback(self.current_searchobject, uri=title)
-15
Ver Arquivo
@@ -1,15 +0,0 @@
[Plugin]
Loader=python
Module=coverart_search_providers
IAge=2
Depends=rb;coverart_browser
Name=CoverArt Browser Search Providers
Description=Additional cover-art search providers for Rhythmbox
Authors=fossfreedom <foss.freedom@gmail.com>, Agustín Carrasco <asermax@gmail.com>
Copyright=© 2012 fossfreedom, Agustín Carrasco
Website=http://github.com/fossfreedom/coverart-browser
Help=http://github.com/fossfreedom/coverart-browser/issues
Version=0.7
[RB]
InitiallyEnabled=true
-103
Ver Arquivo
@@ -1,103 +0,0 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
# define plugin
import rb
import locale
import gettext
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import RB
from gi.repository import GdkPixbuf
from gi.repository import Peas
from coverart_browser_prefs import GSetting
from coverart_album_search import CoverAlbumSearch
from coverart_album_search import DiscogsSearch
from coverart_album_search import CoverSearch
from coverart_album_search import CoverartArchiveSearch
class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
'''
Main class of the plugin. Manages the activation and deactivation of the
plugin.
'''
__gtype_name = 'CoverArtAlbumSearchPlugin'
object = GObject.property(type=GObject.Object)
def __init__(self):
'''
Initialises the plugin object.
'''
GObject.Object.__init__(self)
GObject.threads_init()
def do_activate(self):
'''
Called by Rhythmbox when the plugin is activated. It creates the
plugin's source and connects signals to manage the plugin's
preferences.
'''
#define .plugin text strings used for translation
#plugin = _('CoverArt Browser')
#desc = _('Browse and play your albums through their covers')
print "CoverArtBrowser DEBUG - do_activate"
self.shell = self.object
self.db = self.shell.props.db
self.art_store = RB.ExtDB(name="album-art")
self.req_id = self.art_store.connect("request", self.album_art_requested)
print "CoverArtBrowser DEBUG - end do_activate"
def do_deactivate(self):
'''
Called by Rhythmbox when the plugin is deactivated. It makes sure to
free all the resources used by the plugin.
'''
print "CoverArtBrowser DEBUG - do_deactivate"
del self.shell
del self.db
self.art_store.disconnect(self.req_id)
self.req_id = 0
self.art_store = None
print "CoverArtBrowser DEBUG - end do_deactivate"
def album_art_requested(self, store, key, last_time):
searches = []
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
if setting[gs.PluginKey.EMBEDDED_SEARCH]:
searches.append(CoverAlbumSearch())
if setting[gs.PluginKey.DISCOGS_SEARCH]:
searches.append(DiscogsSearch())
if setting[gs.PluginKey.COVERARTARCHIVE_SEARCH]:
searches.append(CoverartArchiveSearch())
print "about to search"
s = CoverSearch(store, key, last_time, searches)
print "finished about to return"
return s.next_search()
-106
Ver Arquivo
@@ -1,106 +0,0 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
##
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
#ttimer: Thread callback timer, it execute your callback function periodically.
#
#adapted from
#
#Author : H.K.Ong
#Date : 27-03-2008
#Website : http://linux.byexamples.com
#Revision : 1
#
import sys,time
from threading import Thread
import threading
class ttimer(object):
"""Threading callback timer - threading timer will callback your function periodically.
interval - interval callback periodically, in sec
retry - execute how many times? -1 is infinity
cbfunc - callback function
cbparam - parameter in list
i.e t=ttimer(1,10,myfunc,["myparam"])"""
def __init__(self, interval, retry, cbfunc, cbparam=[]):
self.is_start=False
self.is_end=False
# doing my thread stuff now.
self.thread = threading.Thread(target = self._callback, args=(interval,retry,cbfunc,cbparam,) )
self.thread.setDaemon(True)
self.thread.start()
#thread.start_new_thread(self._callback,(interval,retry,cbfunc,cbparam,))
def Start(self):
#start the thread
self.mytime=time.time()
self.is_start=True
self.is_end=False
def Stop(self):
#stop the thread.
self.mytime=time.time()
self.is_start=False
self.is_end=True
def IsStop(self):
#Is the thread already end? return True if yes.
if self.is_end:
return True
else:
return False
def _callback(self,interval,retry,cbfunc,cbparam=[]):
""" This is the private thread loop, call start() to start the threading timer."""
print "callback"
self.retry=retry
retry=0
if self.is_end:
return None
while True:
if not self.is_end:
if self.retry==-1:
pass
elif retry>=self.retry:
break
if self.is_start:
#check time
tmptime=time.time()
if tmptime >=(self.mytime + interval):
print "before"
cbfunc(cbparam) # callback your function
print "after"
self.mytime=time.time()
if not self.retry== -1:
retry+=1
else:
pass
time.sleep(0.5)
self.is_end=True
print "end callback"
+263
Ver Arquivo
@@ -0,0 +1,263 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import RB
from gi.repository import Gio
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import CoverLocale
from coverart_utils import Theme
from coverart_controllers import PlaylistPopupController
from coverart_controllers import GenrePopupController
from coverart_controllers import SortPopupController
from coverart_controllers import ArtistSortPopupController
from coverart_controllers import PropertiesMenuController
from coverart_controllers import DecadePopupController
from coverart_controllers import SortOrderToggleController
from coverart_controllers import ArtistSortOrderToggleController
from coverart_controllers import AlbumSearchEntryController
from coverart_widgets import SearchEntry
from coverart_browser_prefs import webkit_support
import rb
class Toolbar(GObject.Object):
def __init__(self, plugin, mainbox, controllers):
super(Toolbar, self).__init__()
self.plugin = plugin
self.mainbox = mainbox
cl = CoverLocale()
ui_file = rb.find_plugin_file(plugin, self.ui)
# create the toolbar
builder = Gtk.Builder()
builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
print (ui_file)
builder.add_from_file(ui_file)
# assign the controllers to the buttons
for button, controller in controllers.items():
if button != 'search':
builder.get_object(button).controller = controller
if not webkit_support():
# button = builder.get_object('flowview_button')
#button.set_visible(False)
separator = builder.get_object('properties_separator')
if separator:
separator.set_visible(False)
# workaround to translate the search entry tooltips
cl.switch_locale(cl.Locale.RB)
search_entry = SearchEntry(has_popup=True)
search_entry.show_all()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
# add it to the ui
align = builder.get_object('entry_search_alignment')
align.add(search_entry)
# assign the controller
search_entry.controller = controllers['search']
Theme(self.plugin).connect('theme_changed', self._theme_changed,
controllers)
self.builder = builder.get_object('toolbar')
# now theme the toolbar including child objects such as the button popups
style_context = self.builder.get_style_context()
style_context.add_class(Gtk.STYLE_CLASS_TOOLBAR)
view_button = builder.get_object(ToolbarObject.VIEW)
view_button.set_visible(not self.plugin.using_headerbar)
def _theme_changed(self, toolbar, controllers):
for controller in list(controllers.values()):
controller.update_images(True)
class TopToolbar(Toolbar):
ui = 'ui/coverart_topbar.ui'
name = 'main'
def hide(self):
if self.builder.get_visible():
self.builder.hide()
def show(self):
self.mainbox.pack_start(self.builder, False, True, 0)
self.mainbox.reorder_child(self.builder, 0)
self.builder.show()
class LeftToolbar(Toolbar):
ui = 'ui/coverart_leftsidebar.ui'
name = 'left'
def hide(self):
if self.builder.get_visible():
self.builder.hide()
self.plugin.shell.remove_widget(self.builder,
RB.ShellUILocation.SIDEBAR)
def show(self):
self.plugin.shell.add_widget(self.builder,
RB.ShellUILocation.SIDEBAR, expand=False, fill=False)
self.builder.show()
class RightToolbar(Toolbar):
ui = 'ui/coverart_rightsidebar.ui'
name = 'right'
def hide(self):
if self.builder.get_visible():
self.builder.hide()
self.plugin.shell.remove_widget(self.builder,
RB.ShellUILocation.RIGHT_SIDEBAR)
def show(self):
self.plugin.shell.add_widget(self.builder,
RB.ShellUILocation.RIGHT_SIDEBAR, expand=False, fill=False)
self.builder.show()
class ToolbarObject(object):
# properties
PROPERTIES = 'properties_button'
SORT_BY = 'sort_by'
SORT_ORDER = 'sort_order'
SORT_BY_ARTIST = 'sort_by_artist'
SORT_ORDER_ARTIST = 'sort_order_artist'
GENRE = 'genre_button'
PLAYLIST = 'playlist_button'
DECADE = 'decade_button'
SEARCH = 'search'
VIEW = 'view_button'
class ToolbarManager(GObject.Object):
# properties
toolbar_pos = GObject.property(type=str, default=TopToolbar.name)
def __init__(self, plugin, main_box, viewmgr):
super(ToolbarManager, self).__init__()
self.plugin = plugin
# create the buttons controllers
controllers = self._create_controllers(plugin, viewmgr)
# initialize toolbars
self._bars = {}
self._bars[TopToolbar.name] = TopToolbar(plugin, main_box,
controllers)
self._bars[LeftToolbar.name] = LeftToolbar(plugin, main_box,
controllers)
self._bars[RightToolbar.name] = RightToolbar(plugin, main_box,
controllers)
self.last_toolbar_pos = None
# if the alternative-toolbar is loaded then lets connect to the toolbar-visibility signal
# to control our sources toolbar visibility
if self.plugin.using_alternative_toolbar:
if self.plugin.using_headerbar:
self.toolbar_pos = TopToolbar.name # we dont allow other toolbar position with headerbar
self._on_notify_toolbar_pos()
self.plugin.shell.alternative_toolbar.connect('toolbar-visibility', self._visibility)
# connect signal and properties
self._connect_signals()
self._connect_properties()
self._controllers = controllers
def _visibility(self, altplugin, value):
if value:
self._bars[self.toolbar_pos].show()
else:
self._bars[self.toolbar_pos].hide()
def set_enabled(self, enabled, toolbar_object=None):
'''
enable or disable the toolbar object.
:param enabled: `bool` value.
:param toolbar_object: `ToolbarObject`
None if enabled is to apply to all objects in the toolbar
'''
if toolbar_object:
self._controllers[toolbar_object].enabled = enabled
else:
for controller in self._controllers:
self._controllers[controller].enabled = enabled
def _connect_signals(self):
if not self.plugin.using_headerbar:
self.connect('notify::toolbar-pos', self._on_notify_toolbar_pos)
def _connect_properties(self):
if not self.plugin.using_headerbar:
gs = GSetting()
setting = gs.get_setting(gs.Path.PLUGIN)
setting.bind(gs.PluginKey.TOOLBAR_POS, self, 'toolbar_pos',
Gio.SettingsBindFlags.GET)
def _create_controllers(self, plugin, viewmgr):
controllers = {}
album_model = viewmgr.source.album_manager.model
controllers[ToolbarObject.PROPERTIES] = \
PropertiesMenuController(plugin, viewmgr.source)
controllers[ToolbarObject.SORT_BY] = \
SortPopupController(plugin, viewmgr)
controllers[ToolbarObject.SORT_ORDER] = \
SortOrderToggleController(plugin, viewmgr)
controllers[ToolbarObject.SORT_BY_ARTIST] = \
ArtistSortPopupController(plugin, viewmgr)
controllers[ToolbarObject.SORT_ORDER_ARTIST] = \
ArtistSortOrderToggleController(plugin, viewmgr)
controllers[ToolbarObject.GENRE] = \
GenrePopupController(plugin, album_model)
controllers[ToolbarObject.PLAYLIST] = \
PlaylistPopupController(plugin, album_model)
controllers[ToolbarObject.DECADE] = \
DecadePopupController(plugin, album_model)
controllers[ToolbarObject.SEARCH] = \
AlbumSearchEntryController(album_model)
controllers[ToolbarObject.VIEW] = viewmgr.controller
return controllers
def _on_notify_toolbar_pos(self, *args):
if self.last_toolbar_pos:
self._bars[self.last_toolbar_pos].hide()
self._bars[self.toolbar_pos].show()
self.last_toolbar_pos = self.toolbar_pos
+393 -54
Ver Arquivo
@@ -15,16 +15,85 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from bisect import bisect_left, bisect_right
import collections
import re
import logging
import sys
from collections import namedtuple
from gi.repository import GdkPixbuf
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import RB
from gi.repository import GObject
from gi.repository import Gio
import lxml.etree as ET
import rb
from coverart_browser_prefs import CoverLocale
import collections
import re
from coverart_browser_prefs import GSetting
import coverart_rb3compat as rb3compat
from coverart_search_providers import lastfm_connected
from coverart_search_providers import get_search_providers
class FauxTb(object):
def __init__(self, tb_frame, tb_lineno, tb_next):
self.tb_frame = tb_frame
self.tb_lineno = tb_lineno
self.tb_next = tb_next
def current_stack(skip=0):
try:
1 / 0
except ZeroDivisionError:
f = sys.exc_info()[2].tb_frame
for i in range(skip + 2):
f = f.f_back
lst = []
while f is not None:
lst.append((f, f.f_lineno))
f = f.f_back
return lst
def extend_traceback(tb, stack):
"""Extend traceback with stack info."""
head = tb
for tb_frame, tb_lineno in stack:
head = FauxTb(tb_frame, tb_lineno, head)
return head
def full_exc_info():
"""Like sys.exc_info, but includes the full traceback."""
t, v, tb = sys.exc_info()
full_tb = extend_traceback(tb, current_stack(1))
return t, v, full_tb
def dumpstack(message):
''' dumps the current stack - useful of debugging
'''
logging.error(message, exc_info=full_exc_info())
def uniquify_and_sort(iterable):
''' Removes duplicates of an iterables and returns a list of unique
elements.
'''
uniques = []
for element in iterable:
if element not in uniques:
uniques.append(element)
return sorted(uniques)
GenreType = namedtuple("GenreType", ["name", "genre_type"])
class NaturalString(str):
@@ -35,10 +104,10 @@ class NaturalString(str):
'''
def __init__(self, string):
super(NaturalString, self).__init__(string)
super(NaturalString, self).__init__()
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)',
key)]
key)]
self._string_elements = alphanum_key(string)
@@ -239,7 +308,6 @@ class SortedCollection(object):
class ReversedSortedCollection(object):
def __init__(self, sorted_collection):
self._sorted_collection = sorted_collection
@@ -295,7 +363,6 @@ class ReversedSortedCollection(object):
class IdleCallIterator(object):
def __init__(self, chunk, process, after=None, error=None, finish=None):
default = lambda *_: None
@@ -309,7 +376,8 @@ class IdleCallIterator(object):
def __call__(self, iterator, **data):
self._iter = iterator
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._idle_call, data)
#Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._idle_call, data)
GLib.idle_add(self._idle_call, data)
def _idle_call(self, data):
if self._stop:
@@ -317,7 +385,7 @@ class IdleCallIterator(object):
for i in range(self._chunk):
try:
next_elem = self._iter.next()
next_elem = next(self._iter)
self._process(next_elem, data)
except StopIteration:
@@ -345,10 +413,88 @@ def idle_iterator(func):
return iter_function
class SpriteSheet(object):
class Theme:
'''
This class manages the theme details
'''
# storage for the instance reference
__instance = None
class _impl(GObject.Object):
""" Implementation of the singleton interface """
# properties
theme = GObject.property(type=str, default="standard")
# signals
'''
changed = signal emitted when a theme has changed
'''
__gsignals__ = {
'theme_changed': (GObject.SIGNAL_RUN_LAST, None, ())
}
# below public variables and methods that can be called for Theme
def __init__(self, plugin):
'''
Initializes the singleton interface, assigning all the constants
used to access the plugin's settings.
'''
super(Theme._impl, self).__init__()
self.plugin = plugin
popups = rb.find_plugin_file(plugin, 'img/popups.xml')
root = ET.parse(open(popups)).getroot()
base = 'theme/theme'
self.themes = []
for elem in root.xpath(base):
self.themes.append(elem.attrib['folder_name'])
self.gs = GSetting()
self.setting = self.gs.get_setting(self.gs.Path.PLUGIN)
# connect properties and signals
self._connect_properties()
self._connect_signals()
@property
def current(self):
return self.setting[self.gs.PluginKey.THEME]
def _connect_properties(self):
self.setting.bind(self.gs.PluginKey.THEME, self,
'theme', Gio.SettingsBindFlags.GET)
def _connect_signals(self):
self.connect('notify::theme', self._on_theme_changed,
None)
def _on_theme_changed(self, *args):
self.emit('theme_changed')
def __init__(self, plugin):
""" Create singleton instance """
# Check whether we already have an instance
if Theme.__instance is None:
# Create and remember instance
Theme.__instance = Theme._impl(plugin)
# Store instance reference as the only member in the handle
self.__dict__['_Theme__instance'] = Theme.__instance
def __getattr__(self, attr):
""" Delegate access to implementation """
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)
class SpriteSheet(object):
def __init__(self, image, icon_width, icon_height, x_spacing, y_spacing,
x_start, y_start, alpha_color=None, size=None):
x_start, y_start, across_dimension, down_dimension,
alpha_color=None, size=None):
# load the image
base_image = GdkPixbuf.Pixbuf.new_from_file(image)
@@ -360,19 +506,18 @@ class SpriteSheet(object):
self._sprites = []
for y in range(0, ((base_image.get_height() - y_start) / delta_y) + 1):
for x in range(0, ((base_image.get_width() - x_start) / delta_x)
+ 1):
for y in range(0, down_dimension):
for x in range(0, across_dimension):
sprite = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True,
8, icon_width, icon_height)
8, icon_width, icon_height)
base_image.copy_area(x_start + (x * delta_x),
y_start + (y * delta_y), icon_width, icon_height,
sprite, 0, 0)
y_start + (y * delta_y), icon_width, icon_height,
sprite, 0, 0)
if size:
sprite = sprite.scale_simple(size[0], size[1],
GdkPixbuf.InterpType.BILINEAR)
GdkPixbuf.InterpType.BILINEAR)
self._sprites.append(sprite)
@@ -385,22 +530,24 @@ class SpriteSheet(object):
class ConfiguredSpriteSheet(object):
def __init__(self, plugin, sprite_name, size=None):
self.plugin = plugin
popups = rb.find_plugin_file(plugin, 'img/popups.xml')
self.root = ET.parse(open(popups)).getroot()
base = 'spritesheet[@name="' + sprite_name + '"]/'
image = rb.find_plugin_file(plugin, 'img/' +
self.root.xpath(base + 'image')[0].text)
icon_width = int(self.root.xpath(base + 'icon')[0].attrib['width'])
icon_height = int(self.root.xpath(base + 'icon')[0].attrib['height'])
x_spacing = int(self.root.xpath(base + 'spacing')[0].attrib['x'])
y_spacing = int(self.root.xpath(base + 'spacing')[0].attrib['y'])
x_start = int(self.root.xpath(base + 'start-position')[0].attrib['x'])
y_start = int(self.root.xpath(base + 'start-position')[0].attrib['y'])
root = ET.parse(open(popups)).getroot()
base = 'theme/theme[@folder_name="' + Theme(plugin).current \
+ '"]/spritesheet[@name="' + sprite_name + '"]/'
image = rb.find_plugin_file(plugin, 'img/' + Theme(plugin).current \
+ '/' + root.xpath(base + 'image')[0].text)
icon_width = int(root.xpath(base + 'icon')[0].attrib['width'])
icon_height = int(root.xpath(base + 'icon')[0].attrib['height'])
x_spacing = int(root.xpath(base + 'spacing')[0].attrib['x'])
y_spacing = int(root.xpath(base + 'spacing')[0].attrib['y'])
x_start = int(root.xpath(base + 'start-position')[0].attrib['x'])
y_start = int(root.xpath(base + 'start-position')[0].attrib['y'])
across_dimension = int(root.xpath(base + 'dimension')[0].attrib['across'])
down_dimension = int(root.xpath(base + 'dimension')[0].attrib['down'])
try:
alpha_color = map(int,
self.root.xpath(base + 'alpha')[0].text.split(' '))
alpha_color = list(map(int,
root.xpath(base + 'alpha')[0].text.split(' ')))
except:
alpha_color = None
@@ -408,24 +555,27 @@ class ConfiguredSpriteSheet(object):
self.locale_names = {}
cl = CoverLocale()
lang=cl.get_locale()
lang = cl.get_locale()
base = sprite_name + '/' + sprite_name +\
'[@spritesheet="' + sprite_name + '"]'
base = sprite_name + '/' + sprite_name + \
'[@spritesheet="' + sprite_name + '"]'
for elem in self.root.xpath(base + '[not(@xml:lang)]'):
for elem in root.xpath(base + '[not(@xml:lang)]'):
self.names.append(elem.text)
for elem in self.root.xpath(base + '[@xml:lang="' + lang + '"]'):
self.locale_names[elem.text]=elem.attrib['name']
for elem in root.xpath(base + '[@xml:lang="' + lang + '"]'):
self.locale_names[elem.text] = elem.attrib['name']
if (not self.locale_names) and len(lang) > 2:
for elem in self.root.xpath(base + '[@xml:lang="' +\
lang[0:2] + '"]'):
self.locale_names[elem.text]=elem.attrib['name']
for elem in root.xpath(base + '[@xml:lang="' + \
lang[0:2] + '"]'):
self.locale_names[elem.text] = elem.attrib['name']
self._sheet = SpriteSheet(image, icon_width, icon_height, x_spacing,
y_spacing, x_start, y_start, alpha_color, size)
y_spacing, x_start, y_start, across_dimension, down_dimension,
alpha_color, size)
self._genre_db = RB.ExtDB(name='cb_genre')
def __len__(self):
return len(self._sheet)
@@ -439,28 +589,159 @@ class ConfiguredSpriteSheet(object):
def __contains__(self, name):
return name in self.names
def keys(self):
return self.names
class GenreConfiguredSpriteSheet(ConfiguredSpriteSheet):
'''
A sprite-sheet of genres. Creates a pixbuf representation of a picture
that has several icons in a regular pattern. This uses the file
'popups.xml' for its definition
:plugin: rhythmbox plugin
:sprite_name: `str` containing name of the spritesheet pattern in
popups.xml
:size: `int` array dimension of the final sprite which is to be used.
output:
:names: `str` array of sprite names
'''
# types of genre
GENRE_USER = 1
GENRE_SYSTEM = 2
GENRE_LOCALE = 3
def __init__(self, plugin, sprite_name, size=None):
super(GenreConfiguredSpriteSheet, self).__init__(plugin, sprite_name,
size)
self.alternate = {}
self.locale_alternate = {}
size)
self.genre_alternate = {} # contains GenreType tuples
self._alt_icons = {}
self._sprite_name = sprite_name
self._size = size
popups = rb.find_plugin_file(plugin, 'img/popups.xml')
root = ET.parse(open(popups)).getroot()
self._parse_popups(plugin, root, self.GENRE_SYSTEM)
try:
# self._user_popups = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml')
self._user_popups = RB.user_cache_dir() + "/coverart_browser/usericons/popups.xml"
root = ET.parse(open(self._user_popups)).getroot()
self._parse_popups(plugin, root, self.GENRE_USER)
elem = root.xpath(self._sprite_name + '/index')
curr_index = int(elem[0].text)
for index in range(0, curr_index + 1):
key = RB.ExtDBKey.create_lookup('icon', str(index))
icon_location = self._genre_db.lookup(key)
sprite = GdkPixbuf.Pixbuf.new_from_file(icon_location)
if self._size:
sprite = sprite.scale_simple(self._size[0], self._size[1],
GdkPixbuf.InterpType.BILINEAR)
self._alt_icons[str(index)] = sprite
self.names.append(str(index))
except:
pass
def __getitem__(self, name):
try:
return self._alt_icons[name]
except:
return self._sheet[self.names.index(name)]
def _parse_popups(self, plugin, root, genre_type):
icon_names = {}
cl = CoverLocale()
lang=cl.get_locale()
lang = cl.get_locale()
base = sprite_name + '/alt'
for elem in self.root.xpath(base + '[not(@xml:lang)]/alt'):
self.alternate[elem.text] = elem.attrib['genre']
base = self._sprite_name + '/alt'
for elem in root.xpath(base + '[not(@xml:lang)]/alt'):
self.genre_alternate[GenreType(name=elem.text, genre_type=genre_type)] = elem.attrib['genre']
for elem in self.root.xpath(base + '[@xml:lang="' + lang + '"]/alt'):
self.locale_alternate[elem.text] = elem.attrib['genre']
for elem in root.xpath(base + '[@xml:lang="' + lang + '"]/alt'):
self.genre_alternate[GenreType(name=elem.text, genre_type=self.GENRE_LOCALE)] = elem.attrib['genre']
if (not self.locale_alternate) and len(lang) > 2:
for elem in self.root.xpath(base + '[@xml:lang="' +\
lang[0:2] + '"]/alt'):
self.locale_alternate[elem.text] = elem.attrib['genre']
# if (not self.locale_alternate) and len(lang) > 2:
if len(lang) > 2:
for elem in root.xpath(base + '[@xml:lang="' + \
lang[0:2] + '"]/alt'):
self.genre_alternate[GenreType(name=elem.text, genre_type=self.GENRE_LOCALE)] = elem.attrib['genre']
def add_genre_icon(self, filename):
root = ET.parse(open(self._user_popups)).getroot()
elem = root.xpath(self._sprite_name + '/index')
next_index = int(elem[0].text)
elem[0].text = str(next_index + 1)
tree = ET.ElementTree(root)
tree.write(self._user_popups, pretty_print=True, xml_declaration=True)
key = RB.ExtDBKey.create_storage('icon', str(next_index))
uri = "file://" + rb3compat.pathname2url(filename)
self._genre_db.store_uri(key, RB.ExtDBSourceType.USER_EXPLICIT, uri)
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
new_genre = GenreType(name=str(next_index), genre_type=self.GENRE_USER)
if self._size:
pixbuf = pixbuf.scale_simple(self._size[0], self._size[1],
GdkPixbuf.InterpType.BILINEAR)
self._alt_icons[new_genre.name] = pixbuf
self.names.append(new_genre.name)
return new_genre
def delete_genre(self, current_genre):
root = ET.parse(open(self._user_popups)).getroot()
base = self._sprite_name + '/alt/alt'
found = False
for elem in root.xpath(base):
if RB.search_fold(elem.text) == RB.search_fold(current_genre):
found = True
break
if found:
elem.getparent().remove(elem)
tree = ET.ElementTree(root)
tree.write(self._user_popups, pretty_print=True, xml_declaration=True)
else:
print("not found to delete")
def amend_genre_info(self, current_genre, new_genre, icon_name):
root = ET.parse(open(self._user_popups)).getroot()
base = self._sprite_name + '/alt/alt'
found = False
if current_genre != "":
for elem in root.xpath(base):
if RB.search_fold(elem.text) == RB.search_fold(current_genre):
found = True
del self.genre_alternate[GenreType(name=elem.text, genre_type=self.GENRE_USER)]
break
else:
elem = ET.SubElement(root.xpath(self._sprite_name + '/alt')[0], "alt")
if elem != None:
found = True
if found:
elem.text = rb3compat.unicodestr(new_genre, 'utf-8')
elem.attrib['genre'] = icon_name
tree = ET.ElementTree(root)
tree.write(self._user_popups, pretty_print=True, xml_declaration=True)
self.genre_alternate[GenreType(name=elem.text, genre_type=self.GENRE_USER)] = icon_name
return GenreType(name=elem.text, genre_type=self.GENRE_USER)
else:
print("nothing found to amend")
return None
def get_stock_size():
@@ -474,26 +755,84 @@ def create_pixbuf_from_file_at_size(filename, width, height):
if pixbuf.get_width() != width or pixbuf.get_height() != height:
pixbuf = pixbuf.scale_simple(width, height,
GdkPixbuf.InterpType.BILINEAR)
GdkPixbuf.InterpType.BILINEAR)
return pixbuf
'''
class to search through a dict without case-sensitivity nor
unicode vs string issues
'''
class CaseInsensitiveDict(collections.Mapping):
def __init__(self, d):
self._d = d
self._s = dict((RB.search_fold(k), k) for k in d)
def __contains__(self, k):
return RB.search_fold(k) in self._s
def __len__(self):
return len(self._s)
def __iter__(self):
return iter(self._s)
def __getitem__(self, k):
return self._d[self._s[RB.search_fold(k)]]
def actual_key_case(self, k):
return self._s.get(RB.search_fold(k))
def check_lastfm(force_check=False):
'''
check validity of lastfm connection
returns True if connected with an account
Also returns True if lastFM is not in the list of search providers
'''
providers = get_search_providers()
print(providers)
print(force_check)
if force_check or 'lastfm-search' in providers:
connected = lastfm_connected()
print(connected)
return connected
elif not 'lastfm-search' in providers:
print("not lastm-search")
return True
else:
print("returning default")
return False
def create_button_image_symbolic(style_context, icon_name):
'''
create a pixbuf for the given symbolic icon_name sized according to the stock icon size
'''
theme = Gtk.IconTheme()
default = theme.get_default()
iconinfo = default.lookup_icon(icon_name, 128, 0)
pixbuf, symbol = iconinfo.load_symbolic_for_context(style_context)
width, height = get_stock_size()
pixbuf = pixbuf.scale_simple(width, height,
GdkPixbuf.InterpType.BILINEAR)
return pixbuf
def create_button_image(plugin, image_filename):
'''
create a pixbuf for the given image_filename sized according to the stock icon size
'''
path = 'img/'
return create_pixbuf_from_file_at_size(
rb.find_plugin_file(plugin, path + image_filename),
*get_stock_size())
+1191 -86
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+230
Ver Arquivo
@@ -0,0 +1,230 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2014 - fossfreedom
# GTK3 port https://github.com/exaile-dev/exaile/blob/master/xlgui/cover.py
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import GObject
from coverart_browser_prefs import CoverLocale
import rb
class CoverWindow(GObject.Object):
"""Shows the cover in a simple image viewer"""
# signals
__gsignals__ = {
'close-window': (GObject.SIGNAL_RUN_LAST, None, ())
}
def __init__(self, plugin, parent, savedir=None):
"""Initializes and shows the cover
:param plugin: source
:type plugin: RBSource
:param parent: Parent window to attach to
:type parent: Gtk.Window
:param savedir: Initial directory for the Save As functionality
:type savedir: basestring
"""
super(CoverWindow, self).__init__()
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.builder = Gtk.Builder()
self.builder.add_from_file(rb.find_plugin_file(plugin,
'ui/coverart_window.ui'))
self.builder.connect_signals(self)
self.cover_window = self.builder.get_object('CoverWindow')
self.cover_window.connect('destroy', self.send_destroy_signal)
self.layout = self.builder.get_object('layout')
self.toolbar = self.builder.get_object('toolbar')
self.save_as_button = self.builder.get_object('save_as_button')
self.zoom_in_button = self.builder.get_object('zoom_in_button')
self.zoom_out_button = self.builder.get_object('zoom_out_button')
self.zoom_100_button = self.builder.get_object('zoom_100_button')
self.zoom_fit_button = self.builder.get_object('zoom_fit_button')
self.close_button = self.builder.get_object('close_button')
self.image = self.builder.get_object('image')
self.statusbar = self.builder.get_object('statusbar')
self.scrolledwindow = self.builder.get_object('scrolledwindow')
self.scrolledwindow.set_hadjustment(self.layout.get_hadjustment())
self.scrolledwindow.set_vadjustment(self.layout.get_vadjustment())
self.savedir = savedir
if parent:
self.cover_window.set_transient_for(parent)
self.cover_window_width = 500
self.cover_window_height = 500 + self.toolbar.size_request().height + \
self.statusbar.size_request().height
self.cover_window.set_default_size(self.cover_window_width, \
self.cover_window_height)
self.min_percent = 1
self.max_percent = 500
self.ratio = 1.5
self.image_interp = GdkPixbuf.InterpType.BILINEAR
self.image_fitted = True
def send_destroy_signal(self, *args):
self.emit('close-window')
def show_all(self, title, pixbuf):
self.image_original_pixbuf = pixbuf
self.image_pixbuf = self.image_original_pixbuf
self.cover_window.set_title(title)
self.cover_window.show_all()
self.set_ratio_to_fit()
self.update_widgets()
def available_image_width(self):
"""Returns the available horizontal space for the image"""
return self.cover_window.get_size()[0]
def available_image_height(self):
"""Returns the available vertical space for the image"""
return self.cover_window.get_size()[1] - \
self.toolbar.size_request().height - \
self.statusbar.size_request().height
def center_image(self):
"""Centers the image in the layout"""
new_x = max(0, int((self.available_image_width() - \
self.image_pixbuf.get_width()) / 2))
new_y = max(0, int((self.available_image_height() - \
self.image_pixbuf.get_height()) / 2))
self.layout.move(self.image, new_x, new_y)
def update_widgets(self):
"""Updates image, layout, scrolled window, tool bar and status bar"""
# if self.cover_window.window:
# self.cover_window.window.freeze_updates()
self.apply_zoom()
self.layout.set_size(self.image_pixbuf.get_width(), \
self.image_pixbuf.get_height())
if self.image_fitted or \
(self.image_pixbuf.get_width() == self.available_image_width() and \
self.image_pixbuf.get_height() == self.available_image_height()):
self.scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
else:
self.scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
percent = int(100 * self.image_ratio)
message = str(self.image_original_pixbuf.get_width()) + " x " + \
str(self.image_original_pixbuf.get_height()) + \
" pixels " + str(percent) + '%'
self.zoom_in_button.set_sensitive(percent < self.max_percent)
self.zoom_out_button.set_sensitive(percent > self.min_percent)
self.statusbar.pop(self.statusbar.get_context_id(''))
self.statusbar.push(self.statusbar.get_context_id(''), message)
self.image.set_from_pixbuf(self.image_pixbuf)
self.center_image()
#if self.cover_window.window:
# self.cover_window.window.thaw_updates()
def apply_zoom(self):
"""Scales the image if needed"""
new_width = int(self.image_original_pixbuf.get_width() * \
self.image_ratio)
new_height = int(self.image_original_pixbuf.get_height() * \
self.image_ratio)
if new_width != self.image_pixbuf.get_width() or \
new_height != self.image_pixbuf.get_height():
self.image_pixbuf = self.image_original_pixbuf.scale_simple(new_width, \
new_height, self.image_interp)
def set_ratio_to_fit(self):
"""Calculates and sets the needed ratio to show the full image"""
width_ratio = float(self.image_original_pixbuf.get_width()) / \
self.available_image_width()
height_ratio = float(self.image_original_pixbuf.get_height()) / \
self.available_image_height()
self.image_ratio = 1 / max(1, width_ratio, height_ratio)
def on_save_as_button_clicked(self, widget):
"""
Saves image to user-specified location
"""
dialog = Gtk.FileChooserDialog(_("Save File"), self.cover_window,
Gtk.FileChooserAction.SAVE,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT))
filename = 'cover.png'
dialog.set_current_name(filename)
if self.savedir:
dialog.set_current_folder(self.savedir)
if dialog.run() == Gtk.ResponseType.ACCEPT:
filename = dialog.get_filename()
lowfilename = filename.lower()
if lowfilename.endswith('.jpg') or lowfilename.endswith('.jpeg'):
type_ = 'jpeg'
else:
type_ = 'png'
self.image_pixbuf.savev(filename, type_, [None], [None])
dialog.destroy()
def on_zoom_in_button_clicked(self, widget):
"""
Zooms into the image
"""
self.image_fitted = False
self.image_ratio *= self.ratio
self.update_widgets()
def on_zoom_out_button_clicked(self, widget):
"""
Zooms out of the image
"""
self.image_fitted = False
self.image_ratio *= 1 / self.ratio
self.update_widgets()
def on_zoom_100_button_clicked(self, widget):
"""
Restores the original image zoom
"""
self.image_fitted = False
self.image_ratio = 1
self.update_widgets()
def on_zoom_fit_button_clicked(self, widget):
"""
Zooms the image to fit the window width
"""
self.image_fitted = True
self.set_ratio_to_fit()
self.update_widgets()
def on_close_button_clicked(self, widget):
"""
Hides the window
"""
self.cover_window.hide()
def cover_window_size_allocate(self, widget, allocation):
if self.cover_window_width != allocation.width or \
self.cover_window_height != allocation.height:
if self.image_fitted:
self.set_ratio_to_fit()
self.update_widgets()
self.cover_window_width = allocation.width
self.cover_window_height = allocation.height
+81
Ver Arquivo
@@ -0,0 +1,81 @@
/* ContentFlowAddOn_black, version 2.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*
*--------------------------------------------------------------------------*/
/* ========== ContentFlow ========== */
/*
* Within this file you can ajust the styling of ContentFlow
* to your personal needs. The default styling is the same as found on the
* projectpage.
*
*/
.ContentFlowAddOn_black {
background: black;
}
/* ----- styling of items ----- */
.ContentFlowAddOn_black .flow .item .caption {
background: url(img/1x1_0.5_black.png);
}
* html .ContentFlowAddOn_black .flow .item .caption {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/1x1_0.5_black.png');
}
.ContentFlowAddOn_black .flow .item .caption a,
.ContentFlowAddOn_black .flow .item .caption a:link,
.ContentFlowAddOn_black .flow .item .caption a:visited,
.ContentFlowAddOn_black .flow .item .caption a:active,
.ContentFlowAddOn_black .flow .item .caption a:hover {
color: black;
}
/* ----- scrollbar ----- */
.ContentFlowAddOn_black .scrollbar {
background: url(img/scrollbar_white.png) left center repeat-x;
}
.ContentFlowAddOn_black .scrollbar .slider {
background: url(img/slider_white.png) center center no-repeat;
}
/* only for IE <= 6 and a alphatransparent slider image */
* html .ContentFlowAddOn_black .scrollbar .slider { background-image: none; }
* html .ContentFlowAddOn_black .scrollbar .slider .virtualSlider {
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='img/slider_white.png');
}
.ContentFlowAddOn_black .scrollbar .slider .position {
color:silver;
}
/* ----- global caption ----- */
.ContentFlowAddOn_black .globalCaption {
color: white;
}
.ContentFlowAddOn_black .globalCaption .caption a,
.ContentFlowAddOn_black .globalCaption .caption a:link,
.ContentFlowAddOn_black .globalCaption .caption a:visited,
.ContentFlowAddOn_black .globalCaption .caption a:active,
.ContentFlowAddOn_black .globalCaption .caption a:hover {
color: white;
}
/* ----- load indicator ----- */
.ContentFlowAddOn_black .loadIndicator {
background: url(img/1x1_0.5_black.png);
}
* html .ContentFlowAddOn_black .loadIndicator {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/1x1_0.5_black.png');
}
.ContentFlowAddOn_black .loadIndicator .indicator {
background: url(img/loader_black.gif) center center no-repeat;
}
/* ================================= */
+19
Ver Arquivo
@@ -0,0 +1,19 @@
/* ContentFlowAddOn_black, version 2.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*/
new ContentFlowAddOn ('black', {
init: function () {
this.addStylesheet();
},
ContentFlowConf: {
reflectionColor: "#000000" // none, transparent, overlay or hex RGB CSS style #RRGGBB
}
});
+65
Ver Arquivo
@@ -0,0 +1,65 @@
/* ContentFlowAddOn_carousel, version 1.1
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*
*--------------------------------------------------------------------------*/
.ContentFlowAddOn_carousel {
border: 5px solid #767676;
margin: 0 25px;
padding: 25px 0px;
overflow: visible;
/*overflow: hidden;*/
}
.ContentFlowAddOn_carousel #preButton,
.ContentFlowAddOn_carousel #nextButton {
position: absolute;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
}
.ContentFlowAddOn_carousel #preButton {
background: url(img/pre_h.png) center no-repeat;
left: -25px;
left: -28px;
}
* html .ContentFlowAddOn_carousel #preButton {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/pre_h.png');
}
.ContentFlowAddOn_carousel #preButton:hover {
background: url(img/pre_h.png) center no-repeat;
}
.ContentFlowAddOn_carousel #nextButton {
background: url(img/next_h.png) center no-repeat;
right: -25px;
right: -28px;
}
* html .ContentFlowAddOn_carousel #nextButton {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/next_h.png');
}
.ContentFlowAddOn_carousel #nextButton:hover {
background: url(img/next_h.png) center no-repeat;
}
/* ----- styling of items ----- */
.ContentFlowAddOn_carousel .showCaption .item .caption {
display: block !important;
}
/* ----- global caption ----- */
.ContentFlowAddOn_carousel .globalCaption {
margin-top: -3em;
}
/* ================================= */
+131
Ver Arquivo
@@ -0,0 +1,131 @@
/* ContentFlowAddOn_carousel, version 1.1
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*/
new ContentFlowAddOn ('carousel', {
conf: {
shownItems: 3,
showCaption: true,
width: 75,
height: 75,
space:0.0
},
init: function() {
this.addStylesheet();
},
onloadInit: function (flow) {
},
afterContentFlowInit: function (flow) {
var SI = flow.getAddOnConf('carousel').shownItems;
var c = flow.Container;
var ac = flow.getAddOnConf('carousel')
if (ac.showCaption) {
$CF(flow.Flow).addClassName('showCaption');
}
var p = document.createElement('div');
p.id = "preButton";
var pre = function () {
var item = flow._activeItem;
for (var i=0; i< SI; i++) { item = item.pre; }
flow.moveToItem(item);
}
p.onclick = pre;
c.appendChild(p);
var n = document.createElement('div');
n.id = "nextButton";
var next = function () {
var item = flow._activeItem;
for (var i=0; i< SI; i++) { item = item.next; }
flow.moveToItem(item);
}
n.onclick = next;
c.appendChild(n);
flow.Flow.style.fontSize = 12*(flow.maxHeight / 150) +"px";
if (flow.Browser.IE) {
window.setTimeout(function () {flow.Flow.style.overflow = "hidden"}, 1000);
}
else {
flow.Flow.style.overflow = "hidden";
}
flow.setConfig({visibleItems: Math.ceil((flow.getAddOnConf('carousel').shownItems - 1)/2) + 1});
},
ContentFlowConf: {
scaleFactorLandscape: "max", // scale factor of landscape images ('max' := height= maxItemHeight)
scaleFactorPortrait: "max",
fixItemSize: true,
relativeItemPosition: "center", // align top/above, bottom/below, left, right, center of position coordinate
visibleItems: 2, // how man item are visible on each side (-1 := auto)
reflectionHeight: 0, // float (relative to original image height)
/* ==================== actions ==================== */
onclickInactiveItem : function (item) {
this.conf.onclickActiveItem(item);
return false;
},
/* ==================== calculations ==================== */
calcStepWidth: function(diff) {
var vI = this.conf.visibleItems;
var items = this.items.length;
items = items == 0 ? 1 : items;
var absDiff = Math.abs(diff);
if (absDiff > vI) {
if (diff > 0) {
var stepwidth = diff - vI;
} else {
var stepwidth = diff + vI;
}
} else if (vI >= items) {
var stepwidth = diff / items;
} else {
var c = this.getAddOnConf('carousel');
var f = 0.1 * 2/3 * c.shownItems * diff/absDiff;
var d = diff * ( vI / items);
var stepwidth = absDiff > 0.1 ? f : d*8;
}
return stepwidth;
},
calcSize: function (item) {
var c = this.getAddOnConf('carousel');
var a = c.width / c.height;
//if (this.conf.verticalFlow) a = 1/a;
var h = 3/c.shownItems / a;
//if (this.conf.verticalFlow) h *= 2/3;
var w = h * a;
return {width: w, height: h};
},
calcCoordinates: function (item) {
var rP = item.relativePosition;
var c = this.getAddOnConf('carousel');
var w = item.size.width;
//if (this.conf.verticalFlow) w = item.size.height;
var x = rP*w/2*(1 + c.space) *this.conf.scaleFactor - w* (c.shownItems % 2 ? 0 : 0.5) / 1.4;
if (this.conf.verticalFlow) x *= 2*2/3;
var y = 0;
return {x: x, y: y};
}
}
});
+52
Ver Arquivo
@@ -0,0 +1,52 @@
/* ContentFlowAddOn_roundabout, version 3.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*/
new ContentFlowAddOn ('roundabout', {
ContentFlowConf: {
circularFlow: true,
visibleItems: -1,
relativeItemPosition: "top center",
endOpacity: 0.5,
/*
* calculates the size of the item at its relative position x
* returns a size object
*/
calcSize: function (item) {
var rP = item.relativePosition;
//var rPN = relativePositionNormed;
//var vI = rPN != 0 ? rP/rPN : 0 ; // visible Items
var h = 1/(Math.abs(rP)+1);
var w = h;
return {width: w, height: h};
},
/*
* calculates the position of an item within the flow element
* returns a vector object
*/
calcCoordinates: function (item) {
var rP = item.relativePosition;
var rPN = item.relativePositionNormed;
var vI = rPN != 0 ? rP/rPN : 0 ; // visible Items
var f = 1 - 1/Math.exp( Math.abs(rP)*0.75);
var x = item.side * vI/(vI+1)* f;
var y = 1;
var f = Math.sin(Math.PI * (rP*(1+1/(rP*rP+1))) / (vI+1));
var x = vI/(vI+1)* f;
var y = 1 - Math.abs(rP)*1.5/(vI+1);
return {x: x, y: y};
}
}
});
+31
Ver Arquivo
@@ -0,0 +1,31 @@
/* ContentFlowAddOn_vertical, version 2.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*/
new ContentFlowAddOn ('vertical', {
ContentFlowConf: {
relativeItemPosition: "center", // top, bottom, left, right, center
verticalFlow: true, // turn ContentFlow 90 degree counterclockwise
reflectionHeight: 0,
calcCoordinates: function (item) {
var rP = item.relativePosition;
var rPN = item.relativePositionNormed;
var vI = rPN != 0 ? rP/rPN : 0 ; // visible Items
var f = 1 - 1/Math.exp( Math.abs(rP)*0.75);
var x = item.side * vI/(vI+1)* f;
var y = 0;
return {x: x, y: y};
}
}
});
+80
Ver Arquivo
@@ -0,0 +1,80 @@
/* ContentFlowAddOn_white, version 2.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*
*--------------------------------------------------------------------------*/
/* ========== ContentFlow ========== */
/*
* Within this file you can ajust the styling of ContentFlow
* to your personal needs. The default styling is the same as found on the
* projectpage.
*
*/
.ContentFlowAddOn_white {
background: white;
}
/* ----- styling of items ----- */
.ContentFlowAddOn_white .flow .item .caption {
background: url(img/1x1_0.5_white.png);
}
* html .ContentFlowAddOn_white .flow .item .caption {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/1x1_0.5_white.png');
}
.ContentFlowAddOn_white .flow .item .caption a,
.ContentFlowAddOn_white .flow .item .caption a:link,
.ContentFlowAddOn_white .flow .item .caption a:visited,
.ContentFlowAddOn_white .flow .item .caption a:active,
.ContentFlowAddOn_white .flow .item .caption a:hover {
color: black;
}
/* ----- scrollbar ----- */
.ContentFlowAddOn_white .scrollbar {
background: url(img/scrollbar_black.png) left center repeat-x;
}
.ContentFlowAddOn_white .scrollbar .slider {
background: url(img/slider_black.png) center center no-repeat;
}
/* only for IE <= 6 and a alphatransparent slider image */
* html .ContentFlowAddOn_white .scrollbar .slider { background-image: none; }
* html .ContentFlowAddOn_white .scrollbar .slider .virtualSlider {
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='img/slider_black.png');
}
.ContentFlowAddOn_white .scrollbar .slider .position {
color:gray;
}
/* ----- global caption ----- */
.ContentFlowAddOn_white .globalCaption {
color: black;
}
.ContentFlowAddOn_white .globalCaption .caption a,
.ContentFlowAddOn_white .globalCaption .caption a:link,
.ContentFlowAddOn_white .globalCaption .caption a:visited,
.ContentFlowAddOn_white .globalCaption .caption a:active,
.ContentFlowAddOn_white .globalCaption .caption a:hover {
color: black;
}
/* ----- load indicator ----- */
.ContentFlowAddOn_white .loadIndicator {
background: url(img/1x1_0.5_white.png);
}
* html .ContentFlowAddOn_white .loadIndicator {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/1x1_0.5_white.png');
}
.ContentFlowAddOn_white .loadIndicator .indicator {
background: url(img/loader_white.gif) center center no-repeat;
}
/* ================================= */
+19
Ver Arquivo
@@ -0,0 +1,19 @@
/* ContentFlowAddOn_white, version 2.0
* (c) 2008 - 2010 Sebastian Kutsch
* <http://www.jacksasylum.eu/ContentFlow/>
*
* This file is distributed under the terms of the MIT license.
* (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
*/
new ContentFlowAddOn ('white', {
init: function () {
this.addStylesheet();
},
ContentFlowConf: {
reflectionColor: "#ffffff" // none, transparent, overlay or hex RGB CSS style #RRGGBB
}
});
+19
Ver Arquivo
@@ -0,0 +1,19 @@
Copyright (c) 2007 - 2010 Sebastian Kutsch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+250
Ver Arquivo
@@ -0,0 +1,250 @@
/* ========== ContentFlow ========== */
/*
* default style to look nice
*/
.ContentFlow {
}
.ContentFlow .flow {
/*border: 1px solid green;*/
}
.ContentFlow .flow * {
}
.ContentFlow .flow .item {
/*border: 1px solid red;*/
}
.ContentFlow .flow .item canvas.content {
height: 100%;
width: 100%;
/*border: 1px solid yellow;*/
}
.ContentFlow .flow .item img.content {
/*border: 1px solid yellow;*/
width: 100%;
}
.ContentFlow .flow .item img.reflection,
.ContentFlow .flow .item canvas.reflection {
width: 100%;
}
/* ----- styling of items ----- */
.ContentFlow .flow .item.active {
cursor: pointer;
}
.ContentFlow .flow .item .caption {
font-size: 100%;
font-weight: bold;
text-align: center;
color: white;
max-height: 30%;
bottom: 10%;
background: url(img/1x1_0.5_black.png);
width: 100%;
}
* html .ContentFlow .flow .item .caption {
background-image: none;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src='img/1x1_0.5_black.png');
}
.ContentFlow .flow .item .caption a,
.ContentFlow .flow .item .caption a:link,
.ContentFlow .flow .item .caption a:visited,
.ContentFlow .flow .item .caption a:active,
.ContentFlow .flow .item .caption a:hover {
text-decoration: none;
color: white;
font-style: italic;
font-size: 0.8em;
}
.ContentFlow .flow .item .caption a:hover {
text-decoration: underline;
}
.ContentFlow .flow .item.active .caption {
/*display: block;*/ /* uncomment to show caption inside item */
}
/* ----- scrollbar ----- */
.ContentFlow .scrollbar {
width: 50%;
margin: 0px auto;
margin-top: 10px;
height: 16px;
background: url(img/scrollbar_white.png) left center repeat-x;
position: relative;
overflow: visible;
}
.ContentFlow .scrollbar .slider {
width: 16px;
height: 16px;
background: url(img/slider_white.png) center center no-repeat;
cursor: move;
}
/* only for IE <= 6 and a alphatransparent slider image */
* html .ContentFlow .scrollbar .slider { background-image: none; }
* html .ContentFlow .scrollbar .slider .virtualSlider {
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='img/slider_white.png');
}
.ContentFlow .scrollbar .slider .position {
top: 120%;
font-size: 16px;
font-weight: bold;
color: silver
}
/* ----- global caption ----- */
.ContentFlow .globalCaption {
text-align: center;
font-weight: bold;
color: white;
font-size: 14px;
height: 20px;
margin: 2em auto;
}
.ContentFlow .globalCaption .caption {
}
.ContentFlow .globalCaption .caption a,
.ContentFlow .globalCaption .caption a:link,
.ContentFlow .globalCaption .caption a:visited,
.ContentFlow .globalCaption .caption a:active,
.ContentFlow .globalCaption .caption a:hover {
text-decoration: none;
color: white;
font-style: italic;
font-size: 0.8em;
}
.ContentFlow .globalCaption .caption a:hover {
text-decoration: underline;
}
/* ----- load indicator ----- */
.ContentFlow .loadIndicator {
width: 100%;
height: 100%;
top: 0px;
left: 0px;
background: black;
}
.ContentFlow .loadIndicator .indicator {
background: url(img/loader.gif) center center no-repeat;
width: 100%;
height: 100%;
}
* html .ContentFlow .loadIndicator .indicator {
height: 100px;
}
/* ================================= */
/* ========== ContentFlow ========== */
/*
* This is the basic CSS file needed for the correct functioning of ContentFlow.
* DON'T CHANGE IT.
*
*/
.ContentFlow {
position: relative; /* needed so overlay dimensions are constrained to the ContentFlow */
overflow: hidden;
}
.ContentFlow * {
margin: 0px;
padding: 0px;
/*border: none;*/
}
.ContentFlow img {
-ms-interpolation-mode: bicubic;
}
.ContentFlow .mouseoverCheckElement {
position: absolute;
width: 0px;
height: 0px;
left: 0px;
/*display: none;*/
visibility: hidden;
}
.ContentFlow:hover .mouseoverCheckElement {
left: 1px;
/*width: 1px;*/
/*left: -1px;*/
/*background-color: red;*/
}
.ContentFlow .flow {
position: relative; /* needed so that items can be positioned relative to flow*/
z-index: 0; /* need so every item has a z-index relative to the flow-box */
visibility: hidden; /* needed so that content is hidden while loading */
width: 100%; /* needed for IE6 */
margin: 0 auto;
}
.ContentFlow .flow.hidden {
visibility: hidden;
}
.ContentFlow .flow .item {
position: absolute; /* needed */
visibility: hidden;
top: 0px;
left: 0px;
}
.ContentFlow .flow .item.active {
}
.ContentFlow .flow .item .content {
display: block;
}
.ContentFlow .flow .item div.content {
width: 100%;
height: 100%;
}
.ContentFlow .flow .item .label {
display: none;
}
.ContentFlow .flow .item .reflection {
display: block;
}
.ContentFlow .flow .item canvas.reflection {
margin-top: -1px; /* for FF */
}
.ContentFlow .flow .item .caption {
position: absolute; /* needed */
display: none; /* needed to hide it on inactive items */
}
.ContentFlow .flow .item.active .caption {
/*display: block;*/ /* uncomment to show caption inside item */
}
/* ----- scrollbar ----- */
.ContentFlow .scrollbar {
position: relative; /* needed for z-index */
z-index: 1; /* set above flow */
visibility: hidden;
}
.ContentFlow .scrollbar .slider {
position: absolute; /* needed */
}
* html .ContentFlow .scrollbar .slider .virtualSlider {
height: 100%;
}
.ContentFlow .scrollbar .slider .position {
position: absolute; /* needed */
text-align: center;
}
/* ----- global caption ----- */
.ContentFlow .globalCaption {
position: relative; /* needed for z-index */
z-index: 1; /* set above flow */
}
/* ----- load indicator ----- */
.ContentFlow .loadIndicator {
position: absolute; /* needed */
z-index: 65000; /* set above everything */
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+44
Ver Arquivo
@@ -0,0 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<link rel="stylesheet" title="Standard" href="styles.css" type="text/css" media="screen" />
<!--<style>
</style> -->
<style type="text/css">
.className{
width:270px;
height:150px;
position:absolute;
left:50%;
top:50%;
margin:-75px 0 0 -135px;
}
.className p{
font-size:22px;
margin:45px 10px 10px;
color: #BACKGROUND_COLOUR;
text-align:center;
position:absolute;
}
body{
background: #BACKGROUND_COLOUR;
font-size:0.825em;
font-family:Arial, Helvetica, sans-serif;
}
</style>
</head>
<body>
<div class="className">
<p>Use the Search and Filter options to display covers
</p>
</div>
</body>
</html>
Arquivo binário não exibido.
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 82 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 82 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 43 B

+241
Ver Arquivo
@@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.47 r22583"
version="1.1"
sodipodi:docname="New document 1.2009_11_04_18_16_23.0.2009_11_29_20_35_24.0.svg">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient3612">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3614" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3616" />
</linearGradient>
<linearGradient
id="linearGradient3157">
<stop
style="stop-color:#ffffff;stop-opacity:0.57391304;"
offset="0"
id="stop3159" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3161" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3157"
id="radialGradient3163"
cx="307.14285"
cy="582.36218"
fx="307.14285"
fy="582.36218"
r="158.57143"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3157"
id="radialGradient3187"
gradientUnits="userSpaceOnUse"
cx="307.14285"
cy="582.36218"
fx="307.14285"
fy="582.36218"
r="158.57143" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3195">
<path
sodipodi:type="arc"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path3197"
sodipodi:cx="268.57144"
sodipodi:cy="569.50507"
sodipodi:rx="171.42857"
sodipodi:ry="171.42857"
d="M 440.00002,569.50507 A 171.42857,171.42857 0 1 1 97.142868,569.50507 A 171.42857,171.42857 0 1 1 440.00002,569.50507 z"
transform="matrix(0.5151267,0,0,0.5151267,47.101979,154.22678)" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3205">
<path
sodipodi:type="arc"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path3207"
sodipodi:cx="268.57144"
sodipodi:cy="569.50507"
sodipodi:rx="171.42857"
sodipodi:ry="171.42857"
d="M 440.00002,569.50507 A 171.42857,171.42857 0 1 1 97.142868,569.50507 A 171.42857,171.42857 0 1 1 440.00002,569.50507 z"
transform="matrix(0.5151267,0,0,0.5151267,385.50521,158.17142)" />
</clipPath>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3612"
id="radialGradient3618"
cx="307.14285"
cy="582.36218"
fx="307.14285"
fy="582.36218"
r="158.57143"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3612"
id="radialGradient3639"
gradientUnits="userSpaceOnUse"
cx="307.14285"
cy="582.36218"
fx="307.14285"
fy="582.36218"
r="158.57143" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-159.28571"
inkscape:cy="405.71429"
inkscape:document-units="px"
inkscape:current-layer="g3620"
showgrid="false"
inkscape:window-width="1400"
inkscape:window-height="1002"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3620">
<g
id="g2835"
transform="matrix(-1,0,0,1,370.9006,0)">
<path
d="m 440.00002,569.50507 c 0,94.67738 -76.75119,171.42857 -171.42858,171.42857 -94.67739,0 -171.428572,-76.75119 -171.428572,-171.42857 0,-94.67739 76.751182,-171.42858 171.428572,-171.42858 94.67739,0 171.42858,76.75119 171.42858,171.42858 z"
sodipodi:ry="171.42857"
sodipodi:rx="171.42857"
sodipodi:cy="569.50507"
sodipodi:cx="268.57144"
id="path3173"
style="fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:none"
sodipodi:type="arc"
transform="matrix(0.5151267,0,0,0.5151267,47.101979,154.22678)" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 193.02128,416.31621 0,17.11698 -69.31988,0 0,28.32175 69.31988,0 0,17.11698 27.09543,-15.63248 27.08251,-15.64537 -27.08251,-15.63248 -27.09543,-15.64538 z"
id="rect3165" />
<path
d="m 440.00002,569.50507 c 0,94.67738 -76.75119,171.42857 -171.42858,171.42857 -94.67739,0 -171.428572,-76.75119 -171.428572,-171.42857 0,-94.67739 76.751182,-171.42858 171.428572,-171.42858 94.67739,0 171.42858,76.75119 171.42858,171.42858 z"
sodipodi:ry="171.42857"
sodipodi:rx="171.42857"
sodipodi:cy="569.50507"
sodipodi:cx="268.57144"
id="path3175"
style="fill:none;stroke:#808080;stroke-width:10.80000019;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
transform="matrix(0.4679068,0,0,0.4679068,59.783914,181.11879)" />
</g>
<path
transform="matrix(0.5847384,0,0,0.5847384,30.872504,70.269726)"
d="m 465.71428,582.36218 a 158.57143,158.57143 0 1 1 -317.14285,0 158.57143,158.57143 0 1 1 317.14285,0 z"
sodipodi:ry="158.57143"
sodipodi:rx="158.57143"
sodipodi:cy="582.36218"
sodipodi:cx="307.14285"
id="path2385"
style="fill:url(#radialGradient3618);fill-opacity:1;fill-rule:evenodd;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="g3627"
transform="translate(314.28571,5.7142857)">
<g
id="g3629">
<path
transform="matrix(0.5151267,0,0,0.5151267,47.101979,154.22678)"
sodipodi:type="arc"
style="fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path3631"
sodipodi:cx="268.57144"
sodipodi:cy="569.50507"
sodipodi:rx="171.42857"
sodipodi:ry="171.42857"
d="m 440.00002,569.50507 c 0,94.67738 -76.75119,171.42857 -171.42858,171.42857 -94.67739,0 -171.428572,-76.75119 -171.428572,-171.42857 0,-94.67739 76.751182,-171.42858 171.428572,-171.42858 94.67739,0 171.42858,76.75119 171.42858,171.42858 z" />
<path
id="path3633"
d="m 193.02128,416.31621 0,17.11698 -69.31988,0 0,28.32175 69.31988,0 0,17.11698 27.09543,-15.63248 27.08251,-15.64537 -27.08251,-15.63248 -27.09543,-15.64538 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<path
transform="matrix(0.4679068,0,0,0.4679068,59.783914,181.11879)"
sodipodi:type="arc"
style="fill:none;stroke:#808080;stroke-width:10.80000019;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3635"
sodipodi:cx="268.57144"
sodipodi:cy="569.50507"
sodipodi:rx="171.42857"
sodipodi:ry="171.42857"
d="m 440.00002,569.50507 c 0,94.67738 -76.75119,171.42857 -171.42858,171.42857 -94.67739,0 -171.428572,-76.75119 -171.428572,-171.42857 0,-94.67739 76.751182,-171.42858 171.428572,-171.42858 94.67739,0 171.42858,76.75119 171.42858,171.42858 z" />
</g>
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3639);fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path3637"
sodipodi:cx="307.14285"
sodipodi:cy="582.36218"
sodipodi:rx="158.57143"
sodipodi:ry="158.57143"
d="m 465.71428,582.36218 c 0,87.57658 -70.99485,158.57143 -158.57143,158.57143 -87.57658,0 -158.57142,-70.99485 -158.57142,-158.57143 0,-87.57658 70.99484,-158.57142 158.57142,-158.57142 87.57658,0 158.57143,70.99484 158.57143,158.57142 z"
transform="matrix(0.5847384,0,0,0.5847384,30.872504,70.269726)" />
</g>
</g>
</svg>

Depois

Largura:  |  Altura:  |  Tamanho: 9.2 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 8.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 8.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 8.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.9 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 82 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 82 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 175 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 178 B

+55
Ver Arquivo
@@ -0,0 +1,55 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<link rel="stylesheet" title="Standard" href="styles.css" type="text/css" media="screen" />
<!-- styles.css contains white for background colour - we'll need an equivalent css statement for a black background -->
<script language="JavaScript" type="text/javascript" src="contentflow.js" load="#BACKGROUND_COLOUR #ADDON"></script>
<script style="text/javascript">
var cf = new ContentFlow('contentFlow',
{reflectionColor: "#000000",
endOpacity : 0.3,
circularFlow: true,
startItem: #START,
scaleFactor: #FACTOR,
visibleItems: 7
});
</script>
<style>
body{
background: #BACKGROUND_COLOUR;
color: #FOREGROUND_COLOUR;
}
</style>
</head>
<body>
#MAXCOVERS
<div class="maincontent">
<div style="width: #WIDTHpx; margin: 0px auto;">
<!-- ===== FLOW ===== -->
<div id="contentFlow" class="ContentFlow">
<!-- should be place before flow so that contained images will be loaded first -->
<div class="loadIndicator"><div class="indicator"></div></div>
<div class="flow">
#ITEMS
</div>
#GLOBAL_CAPTION
<!--
<div class="scrollbar">
<div class="slider"></div>
</div>
-->
</div>
</div>
</div>
</body>
</html>
+231
Ver Arquivo
@@ -0,0 +1,231 @@
body {
background: white;
color: white;
font-size: 8pt;
font-family: sans-serif;
margin: 0;
padding: 0 7%;
}
.className{
background-color:#338BC7;
width:270px;
height:150px;
position:relative;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
}
a,
a:link,
a:visited,
a:active,
a:hover {
text-decoration: none;
color: white;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
.clear { clear: both; }
img { border: none; }
h1 {
text-align: center;
text-decoration: underline;
font-family: serif;
color: white;
font-size: 1.8em;
}
h2 {
margin-top: 2.5em;
font-size: 1.7em;
text-decoration: underline;
}
h3 {
margin-top: 2em;
font-size: 1.3em;
text-decoration: underline;
}
p {
line-height: 150%;
text-align: justify;
}
pre {
font-family: sans-serif;
background: #222;
}
.simpleBlack {
background: black;
width: 80ex;
margin: 0 auto;
font-family: sans-serif;
}
code {
font-family: monospace;
font-size: 0.9em;
line-height: 150%;
}
dl {
margin-left: 4ex;
}
dl dt {
font-size: 0.9em;
font-weight: bold;
color: #eee;
font-family: monospace;
}
dl dd {
margin-bottom: 1.5em;
color: #ccc;
font-size: 0.85em;
line-height: 1.5em;
}
ol {
}
ol li {
margin-bottom: 1em;
line-height: 150%;
}
ul {
}
ul li {
margin-bottom: 0;
}
/* ------------------------------------------------------------------------- */
#title {
margin: 0px auto;
}
#sponsor {
position: absolute;
right: 7%;
font-size: 10px;
height: 66px;
line-height: 66px;
padding: 0 20px;
}
div.maincontent {
margin: 0px auto 20pt auto;
}
#menu {
display: block;
text-align: center;
background: #222;
padding: 1em;
margin: 2em auto 4em auto;
}
#menu li {
display: inline;
padding: 0px 20px;
}
.totop {
text-align: center;
margin: 4em 10%;
padding: 0.25em;
background: #161616;
}
.totop a {
margin-right: 7%;
color: silver;
}
.block {
display: none;
}
#browserComp {
border-collapse: collapse;
margin: 40px auto;
}
#browserComp caption {
text-align: center;
margin: 20px auto;
font-size: 1.1em;
font-weight: bold;
}
#browserComp th {
font-size: 10px;
padding: 5px 10px;
border-bottom: 1px solid silver;
}
#browserComp th img {
width: 22px;
}
#browserComp td {
text-align: center;
}
#browserComp th.feature,
#browserComp td.feature {
text-align: right;
padding: 5px;
padding-right: 10px;
border-right: 1px solid silver;
}
.addon {
border: 1px solid #222;
padding: 20px;
margin-bottom: 20px;
background: #222;
}
.addon .flowBox {
width: 500px;
margin-right: 40px;
float: left;
}
.addon .discription {
margin-left: 540px;
}
.addon .title {
line-height: 1.3em;
}
.addon .title h3 {
margin: 0;
display: inline;
}
.addon .title .by {
margin-left: 2em;
font-size: 0.8em;
text-decoration: none;
}
.addon p {
font-size: 0.9em;
}
.addon .comment {
margin-top: 2em;
font-style: italic;
}
.addon .download {
font-size: 0.8em;
float: right;
margin: 0 0 2em 4em;
line-height: 1.5em;
}
.addon .download a {
font-weight: normal;
}
#info { /* for example.php */
color: white;
/*display: none;*/
}
-329
Ver Arquivo
@@ -1,329 +0,0 @@
__version_info__ = (1,1,1)
__version__ = '1.1.1'
import requests
import json
import urllib
import httplib
from collections import defaultdict
from coverart_browser_prefs import GSetting
api_uri = 'http://api.discogs.com'
user_agent = None
class APIBase(object):
def __init__(self):
self._cached_response = None
self._params = {}
self._headers = { 'accept-encoding': 'gzip, deflate' }
def __str__(self):
return '<%s "%s">' % (self.__class__.__name__, self._id)
def __repr__(self):
return self.__str__().encode('utf-8')
def _check_user_agent(self):
if 'user_agent' in globals() and user_agent is not None:
self._headers['user-agent'] = user_agent
return 'user-agent' in self._headers and self._headers.get('user-agent')
def _clear_cache(self):
self._cached_response = None
@property
def _response(self):
if not self._cached_response:
if not self._check_user_agent():
raise DiscogsAPIError, 'Invalid or no User-Agent set'
try:
#gs = GSetting()
#setting = gs.get_setting(gs.Path.PLUGIN)
#type_val = setting[gs.PluginKey.PROXY_TYPE]
#if type_val == 0:
# type_name = 'http'
#elif type_val == 1:
# type_name = 'https'
#elif type_val == 2:
# type_name = 'ftp'
#proxy_name = setting[gs.PluginKey.PROXY_VALUE]
proxydict = {'http':''}
#proxydict[type_name] = proxy_name
self._cached_response = requests.get(self._uri, params=self._params, headers=self._headers, proxies=proxydict)
except:
raise DiscogsAPIError, 'bad response'
return self._cached_response
@property
def _uri_name(self):
return self.__class__.__name__.lower()
@property
def _uri(self):
return '%s/%s/%s' % (api_uri, self._uri_name, urllib.quote(unicode(self._id).encode('utf-8')))
@property
def data(self):
if self._response.content and self._response.status_code == 200:
release_json = json.loads(self._response.content)
return release_json.get('resp').get(self._uri_name)
else:
status_code = self._response.status_code
raise DiscogsAPIError, '%s %s' % (status_code, httplib.responses[status_code])
class DiscogsAPIError(BaseException):
pass
def _parse_credits(extraartists):
"""
Parse release and track level credits
"""
_credits = defaultdict(list)
for artist in extraartists:
role = artist.get('role')
tracks = artist.get('tracks')
artist_dict = {'artists': Artist(artist['name'], anv=artist.get('anv'))}
if tracks:
artist_dict['tracks'] = tracks
_credits[role].append(artist_dict)
return _credits
def _class_from_string(api_string):
class_map = {
'master': MasterRelease,
'release': Release,
'artist': Artist,
'label': Label
}
return class_map[api_string]
class Artist(APIBase):
def __init__(self, name, anv=None):
self._id = name
self._aliases = []
self._namevariations = []
self._releases = []
self._anv = anv or None
APIBase.__init__(self)
def __str__(self):
return '<%s "%s">' % (self.__class__.__name__, self._anv + '*' if self._anv else self._id)
@property
def name(self):
return self._id
@property
def anv(self):
return self._anv
@property
def aliases(self):
if not self._aliases:
for alias in self.data.get('aliases', []):
self._aliases.append(Artist(alias))
return self._aliases
@property
def releases(self):
# TODO: Implement fetch many release IDs
#return [Release(r.get('id') for r in self.data.get('releases')]
if not self._releases:
self._params.update({'releases': '1'})
self._clear_cache()
for r in self.data.get('releases', []):
self._releases.append(_class_from_string(r['type'])(r['id']))
return self._releases
class Release(APIBase):
def __init__(self, id):
self._id = id
self._artists = []
self._master = None
self._labels = []
self._credits = None
self._tracklist = []
APIBase.__init__(self)
@property
def artists(self):
if not self._artists:
self._artists = [Artist(a['name']) for a in self.data.get('artists', [])]
return self._artists
@property
def master(self):
if not self._master and self.data.get('master_id'):
self._master = MasterRelease(self.data.get('master_id'))
return self._master
@property
def labels(self):
if not self._labels:
self._labels = [Label(l['name']) for l in self.data.get('labels', [])]
return self._labels
@property
def credits(self):
if not self._credits:
self._credits = _parse_credits(self.data.get('extraartists', []))
return self._credits
@property
def tracklist(self):
if not self._tracklist:
for track in self.data.get('tracklist', []):
artists = []
track['extraartists'] = _parse_credits(track.get('extraartists', []))
for artist in track.get('artists', []):
artists.append(Artist(artist['name'], anv=artist.get('anv')))
if artist['join']:
artists.append(artist['join'])
track['artists'] = artists
track['type'] = 'Track' if track['position'] else 'Index Track'
self._tracklist.append(track)
return self._tracklist
@property
def title(self):
return self.data.get('title')
class MasterRelease(APIBase):
def __init__(self, id):
self._id = id
self._key_release = None
self._versions = []
self._artists = []
APIBase.__init__(self)
# Override class name introspection in BaseAPI since it would otherwise return "masterrelease"
@property
def _uri_name(self):
return 'master'
@property
def key_release(self):
if not self._key_release:
self._key_release = Release(self.data.get('main_release'))
return self._key_release
@property
def title(self):
return self.key_release.data.get('title')
@property
def versions(self):
if not self._versions:
for version in self.data.get('versions', []):
self._versions.append(Release(version.get('id')))
return self._versions
@property
def artists(self):
if not self._artists:
for artist in self.data.get('artists', []):
self._artists.append(Artist(artist.get('name')))
return self._artists
@property
def tracklist(self):
return self.key_release.tracklist
class Label(APIBase):
def __init__(self, name):
self._id = name
self._sublabels = []
self._parent_label = None
APIBase.__init__(self)
@property
def sublabels(self):
if not self._sublabels:
for sublabel in self.data.get('sublabels', []):
self._sublabels.append(Label(sublabel))
return self._sublabels
@property
def parent_label(self):
if not self._parent_label and self.data.get('parentLabel'):
self._parent_label = Label(self.data.get('parentLabel'))
return self._parent_label
@property
def releases(self):
self._params.update({'releases': '1'})
self._clear_cache()
return self.data.get('releases')
class Search(APIBase):
def __init__(self, query, page=1):
self._id = query
self._results = {}
self._exactresults = []
self._page = page
APIBase.__init__(self)
self._params['q'] = self._id
self._params['page'] = self._page
def _to_object(self, result):
id = result['title']
if result['type'] in ('master', 'release'):
id = result['uri'].split('/')[-1]
elif result['type'] == 'artist':
return Artist(id, anv=result.get('anv'))
return _class_from_string(result['type'])(id)
@property
def _uri(self):
return '%s/%s' % (api_uri, self._uri_name)
@property
def exactresults(self):
if not self.data:
return []
if not self._exactresults:
for result in self.data.get('exactresults', []):
self._exactresults.append(self._to_object(result))
return self._exactresults
def results(self, page=1):
page_key = 'page%s' % page
if page != self._page:
if page > self.pages:
raise DiscogsAPIError, 'Page number exceeds maximum number of pages returned'
self._params['page'] = page
self._clear_cache()
if not self.data:
return []
if page_key not in self._results:
self._results[page_key] = []
for result in self.data['searchresults']['results']:
self._results[page_key].append(self._to_object(result))
return self._results[page_key]
@property
def numresults(self):
if not self.data:
return 0
return int(self.data['searchresults'].get('numResults', 0))
@property
def pages(self):
if not self.data:
return 0
return (self.numresults / 20) + 1
+1
Ver Arquivo
@@ -1,6 +1,7 @@
import gi
from gi.repository import Pango
from gi.repository import Gtk
gi.require_version("Gtk", "3.0")
import logging
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 14 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 9.6 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 7.2 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 8.5 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 7.6 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 9.0 KiB

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 3.3 KiB

+93
Ver Arquivo
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/jrbastien/Dropbox/CoverArt/Issue_225/coverart_monochrome_icon5.png"
sodipodi:docname="coverart_icon_monochrome.svg"
inkscape:version="0.48.4 r9939"
version="1.1"
width="16"
height="16"
id="svg7384">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1313"
inkscape:window-height="744"
id="base"
showgrid="false"
inkscape:zoom="22.627417"
inkscape:cx="7.8121502"
inkscape:cy="8.1026068"
inkscape:window-x="53"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg7384">
<inkscape:grid
type="xygrid"
id="grid2984" />
</sodipodi:namedview>
<defs
id="defs13" />
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Gnome Symbolic Icon Theme</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<title
id="title9167">Gnome Symbolic Icon Theme</title>
<path
style="color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
d="M 0 1 L 0 15 L 16 15 L 16 1 L 0 1 z M 3 2 L 15 2 L 15 14 L 3 14 L 3 2 z "
id="path3049" />
<path
id="path3145"
style="color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
d="m 8.49454,6.9999998 c -0.1963888,0 -0.2708736,0.00458 -0.421926,0.075312 C 7.8492631,7.1799014 7.6954236,7.3410447 7.5904128,7.5573095 7.5112238,7.7203905 7.5,7.8010382 7.5,7.9941194 c 0,0.1930808 0.011222,0.2737261 0.090413,0.4368099 0.1405533,0.2894579 0.401828,0.4949454 0.7233017,0.557309 0.1058087,0.020535 0.330263,0.013347 0.4369949,-0.015065 C 8.9273383,8.9261648 9.1300999,8.8031091 9.2479793,8.6719249 9.3497026,8.558718 9.4316453,8.391306 9.4740114,8.235115 c 0.034652,-0.1277581 0.034652,-0.3693021 0,-0.4970598 C 9.393561,7.4414403 9.2029191,7.2229953 8.9315348,7.0903743 8.7670195,7.0099746 8.6978322,6.9999998 8.49454,6.9999998 z m 0.00546,-3.2619392 c 0.3435847,0 0.6929605,0.032658 1.0213496,0.1135478 1.5301191,0.3769006 2.7439931,1.5920313 3.1207921,3.1225655 0.161741,0.6569562 0.161741,1.3868971 0,2.0438611 -0.413142,1.678183 -1.83062,2.926869 -3.5463542,3.179339 -0.2588467,0.03813 -0.7345973,0.05226 -0.9646085,0.02839 C 7.0640041,12.114865 6.1115479,11.676536 5.4075798,10.919964 4.646015,10.101478 4.2443755,9.103245 4.2443755,7.9961045 4.2443755,7.312468 4.3736153,6.7297403 4.6699382,6.1225653 5.2189083,4.9976584 6.2624431,4.1511883 7.4786511,3.8516084 7.8070437,3.7707188 8.1564165,3.7380606 8.5000013,3.7380606 z m 0,-0.738061 c -0.2467741,0 -0.4911568,0.00498 -0.6525293,0.028388 C 6.7061092,3.193961 5.780077,3.6778426 4.9820172,4.4761218 4.1839582,5.2743991 3.700631,6.2015307 3.5351045,7.3432048 c -0.046806,0.3228327 -0.046806,0.9829673 0,1.3058001 0.1655265,1.1416745 0.6488537,2.0688051 1.4469127,2.8670811 0.855663,0.855896 1.9055518,1.365449 3.1207923,1.476123 0.2703983,0.02463 0.8115804,-0.0132 1.1064627,-0.05677 1.0961058,-0.161943 2.0226018,-0.633028 2.8087138,-1.419348 0.798063,-0.798276 1.28138,-1.7254067 1.446912,-2.8670813 0.0468,-0.3228328 0.0468,-0.9829675 0,-1.3058001 C 13.299366,6.2015304 12.816049,5.2743988 12.017986,4.4761215 11.219926,3.6778423 10.293899,3.1939607 9.1525306,3.0283866 8.991163,3.0049817 8.7467754,2.9999996 8.5000013,2.9999996 z" />
<rect
style="color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3809"
width="1.4146408"
height="13.103647"
x="-2.9486878"
y="2.1104977"
transform="matrix(0,-1,0.99999431,0.00337266,0,0)" />
<rect
style="color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3809-0"
width="1.1411272"
height="13.103621"
x="-14.135278"
y="2.1503386"
transform="matrix(0,-1,0.9999963,0.00272058,0,0)" />
<rect
style="color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3809-0-7"
width="1.1411272"
height="13.103621"
x="-15.180982"
y="-14.649317"
transform="matrix(-1,0,0.00272058,-0.9999963,0,0)" />
</svg>

Depois

Largura:  |  Altura:  |  Tamanho: 5.6 KiB

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 60 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.7 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.1 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 33 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.8 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 40 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 15 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 18 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.4 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 13 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 16 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 4.8 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.3 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.2 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 54 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.7 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 68 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 19 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.8 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 17 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 22 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.7 KiB

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 68 KiB

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 4.0 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.9 KiB

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais