Merge lp:~mblayman/entertainer/clean-messages into lp:entertainer
- clean-messages
- Merge into trunk
Proposed by
Matt Layman
Status: | Rejected |
---|---|
Rejected by: | Paul Hummer |
Proposed branch: | lp:~mblayman/entertainer/clean-messages |
Merge into: | lp:entertainer |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~mblayman/entertainer/clean-messages |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer | Needs Resubmitting | ||
Review via email: mp+11338@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Matt Layman (mblayman) wrote : | # |
Revision history for this message
Paul Hummer (rockstar) wrote : | # |
Please re-propose this against future, as that's where the rest of the dependent branches will land.
review:
Needs Resubmitting
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'MANIFEST.in' |
2 | --- MANIFEST.in 2008-10-05 17:11:31 +0000 |
3 | +++ MANIFEST.in 2009-09-05 13:58:02 +0000 |
4 | @@ -1,3 +1,10 @@ |
5 | include entertainer* |
6 | +include docs/COPYING docs/DEPENDENCIES |
7 | +include docs/entertainer.1 docs/entertainer.desktop |
8 | recursive-include cfg * |
9 | +recursive-include entertainerlib * |
10 | +recursive-include icons * |
11 | +recursive-include themes * |
12 | +recursive-include tools * |
13 | +global-exclude *pyc |
14 | |
15 | |
16 | === added file 'README' |
17 | --- README 1970-01-01 00:00:00 +0000 |
18 | +++ README 2009-09-05 13:58:02 +0000 |
19 | @@ -0,0 +1,6 @@ |
20 | +To install Entertainer, run: |
21 | + |
22 | +python setup.py install |
23 | + |
24 | +For information on how to contribute, read docs/HACKING. |
25 | + |
26 | |
27 | === modified file 'cfg/content.conf' |
28 | --- cfg/content.conf 2009-05-31 17:11:16 +0000 |
29 | +++ cfg/content.conf 2009-08-16 22:19:22 +0000 |
30 | @@ -1,26 +1,34 @@ |
31 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
32 | -[Images] |
33 | -folders = |
34 | -display_hidden_files_folders = False |
35 | +[Media] |
36 | +download_lyrics = False |
37 | +download_album_art = True |
38 | +download_metadata = True |
39 | +display_eject_in_menu = False |
40 | +folders = |
41 | |
42 | [Weather] |
43 | location = Bath,England |
44 | display_in_menu = True |
45 | -metric_units = True |
46 | - |
47 | -[Music] |
48 | -folders = |
49 | -download_lyrics = False |
50 | -download_album_art = True |
51 | - |
52 | -[Videos] |
53 | -folders = |
54 | -download_metadata = True |
55 | |
56 | [RSS] |
57 | -feeds = http://theironlion.net/feeds/blog;http://www.joshuascotton.com/main/archives/tag/entertainer/feed;http://laymanstermsdev.wordpress.com/feed |
58 | +feeds = http://theironlion.net/blog/feed;http://www.joshuascotton.com/main/archives/tag/entertainer/feed;http://laymanstermsdev.wordpress.com/feed |
59 | fetch_interval = 15 |
60 | |
61 | [CD] |
62 | display_eject_in_menu = False |
63 | |
64 | +[Photographs] |
65 | +slideshow_step = 5 |
66 | + |
67 | +[General] |
68 | +stage_width = 1366 |
69 | +stage_height = 768 |
70 | +show_effects = True |
71 | +start_in_fullscreen = True |
72 | +theme = Default |
73 | +backend_port = 45054 |
74 | +history_size = 8 |
75 | +transition_effect = Slide |
76 | +start_server_auto = True |
77 | +display_icon = False |
78 | + |
79 | |
80 | === removed file 'cfg/preferences.conf' |
81 | --- cfg/preferences.conf 2009-05-06 03:40:22 +0000 |
82 | +++ cfg/preferences.conf 1970-01-01 00:00:00 +0000 |
83 | @@ -1,16 +0,0 @@ |
84 | -# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
85 | -[Photographs] |
86 | -slideshow_step = 5 |
87 | - |
88 | -[General] |
89 | -stage_width = 1366 |
90 | -stage_height = 768 |
91 | -show_effects = True |
92 | -start_in_fullscreen = True |
93 | -theme = Default |
94 | -backend_port = 45054 |
95 | -history_size = 8 |
96 | -transition_effect = Slide |
97 | -start_server_auto = True |
98 | -display_icon = False |
99 | - |
100 | |
101 | === modified file 'docs/DEPENDENCIES' |
102 | --- docs/DEPENDENCIES 2009-05-09 19:08:39 +0000 |
103 | +++ docs/DEPENDENCIES 2009-08-25 02:51:36 +0000 |
104 | @@ -14,3 +14,4 @@ |
105 | python-pyvorbis |
106 | python-storm |
107 | python-twisted |
108 | +python-xdg |
109 | |
110 | === modified file 'entertainer' |
111 | --- entertainer 2009-05-06 02:58:08 +0000 |
112 | +++ entertainer 2009-05-10 17:36:49 +0000 |
113 | @@ -1,8 +1,9 @@ |
114 | #!/usr/bin/env python |
115 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
116 | +'''Main client executable''' |
117 | '''Main frontend executable''' |
118 | |
119 | -from entertainerlib.frontend import main |
120 | +from entertainerlib.client import main |
121 | |
122 | if __name__ == '__main__': |
123 | main() |
124 | |
125 | === modified file 'entertainer-backend' |
126 | --- entertainer-backend 2009-05-06 02:58:08 +0000 |
127 | +++ entertainer-backend 2009-09-08 02:02:58 +0000 |
128 | @@ -51,11 +51,7 @@ |
129 | sys.exit(0) |
130 | |
131 | if len(sys.argv) > 1 and sys.argv[1] == "--foreground": |
132 | - try: |
133 | - backend = BackendServer() |
134 | - except KeyboardInterrupt: |
135 | - backend.quitBackend() |
136 | - sys.exit() |
137 | + backend = BackendServer() |
138 | else: |
139 | print "Entertainer backend starting..." |
140 | libc = ctypes.CDLL('libc.so.6') |
141 | |
142 | === renamed file 'entertainer-content-manager' => 'entertainer-manager' |
143 | --- entertainer-content-manager 2009-04-28 23:54:49 +0000 |
144 | +++ entertainer-manager 2009-08-23 01:01:18 +0000 |
145 | @@ -1,15 +1,14 @@ |
146 | #!/usr/bin/env python |
147 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
148 | -'''Content Management executable''' |
149 | +'''Manager executable''' |
150 | |
151 | import gtk |
152 | |
153 | -from entertainerlib.frontend.translation_setup import TranslationSetup |
154 | +from entertainerlib.client.translation_setup import TranslationSetup |
155 | TranslationSetup() |
156 | |
157 | -from entertainerlib.utils.content_management_dialog import ( |
158 | - ContentManagementDialog) |
159 | - |
160 | - |
161 | -ContentManagementDialog(True) |
162 | +from entertainerlib.dialog import ManagerDialog |
163 | + |
164 | + |
165 | +ManagerDialog(True) |
166 | gtk.main() |
167 | |
168 | === removed file 'entertainer-preferences' |
169 | --- entertainer-preferences 2009-04-28 23:54:49 +0000 |
170 | +++ entertainer-preferences 1970-01-01 00:00:00 +0000 |
171 | @@ -1,14 +0,0 @@ |
172 | -#!/usr/bin/env python |
173 | -# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
174 | -'''A preferences editing tool''' |
175 | - |
176 | -import gtk |
177 | - |
178 | -from entertainerlib.frontend.translation_setup import TranslationSetup |
179 | -TranslationSetup() |
180 | - |
181 | -from entertainerlib.utils.preferences_dialog import PreferencesDialog |
182 | - |
183 | - |
184 | -PreferencesDialog(True) |
185 | -gtk.main() |
186 | |
187 | === added file 'entertainer-server' |
188 | --- entertainer-server 1970-01-01 00:00:00 +0000 |
189 | +++ entertainer-server 2009-04-04 04:24:46 +0000 |
190 | @@ -0,0 +1,7 @@ |
191 | +#!/usr/bin/env python |
192 | +'''Server executable''' |
193 | + |
194 | +from entertainerlib.network import server_main |
195 | + |
196 | +server_main() |
197 | + |
198 | |
199 | === modified file 'entertainerlib/backend/backend_server.py' |
200 | --- entertainerlib/backend/backend_server.py 2009-05-06 02:58:08 +0000 |
201 | +++ entertainerlib/backend/backend_server.py 2009-09-08 02:02:58 +0000 |
202 | @@ -3,8 +3,8 @@ |
203 | |
204 | import gobject |
205 | |
206 | -from entertainerlib.utils.configuration import Configuration |
207 | -from entertainerlib.utils.logger import Logger |
208 | +from entertainerlib.configuration import Configuration |
209 | +from entertainerlib.logger import Logger |
210 | |
211 | # Entertainer backend core |
212 | from entertainerlib.backend.core.message import Message |
213 | @@ -39,18 +39,13 @@ |
214 | self.config = Configuration() |
215 | self.logger = Logger().getLogger('backend.BackendServer') |
216 | self.message_bus = MessageBus() |
217 | - self._port = self.config.get_port() |
218 | + self._port = self.config.port |
219 | |
220 | # Connection server - Thread that listens incoming socket connections |
221 | self.connection_server = None |
222 | |
223 | - # Config files |
224 | - self.content_config = None |
225 | - self.preferences = None |
226 | - |
227 | self.scheduler = None |
228 | self.feed_manager = None |
229 | - self.guide_updater = None |
230 | self.media_manager = None |
231 | |
232 | # The order of the initialize method calls is significant! Don't change |
233 | @@ -65,15 +60,13 @@ |
234 | """Initialize configuration""" |
235 | cfg_dict = { |
236 | MessageType.CONTENT_CONF_UPDATED : MessagePriority.VERY_HIGH, |
237 | - MessageType.PREFERENCES_CONF_UPDATED : MessagePriority.VERY_HIGH |
238 | } |
239 | self.message_bus.registerMessageHandler(self.config, cfg_dict) |
240 | self.logger.debug("Configuration intialized successfully") |
241 | |
242 | def initialize_connection_server(self): |
243 | """Initialize connection server.""" |
244 | - self.connection_server = ConnectionServer(self._port, |
245 | - self.message_bus) |
246 | + self.connection_server = ConnectionServer(self._port, self.message_bus) |
247 | # Start listening incoming connections |
248 | self.connection_server.start() |
249 | |
250 | @@ -81,7 +74,7 @@ |
251 | """Initialize message scheduler.""" |
252 | self.scheduler = MessageScheduler(self.message_bus) |
253 | self.scheduler.addMessage(Message(MessageType.UPDATE_FEEDS), |
254 | - 60 * self.config.get_feed_fetch_interval()) |
255 | + 60 * self.config.feed_fetch_interval) |
256 | self.logger.debug("Message scheduler intialized successfully") |
257 | |
258 | def initialize_feed_manager(self): |
259 | @@ -105,7 +98,3 @@ |
260 | self.message_bus.registerMessageHandler(self.media_manager, media_dict) |
261 | self.logger.debug("Media Manager intialized successfully") |
262 | |
263 | - def quitBackend(self): |
264 | - '''Close the backend server''' |
265 | - self.message_bus.unregisterAllMessageHandlers() |
266 | - |
267 | |
268 | === modified file 'entertainerlib/backend/components/feeds/feed_fetcher.py' |
269 | --- entertainerlib/backend/components/feeds/feed_fetcher.py 2009-05-06 02:58:08 +0000 |
270 | +++ entertainerlib/backend/components/feeds/feed_fetcher.py 2009-05-10 17:36:49 +0000 |
271 | @@ -7,8 +7,8 @@ |
272 | from datetime import datetime |
273 | from pysqlite2 import dbapi2 as sqlite |
274 | |
275 | -from entertainerlib.utils.configuration import Configuration |
276 | -from entertainerlib.utils.logger import Logger |
277 | +from entertainerlib.configuration import Configuration |
278 | +from entertainerlib.logger import Logger |
279 | |
280 | # Messaging system |
281 | from entertainerlib.backend.core.message import Message |
282 | |
283 | === modified file 'entertainerlib/backend/components/feeds/feed_manager.py' |
284 | --- entertainerlib/backend/components/feeds/feed_manager.py 2009-05-06 02:58:08 +0000 |
285 | +++ entertainerlib/backend/components/feeds/feed_manager.py 2009-08-16 23:01:49 +0000 |
286 | @@ -5,8 +5,8 @@ |
287 | from pysqlite2 import dbapi2 as sqlite |
288 | from entertainerlib.backend.components.feeds.feed_fetcher import FeedFetcher |
289 | |
290 | -from entertainerlib.utils.configuration import Configuration |
291 | -from entertainerlib.utils.logger import Logger |
292 | +from entertainerlib.configuration import Configuration |
293 | +from entertainerlib.logger import Logger |
294 | |
295 | # Messaging system |
296 | from entertainerlib.backend.core.message_type_priority import MessageType |
297 | @@ -35,8 +35,7 @@ |
298 | Update all feeds to cache in a new thread and after |
299 | that emit FEED_DB_UPDATED message to the messagebus. |
300 | """ |
301 | - fetch_thread = FeedFetcher(self.message_bus, |
302 | - self.config.get_feeds()) |
303 | + fetch_thread = FeedFetcher(self.message_bus, self.config.feeds) |
304 | fetch_thread.start() |
305 | |
306 | def createFeedCacheDatabase(self): |
307 | |
308 | === renamed file 'entertainerlib/utils/feed_utils.py' => 'entertainerlib/backend/components/feeds/feed_utils.py' |
309 | --- entertainerlib/utils/feed_utils.py 2009-05-06 02:02:20 +0000 |
310 | +++ entertainerlib/backend/components/feeds/feed_utils.py 2009-05-10 17:36:49 +0000 |
311 | @@ -8,7 +8,7 @@ |
312 | import gtk |
313 | import gtk.glade |
314 | |
315 | -from entertainerlib.utils.configuration import Configuration |
316 | +from entertainerlib.configuration import Configuration |
317 | |
318 | |
319 | class FeedEntryParser: |
320 | |
321 | === modified file 'entertainerlib/backend/components/mediacache/image_cache.py' |
322 | --- entertainerlib/backend/components/mediacache/image_cache.py 2009-06-01 09:55:40 +0000 |
323 | +++ entertainerlib/backend/components/mediacache/image_cache.py 2009-08-16 18:51:06 +0000 |
324 | @@ -8,8 +8,8 @@ |
325 | from pysqlite2 import dbapi2 as sqlite |
326 | |
327 | from entertainerlib.thumbnailer import ImageThumbnailer |
328 | -from entertainerlib.utils.configuration import Configuration |
329 | -from entertainerlib.utils.logger import Logger |
330 | +from entertainerlib.configuration import Configuration |
331 | +from entertainerlib.logger import Logger |
332 | |
333 | from entertainerlib.backend.components.mediacache.cache import Cache |
334 | |
335 | @@ -117,15 +117,13 @@ |
336 | "Path doesn't exist: " + path) |
337 | else: |
338 | for root, dirs, files in os.walk(path): |
339 | - if os.path.split(root)[-1][0] == "." and not \ |
340 | - self.config.display_hidden_files_folders(): |
341 | + if os.path.split(root)[-1][0] == ".": |
342 | continue |
343 | if not self.isDirectoryInCache(root): |
344 | self._addAlbum(root) |
345 | |
346 | for name in files: |
347 | - if os.path.split(name)[-1][0] == "." and not \ |
348 | - self.config.display_hidden_files_folders(): |
349 | + if os.path.split(name)[-1][0] == ".": |
350 | continue |
351 | if self.isSupportedFormat(name): |
352 | self.addFile(os.path.join(root, name)) |
353 | |
354 | === modified file 'entertainerlib/backend/components/mediacache/media_cache_manager.py' |
355 | --- entertainerlib/backend/components/mediacache/media_cache_manager.py 2009-05-06 02:58:08 +0000 |
356 | +++ entertainerlib/backend/components/mediacache/media_cache_manager.py 2009-08-16 22:19:22 +0000 |
357 | @@ -1,8 +1,8 @@ |
358 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
359 | '''MediaCacheManager - Downloads metadata and keeps media cache up-to-date''' |
360 | |
361 | -from entertainerlib.utils.configuration import Configuration |
362 | -from entertainerlib.utils.logger import Logger |
363 | +from entertainerlib.configuration import Configuration |
364 | +from entertainerlib.logger import Logger |
365 | |
366 | from entertainerlib.backend.core.message_type_priority import MessageType |
367 | from entertainerlib.backend.core.message_handler import MessageHandler |
368 | @@ -13,7 +13,7 @@ |
369 | from entertainerlib.backend.components.mediacache.video_cache import VideoCache |
370 | |
371 | class MediaCacheManager(MessageHandler): |
372 | - """Makes sure that frontend has all the data available.""" |
373 | + """Makes sure that client has all the data available.""" |
374 | |
375 | def __init__(self): |
376 | """ |
377 | @@ -23,11 +23,11 @@ |
378 | self.logger = Logger().getLogger( |
379 | 'backend.components.mediacache.MediaCacheManager') |
380 | self.config = Configuration() |
381 | - self.video_folders = self.config.get_video_folders() |
382 | + self.video_folders = self.config.media_folders |
383 | self._index_videos(self.video_folders) |
384 | - self.music_folders = self.config.get_music_folders() |
385 | + self.music_folders = self.config.media_folders |
386 | self._index_music(self.music_folders) |
387 | - self.image_folders = self.config.get_image_folders() |
388 | + self.image_folders = self.config.media_folders |
389 | self._index_images(self.image_folders) |
390 | |
391 | # Should we rebuild to detect files that were removed while backend was |
392 | @@ -104,9 +104,9 @@ |
393 | we need to index them. If folders are removed, we need to remove |
394 | them from the cache and also from FileSystemObeserver. |
395 | """ |
396 | - updated_video_folders = self.config.get_video_folders() |
397 | - updated_music_folders = self.config.get_music_folders() |
398 | - updated_image_folders = self.config.get_image_folders() |
399 | + updated_video_folders = self.config.media_folders |
400 | + updated_music_folders = self.config.media_folders |
401 | + updated_image_folders = self.config.media_folders |
402 | |
403 | # Handle image folder changes |
404 | current_images = set(self.image_folders) |
405 | |
406 | === modified file 'entertainerlib/backend/components/mediacache/music_cache.py' |
407 | --- entertainerlib/backend/components/mediacache/music_cache.py 2009-05-09 17:03:51 +0000 |
408 | +++ entertainerlib/backend/components/mediacache/music_cache.py 2009-08-18 02:50:40 +0000 |
409 | @@ -9,11 +9,11 @@ |
410 | import ogg.vorbis |
411 | from pysqlite2 import dbapi2 as sqlite |
412 | |
413 | -from entertainerlib.utils.albumart_downloader import AlbumArtDownloader |
414 | -from entertainerlib.utils.configuration import Configuration |
415 | -from entertainerlib.utils.logger import Logger |
416 | - |
417 | from entertainerlib.backend.components.mediacache.cache import Cache |
418 | +from entertainerlib.configuration import Configuration |
419 | +from entertainerlib.download import AlbumArtDownloader |
420 | +from entertainerlib.logger import Logger |
421 | + |
422 | |
423 | class MusicCache(Cache): |
424 | """ |
425 | @@ -433,7 +433,7 @@ |
426 | album_art_file) |
427 | # Local not found -> try internet |
428 | else: |
429 | - if self.config.download_album_art(): |
430 | + if self.config.download_album_art: |
431 | if album != "Unknown album" and artist != "Unknown Artist": |
432 | loader_thread = AlbumArtDownloader(album, artist, |
433 | self.config.ALBUM_ART_DIR) |
434 | |
435 | === modified file 'entertainerlib/backend/components/mediacache/video_cache.py' |
436 | --- entertainerlib/backend/components/mediacache/video_cache.py 2009-05-06 02:58:08 +0000 |
437 | +++ entertainerlib/backend/components/mediacache/video_cache.py 2009-08-18 02:45:02 +0000 |
438 | @@ -6,8 +6,8 @@ |
439 | from pysqlite2 import dbapi2 as sqlite |
440 | |
441 | from entertainerlib.thumbnailer import VideoThumbnailer |
442 | -from entertainerlib.utils.configuration import Configuration |
443 | -from entertainerlib.utils.logger import Logger |
444 | +from entertainerlib.configuration import Configuration |
445 | +from entertainerlib.logger import Logger |
446 | |
447 | from entertainerlib.backend.components.mediacache.cache import Cache |
448 | from entertainerlib.backend.components.mediacache.video_metadata_search import ( |
449 | @@ -243,7 +243,7 @@ |
450 | VALUES (:fn)""", |
451 | { "fn" : filename } ) |
452 | self.__db_conn.commit() |
453 | - if self.config.download_video_metadata(): |
454 | + if self.config.download_metadata: |
455 | self.__searchMetadata(filename) |
456 | |
457 | def __searchMetadata(self, filename): |
458 | |
459 | === modified file 'entertainerlib/backend/components/mediacache/video_metadata_search.py' |
460 | --- entertainerlib/backend/components/mediacache/video_metadata_search.py 2009-05-06 02:58:08 +0000 |
461 | +++ entertainerlib/backend/components/mediacache/video_metadata_search.py 2009-05-10 17:36:49 +0000 |
462 | @@ -9,8 +9,8 @@ |
463 | import threading |
464 | from pysqlite2 import dbapi2 as sqlite |
465 | |
466 | -from entertainerlib.utils.logger import Logger |
467 | -from entertainerlib.utils.configuration import Configuration |
468 | +from entertainerlib.logger import Logger |
469 | +from entertainerlib.configuration import Configuration |
470 | |
471 | class VideoMetadataSearch(threading.Thread): |
472 | """ |
473 | |
474 | === modified file 'entertainerlib/backend/core/client_connection.py' |
475 | --- entertainerlib/backend/core/client_connection.py 2009-05-06 02:58:08 +0000 |
476 | +++ entertainerlib/backend/core/client_connection.py 2009-05-10 17:36:49 +0000 |
477 | @@ -8,7 +8,7 @@ |
478 | # Messaging system |
479 | from entertainerlib.backend.core.message_handler import MessageHandler |
480 | |
481 | -from entertainerlib.utils.logger import Logger |
482 | +from entertainerlib.logger import Logger |
483 | |
484 | class ClientConnection(threading.Thread, MessageHandler): |
485 | """ |
486 | |
487 | === modified file 'entertainerlib/backend/core/connection_server.py' |
488 | --- entertainerlib/backend/core/connection_server.py 2009-05-06 02:58:08 +0000 |
489 | +++ entertainerlib/backend/core/connection_server.py 2009-05-10 17:36:49 +0000 |
490 | @@ -7,7 +7,7 @@ |
491 | |
492 | from entertainerlib.backend.core.client_connection import ClientConnection |
493 | |
494 | -from entertainerlib.utils.logger import Logger |
495 | +from entertainerlib.logger import Logger |
496 | |
497 | class ConnectionServer(threading.Thread): |
498 | """ |
499 | |
500 | === modified file 'entertainerlib/backend/core/message_bus.py' |
501 | --- entertainerlib/backend/core/message_bus.py 2009-05-06 02:58:08 +0000 |
502 | +++ entertainerlib/backend/core/message_bus.py 2009-09-08 02:02:58 +0000 |
503 | @@ -6,7 +6,7 @@ |
504 | from entertainerlib.backend.core.message import Message |
505 | from entertainerlib.backend.core.message_handler import MessageHandler |
506 | from entertainerlib.backend.core.message_type_priority import MessageType |
507 | -from entertainerlib.utils.logger import Logger |
508 | +from entertainerlib.logger import Logger |
509 | |
510 | class MessageBus: |
511 | """ |
512 | @@ -23,12 +23,7 @@ |
513 | When MessageHandler is registered to the MessageBus there is also another |
514 | parameter besides handler itself. Second parameter is a dictionary that |
515 | defines MessageTypes that registered handler wants to be notified of and |
516 | - also priorities for those message types. |
517 | - |
518 | - Example of second parameter: |
519 | - dict = {MessageType.FRONTEND_OPENED : MessagePriority.LOW , |
520 | - MessageType.FRONTEND_CLOSED : MessagePriority.NORMAL } |
521 | - """ |
522 | + also priorities for those message types.""" |
523 | |
524 | # This determines number of message types avaialble. In other words, this |
525 | # variable tells how many variables is defined in MessageType class. |
526 | @@ -89,12 +84,6 @@ |
527 | self.logger.debug("MessageHandler '" + str(message_handler) + |
528 | "' unregistered from the message bus.") |
529 | |
530 | - def unregisterAllMessageHandlers(self): |
531 | - """ |
532 | - Unregisters all MessageHandler from this MessageBus. |
533 | - """ |
534 | - self.message_handlers[:] = [] |
535 | - |
536 | def notifyMessage(self, message): |
537 | """ |
538 | Emit a new Message to this MessageBus. |
539 | |
540 | === modified file 'entertainerlib/backend/core/message_bus_proxy.py' |
541 | --- entertainerlib/backend/core/message_bus_proxy.py 2009-05-06 02:58:08 +0000 |
542 | +++ entertainerlib/backend/core/message_bus_proxy.py 2009-08-16 22:03:14 +0000 |
543 | @@ -6,7 +6,7 @@ |
544 | import threading |
545 | from cStringIO import StringIO |
546 | |
547 | -from entertainerlib.utils.configuration import Configuration |
548 | +from entertainerlib.configuration import Configuration |
549 | |
550 | class MessageBusProxy(threading.Thread): |
551 | """ |
552 | @@ -57,7 +57,7 @@ |
553 | MessageHandler. |
554 | """ |
555 | # Open socket |
556 | - self.socket_to_server.connect(('localhost', self.config.get_port())) |
557 | + self.socket_to_server.connect(('localhost', self.config.port)) |
558 | |
559 | # Send client name |
560 | self.socket_to_server.sendall(self.client_name + "\n") |
561 | |
562 | === modified file 'entertainerlib/backend/core/message_type_priority.py' |
563 | --- entertainerlib/backend/core/message_type_priority.py 2009-05-06 02:58:08 +0000 |
564 | +++ entertainerlib/backend/core/message_type_priority.py 2009-09-08 00:53:03 +0000 |
565 | @@ -15,56 +15,27 @@ |
566 | VERY_LOW = 40 |
567 | |
568 | class MessageType: |
569 | - """ |
570 | - Determines all allowed Message types. MessageHandler should use these |
571 | - to determine type. This simply makes code more readable. |
572 | - |
573 | - Example: |
574 | - if message.get_type() == MessageType.FRONTEND_OPENED: |
575 | - do_something_useful() |
576 | - """ |
577 | - |
578 | - # Indicates that Preferences UI has been used to update preferences. |
579 | - PREFERENCES_CONF_UPDATED = 0 |
580 | + """Determines all allowed Message types. MessageHandler should use these to |
581 | + determine type. This simply makes code more readable.""" |
582 | |
583 | # Indicates that Content Management UI has been used to update contents. |
584 | - CONTENT_CONF_UPDATED = 1 |
585 | - |
586 | - # Indicates that frontend has been opened. |
587 | - FRONTEND_OPENED = 2 |
588 | - |
589 | - # Indicates that frontend has been closed. |
590 | - FRONTEND_CLOSED = 3 |
591 | + CONTENT_CONF_UPDATED = 0 |
592 | |
593 | # Indicates that Feed cache has been updated. |
594 | - FEED_DB_UPDATED = 4 |
595 | - |
596 | - NOT_USED_5 = 5 |
597 | - |
598 | - NOT_USED_1 = 6 |
599 | + FEED_DB_UPDATED = 1 |
600 | |
601 | # Indicates that Feed cache should be updated. |
602 | - UPDATE_FEEDS = 7 |
603 | - |
604 | - NOT_USED_6 = 8 |
605 | - |
606 | - #This should be left in otherwise message_bus.py breaks |
607 | - #If another message type is needed please use this |
608 | - DO_NOT_DELETE_USE_NEXT = 9 |
609 | - |
610 | - NOT_USED_2 = 10 |
611 | - NOT_USED_3 = 11 |
612 | - NOT_USED_4 = 12 |
613 | + UPDATE_FEEDS = 2 |
614 | |
615 | # Require to rebuild image cache |
616 | - REBUILD_IMAGE_CACHE = 13 |
617 | + REBUILD_IMAGE_CACHE = 3 |
618 | |
619 | # Require to rebuild music cache |
620 | - REBUILD_MUSIC_CACHE = 14 |
621 | + REBUILD_MUSIC_CACHE = 4 |
622 | |
623 | # Require to rebuild video cache |
624 | - REBUILD_VIDEO_CACHE = 15 |
625 | + REBUILD_VIDEO_CACHE = 5 |
626 | |
627 | # Require to rebuild feed cache |
628 | - REBUILD_FEED_CACHE = 16 |
629 | + REBUILD_FEED_CACHE = 6 |
630 | |
631 | |
632 | === renamed directory 'entertainerlib/frontend' => 'entertainerlib/client' |
633 | === modified file 'entertainerlib/client/__init__.py' |
634 | --- entertainerlib/frontend/__init__.py 2009-05-06 03:40:22 +0000 |
635 | +++ entertainerlib/client/__init__.py 2009-08-21 01:55:09 +0000 |
636 | @@ -1,12 +1,12 @@ |
637 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
638 | -'''Frontend gui to entertainer''' |
639 | +'''Client code for Entertainer.''' |
640 | # pylint: disable-msg=W0612 |
641 | |
642 | def main(*args, **kwargs): |
643 | - '''Frontend runner''' |
644 | + '''Client code main loop.''' |
645 | |
646 | # Import statements are inside the function so that they aren't imported |
647 | - # every time something from the frontend is imported |
648 | + # every time something from the client is imported |
649 | |
650 | # cluttergtk must be imported before the first import of clutter so it |
651 | # must be imported even though pylint complains about it not being used. |
652 | @@ -15,23 +15,23 @@ |
653 | import gobject |
654 | import gtk |
655 | |
656 | - from entertainerlib.frontend.translation_setup import TranslationSetup |
657 | + from entertainerlib.client.translation_setup import TranslationSetup |
658 | TranslationSetup() |
659 | |
660 | from entertainerlib.backend.backend_server import BackendServer |
661 | - from entertainerlib.utils.configuration import Configuration |
662 | - from entertainerlib.frontend.frontend_client import FrontendClient |
663 | + from entertainerlib.configuration import Configuration |
664 | + from entertainerlib.client.client import Client |
665 | |
666 | gobject.threads_init() |
667 | gtk.gdk.threads_init() |
668 | clutter.threads_init() |
669 | |
670 | config = Configuration() |
671 | - if config.start_auto_server(): |
672 | + if config.start_auto_server: |
673 | print "Entertainer backend starting..." |
674 | BackendServer() |
675 | |
676 | - frontend_client = FrontendClient() |
677 | - frontend_client.start() |
678 | + client_client = Client() |
679 | + client_client.start() |
680 | |
681 | |
682 | |
683 | === modified file 'entertainerlib/client/backend_connection.py' |
684 | --- entertainerlib/frontend/backend_connection.py 2009-05-06 02:58:08 +0000 |
685 | +++ entertainerlib/client/backend_connection.py 2009-09-08 02:40:16 +0000 |
686 | @@ -7,49 +7,21 @@ |
687 | from entertainerlib.backend.core.message_handler import MessageHandler |
688 | |
689 | class BackendConnection(MessageHandler): |
690 | - """ |
691 | - BackendConnection - Connection to the Entertainer backend. Instance from |
692 | - this class is a gate to backend messagebus. |
693 | - """ |
694 | + """Connection to the Entertainer backend messagebus.""" |
695 | |
696 | def __init__(self): |
697 | - """Initialize connection. Connects to backend.""" |
698 | - #messages = { |
699 | - # MessageType.CONTENT_CONF_UPDATED : MessagePriority.NORMAL, |
700 | - # MessageType.PREFERENCES_CONF_UPDATED : MessagePriority.NORMAL, |
701 | - # MessageType.FEED_DB_UPDATED : MessagePriority.HIGH } |
702 | - # XXX: rockstar - The messages above are the "real" messages, and I'm |
703 | - # not sure why they are commented out. Anyone? |
704 | MessageHandler.__init__(self) |
705 | messages = {} |
706 | name = "Entertainer Frontend" |
707 | self.message_bus_proxy = MessageBusProxy(messages, self, name) |
708 | self.message_bus_proxy.connectToMessageBus() |
709 | self.message_bus_proxy.start() |
710 | - self.message_bus_proxy.sendMessage( |
711 | - Message(MessageType.FRONTEND_OPENED)) |
712 | |
713 | def close_connection(self): |
714 | """Close connection to backend""" |
715 | - self.message_bus_proxy.sendMessage( |
716 | - Message(MessageType.FRONTEND_CLOSED)) |
717 | self.message_bus_proxy.disconnectFromMessageBus() |
718 | |
719 | def request_feed_update(self): |
720 | """Request backend to fetch all feeds from the Internet.""" |
721 | self.message_bus_proxy.sendMessage(Message(MessageType.UPDATE_FEEDS)) |
722 | |
723 | - def request_weather_update(self): |
724 | - """Request backend to update weather information from the Internet.""" |
725 | - self.message_bus_proxy.sendMessage(Message(MessageType.UPDATE_WEATHER)) |
726 | - |
727 | - # Implements MessageHandler interface |
728 | - def handleMessage(self, message): |
729 | - """Handle received messages. (Implements MessageHandler interface)""" |
730 | - if message.get_type() == MessageType.CONTENT_CONF_UPDATED: |
731 | - pass |
732 | - elif message.get_type() == MessageType.PREFERENCES_CONF_UPDATED: |
733 | - pass |
734 | - elif message.get_type() == MessageType.FEED_DB_UPDATED: |
735 | - pass |
736 | - |
737 | |
738 | === renamed file 'entertainerlib/frontend/frontend_client.py' => 'entertainerlib/client/client.py' |
739 | --- entertainerlib/frontend/frontend_client.py 2009-05-06 02:58:08 +0000 |
740 | +++ entertainerlib/client/client.py 2009-09-08 02:40:16 +0000 |
741 | @@ -1,71 +1,65 @@ |
742 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
743 | -'''Entertainer Frontend - This is a client side of Entertainer.''' |
744 | +'''Entertainer client.''' |
745 | |
746 | import sys |
747 | |
748 | import gtk |
749 | - |
750 | -from entertainerlib.frontend.backend_connection import BackendConnection |
751 | -from entertainerlib.frontend.gui.user_interface import UserInterface |
752 | -from entertainerlib.frontend.medialibrary.feeds import FeedLibrary |
753 | -from entertainerlib.frontend.medialibrary.music import MusicLibrary |
754 | -from entertainerlib.frontend.medialibrary.images import ImageLibrary |
755 | -from entertainerlib.frontend.medialibrary.videos import VideoLibrary |
756 | -from entertainerlib.utils.configuration import Configuration |
757 | -from entertainerlib.utils.logger import Logger |
758 | -from entertainerlib.utils.system_tray_icon import SystemTrayIcon |
759 | - |
760 | -class FrontendClient: |
761 | - ''' |
762 | - Entertainer frontend |
763 | - |
764 | - This is a frontend application of the Entertainer. Frontend is a GUI part |
765 | - that user sees on the screen. This class is a core of the frontend. Frontend |
766 | - connects to the backend's messagebus at startup. |
767 | - ''' |
768 | +from twisted.internet import gtk2reactor |
769 | +gtk2reactor.install() # Install the gtk2 reactor before import the real reactor |
770 | +from twisted.internet import reactor |
771 | +from twisted.internet.protocol import ClientCreator |
772 | +from twisted.python.log import startLogging |
773 | + |
774 | +from entertainerlib.client.backend_connection import BackendConnection |
775 | +from entertainerlib.client.medialibrary.feeds import FeedLibrary |
776 | +from entertainerlib.client.medialibrary.music import MusicLibrary |
777 | +from entertainerlib.client.medialibrary.images import ImageLibrary |
778 | +from entertainerlib.client.medialibrary.videos import VideoLibrary |
779 | +from entertainerlib.configuration import Configuration |
780 | +from entertainerlib.gui.user_interface import UserInterface |
781 | +from entertainerlib.gui.system_tray_icon import SystemTrayIcon |
782 | +from entertainerlib.network.local.client import EntertainerLocalClientProtocol |
783 | + |
784 | + |
785 | +class Client: |
786 | + '''This is a client application of the Entertainer. Entertainer's client |
787 | + hooks into the server, and then provides a user interface for the data the |
788 | + server creates.''' |
789 | |
790 | def __init__(self): |
791 | - ''' |
792 | - Create a new frontend. |
793 | - |
794 | - This initializes all frontend stuff like media librarys, remote |
795 | - control receiver and GUI. After this we just wait user actions. |
796 | - ''' |
797 | config = Configuration() |
798 | - self.logger = Logger().getLogger('frontend.FrontendClient') |
799 | - self.backend_connection = self.initialize_backend_connection() |
800 | + self.backend_connection = BackendConnection() |
801 | feed_library = FeedLibrary(self.backend_connection) |
802 | - music_library = MusicLibrary(self.backend_connection) |
803 | - image_library = ImageLibrary(self.backend_connection) |
804 | - video_library = VideoLibrary(self.backend_connection) |
805 | + music_library = MusicLibrary() |
806 | + image_library = ImageLibrary() |
807 | + video_library = VideoLibrary() |
808 | self.ui = UserInterface( |
809 | feed_library, image_library, music_library, video_library, |
810 | - self.quit_frontend) |
811 | - |
812 | - if config.tray_icon_enabled(): |
813 | - SystemTrayIcon( |
814 | - self.quit_frontend, self.toggle_interface_visibility) |
815 | + self.quit_client) |
816 | + |
817 | + if config.tray_icon_enabled: |
818 | + SystemTrayIcon(self.quit_client, self.toggle_interface_visibility) |
819 | + |
820 | + startLogging(sys.stdout) |
821 | + client = EntertainerLocalClientProtocol |
822 | + |
823 | + ClientCreator(reactor, client).connectTCP( |
824 | + config.network_options['host'], |
825 | + config.network_options['port']) |
826 | |
827 | def start(self): |
828 | '''Start the necessary main loop.''' |
829 | self.ui.start_up() |
830 | self.interface_visible = True |
831 | gtk.gdk.threads_enter() |
832 | - gtk.main() |
833 | + reactor.run() |
834 | gtk.gdk.threads_leave() |
835 | |
836 | - def initialize_backend_connection(self): |
837 | - '''Connect to the backend server.''' |
838 | - backend_connection = BackendConnection() |
839 | - self.logger.debug('Connected to the Entertainer backend server.') |
840 | - |
841 | - return backend_connection |
842 | - |
843 | - def quit_frontend(self): |
844 | - '''Clean up the connection to the backend then close the frontend.''' |
845 | + def quit_client(self): |
846 | + '''Clean up the connection to the backend then close the client.''' |
847 | self.backend_connection.close_connection() |
848 | |
849 | - gtk.main_quit() |
850 | + reactor.stop() |
851 | sys.exit(0) |
852 | |
853 | def toggle_interface_visibility(self): |
854 | |
855 | === modified file 'entertainerlib/client/media_player.py' |
856 | --- entertainerlib/frontend/media_player.py 2009-06-29 19:41:35 +0000 |
857 | +++ entertainerlib/client/media_player.py 2009-06-30 01:06:01 +0000 |
858 | @@ -9,10 +9,10 @@ |
859 | import gobject |
860 | import gst |
861 | |
862 | -from entertainerlib.frontend.gui.widgets.motion_buffer import MotionBuffer |
863 | -from entertainerlib.frontend.gui.widgets.texture import Texture |
864 | -from entertainerlib.frontend.medialibrary.playable import Playable |
865 | -from entertainerlib.utils.logger import Logger |
866 | +from entertainerlib.gui.widgets.motion_buffer import MotionBuffer |
867 | +from entertainerlib.gui.widgets.texture import Texture |
868 | +from entertainerlib.client.medialibrary.playable import Playable |
869 | +from entertainerlib.logger import Logger |
870 | |
871 | class MediaPlayer(gobject.GObject, object): |
872 | ''' |
873 | @@ -72,7 +72,7 @@ |
874 | self.is_playing = False # Is media player currently playing |
875 | self.is_reactive_allowed = False # Is the video_texture reactive |
876 | |
877 | - self.logger = Logger().getLogger('frontend.MediaPlayer') |
878 | + self.logger = Logger().getLogger('client.MediaPlayer') |
879 | |
880 | self._internal_callback_timeout_key = None |
881 | |
882 | |
883 | === modified file 'entertainerlib/client/medialibrary/feeds.py' |
884 | --- entertainerlib/frontend/medialibrary/feeds.py 2009-06-25 19:49:48 +0000 |
885 | +++ entertainerlib/client/medialibrary/feeds.py 2009-07-29 03:09:34 +0000 |
886 | @@ -3,7 +3,7 @@ |
887 | |
888 | from pysqlite2 import dbapi2 as sqlite |
889 | |
890 | -from entertainerlib.utils.configuration import Configuration |
891 | +from entertainerlib.configuration import Configuration |
892 | |
893 | class FeedLibrary(object): |
894 | '''This library can be used to handle RSS feeds in Entertainer.''' |
895 | |
896 | === modified file 'entertainerlib/client/medialibrary/images.py' |
897 | --- entertainerlib/frontend/medialibrary/images.py 2009-05-06 03:40:22 +0000 |
898 | +++ entertainerlib/client/medialibrary/images.py 2009-09-08 02:40:16 +0000 |
899 | @@ -4,25 +4,16 @@ |
900 | import os |
901 | from pysqlite2 import dbapi2 as sqlite |
902 | |
903 | -from entertainerlib.utils.configuration import Configuration |
904 | +from entertainerlib.configuration import Configuration |
905 | |
906 | class ImageLibrary: |
907 | - """ |
908 | - Image library. |
909 | - |
910 | - Entertainer's image cache. |
911 | - """ |
912 | - |
913 | - def __init__(self, backend_connection): |
914 | - """ |
915 | - Initialize image library |
916 | - @param backend_connection: BackendConnection object |
917 | - """ |
918 | + """Entertainer's image cache.""" |
919 | + |
920 | + def __init__(self): |
921 | self.config = Configuration() |
922 | |
923 | if not os.path.exists(self.config.IMAGE_DB): |
924 | raise Exception("Image database doesn't exist!") |
925 | - self.backend_connection = backend_connection |
926 | |
927 | def get_all_images(self): |
928 | """ |
929 | |
930 | === modified file 'entertainerlib/client/medialibrary/music.py' |
931 | --- entertainerlib/frontend/medialibrary/music.py 2009-07-19 19:27:31 +0000 |
932 | +++ entertainerlib/client/medialibrary/music.py 2009-09-08 02:40:16 +0000 |
933 | @@ -5,10 +5,10 @@ |
934 | import CDDB, DiscID |
935 | from pysqlite2 import dbapi2 as sqlite |
936 | |
937 | -from entertainerlib.utils.configuration import Configuration |
938 | +from entertainerlib.configuration import Configuration |
939 | |
940 | -from entertainerlib.frontend.medialibrary.playable import Playable |
941 | -from entertainerlib.utils.lyrics_downloader import LyricsDownloader |
942 | +from entertainerlib.client.medialibrary.playable import Playable |
943 | +from entertainerlib.download import LyricsDownloader |
944 | |
945 | |
946 | class MusicLibraryException(Exception): |
947 | @@ -28,22 +28,13 @@ |
948 | pass |
949 | |
950 | class MusicLibrary: |
951 | - """ |
952 | - Music library. |
953 | - |
954 | - Interface for Entertainer's music cache. |
955 | - """ |
956 | - |
957 | - def __init__(self, backend_connection): |
958 | - """ |
959 | - Initialize library. |
960 | - @param backend_connection: BackendConnection object |
961 | - """ |
962 | + """Interface for Entertainer's music cache.""" |
963 | + |
964 | + def __init__(self): |
965 | self.config = Configuration() |
966 | |
967 | if not os.path.exists(self.config.MUSIC_DB): |
968 | raise Exception("Music database doesn't exist!") |
969 | - self.backend_connection = backend_connection |
970 | self.db_connection = sqlite.connect(self.config.MUSIC_DB) |
971 | self.cursor = self.db_connection.cursor() |
972 | |
973 | |
974 | === modified file 'entertainerlib/client/medialibrary/videos.py' |
975 | --- entertainerlib/frontend/medialibrary/videos.py 2009-05-06 02:58:08 +0000 |
976 | +++ entertainerlib/client/medialibrary/videos.py 2009-09-08 02:40:16 +0000 |
977 | @@ -4,28 +4,19 @@ |
978 | import os |
979 | from pysqlite2 import dbapi2 as sqlite |
980 | |
981 | -from entertainerlib.utils.configuration import Configuration |
982 | -from entertainerlib.utils.logger import Logger |
983 | +from entertainerlib.configuration import Configuration |
984 | +from entertainerlib.logger import Logger |
985 | |
986 | -from entertainerlib.frontend.medialibrary.playable import Playable |
987 | +from entertainerlib.client.medialibrary.playable import Playable |
988 | |
989 | class VideoLibrary: |
990 | - """ |
991 | - Video library. |
992 | - |
993 | - Interface for Entertainer's video cache. |
994 | - """ |
995 | - |
996 | - def __init__(self, backend_connection): |
997 | - """ |
998 | - Initialize video library |
999 | - @param backend_connection: BackendConnection object |
1000 | - """ |
1001 | + """Interface for Entertainer's video cache.""" |
1002 | + |
1003 | + def __init__(self): |
1004 | self.config = Configuration() |
1005 | |
1006 | if not os.path.exists(self.config.VIDEO_DB): |
1007 | raise Exception("Video database doesn't exist!") |
1008 | - self.backend_connection = backend_connection |
1009 | |
1010 | def get_movies(self): |
1011 | """ |
1012 | @@ -133,7 +124,7 @@ |
1013 | |
1014 | #Setting default values |
1015 | self.logger = Logger().getLogger( |
1016 | - 'frontend.medialibrary.videos.VideoItem') |
1017 | + 'client.medialibrary.videos.VideoItem') |
1018 | self.__title = "" |
1019 | self.__filename = "" |
1020 | self.__length = 0 |
1021 | @@ -220,12 +211,12 @@ |
1022 | else: |
1023 | self.logger.error("Thumbnail does not exist for " + \ |
1024 | self.get_filename() + ", using default art instead") |
1025 | - return os.path.join(self.config.get_theme_path(), |
1026 | + return os.path.join(self.config.theme_path, |
1027 | "images/default_movie_art.png") |
1028 | else: |
1029 | self.logger.error("Thumbnail does not exist for " + \ |
1030 | self.get_filename() + ", using default art instead") |
1031 | - return os.path.join(self.config.get_theme_path(), |
1032 | + return os.path.join(self.config.theme_path, |
1033 | "images/default_movie_art.png") |
1034 | |
1035 | def has_thumbnail(self): |
1036 | |
1037 | === modified file 'entertainerlib/client/translation_setup.py' |
1038 | --- entertainerlib/frontend/translation_setup.py 2009-05-06 02:02:20 +0000 |
1039 | +++ entertainerlib/client/translation_setup.py 2009-08-28 02:38:48 +0000 |
1040 | @@ -1,4 +1,3 @@ |
1041 | -#!/usr/bin/env python |
1042 | # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
1043 | '''Translation Setup Code''' |
1044 | |
1045 | @@ -7,33 +6,37 @@ |
1046 | import gettext |
1047 | import gtk |
1048 | import gtk.glade |
1049 | - |
1050 | -TRANSLATION_SOURCE = { |
1051 | - 'branch' : os.path.abspath(os.path.dirname(__file__) + '/../../locale'), |
1052 | - # Hardcoded path for a package install |
1053 | - 'package' : "/usr/share/locale" |
1054 | - } |
1055 | +from xdg import BaseDirectory |
1056 | |
1057 | class TranslationSetup: |
1058 | def __init__(self): |
1059 | + '''Because of how early translation setup has to occur. This should be |
1060 | + the only file outside of Configuration that imports xdg.''' |
1061 | + |
1062 | + def install_locale(locale_dir): |
1063 | + '''Install locale data from the provided directory.''' |
1064 | + # This sets up the _ function |
1065 | + gettext.install('entertainer', locale_dir) |
1066 | + |
1067 | + # This sets up the glade translations |
1068 | + gtk.glade.bindtextdomain('entertainer', locale_dir) |
1069 | + |
1070 | # Find locale data from a dev branch if we can |
1071 | - if os.path.exists(TRANSLATION_SOURCE['branch']): |
1072 | - #This setups the _ function |
1073 | - gettext.install('entertainer', TRANSLATION_SOURCE['branch']) |
1074 | - |
1075 | - #This setups the glade translations |
1076 | - gtk.glade.bindtextdomain('entertainer', |
1077 | - TRANSLATION_SOURCE['branch']) |
1078 | - # Install locale data from the hardcoded package path |
1079 | - elif os.path.exists(TRANSLATION_SOURCE['package']): |
1080 | - #This setups the _ function |
1081 | - gettext.install('entertainer', TRANSLATION_SOURCE['package']) |
1082 | - |
1083 | - #This setups the glade translations |
1084 | - gtk.glade.bindtextdomain('entertainer', |
1085 | - TRANSLATION_SOURCE['package']) |
1086 | + dev_locale = os.path.abspath(os.path.dirname(__file__) + |
1087 | + '/../../locale') |
1088 | + if os.path.exists(dev_locale): |
1089 | + install_locale(dev_locale) |
1090 | + |
1091 | + # Install locale data from the system location |
1092 | else: |
1093 | - pass |
1094 | + system_data_dirs = [data_dir for data_dir in |
1095 | + BaseDirectory.xdg_data_dirs if not |
1096 | + data_dir.startswith(BaseDirectory.xdg_data_home)] |
1097 | + # Since we don't know for certain where the mo files were installed, |
1098 | + # we try to install from both /usr/share and /usr/local/share. |
1099 | + for data_dir in system_data_dirs: |
1100 | + system_locale = os.path.join(data_dir, 'locale') |
1101 | + install_locale(system_locale) |
1102 | |
1103 | - gtk.glade.textdomain ('entertainer') |
1104 | + gtk.glade.textdomain('entertainer') |
1105 | |
1106 | |
1107 | === renamed file 'entertainerlib/utils/configuration.py' => 'entertainerlib/configuration.py' |
1108 | --- entertainerlib/utils/configuration.py 2009-06-01 09:55:40 +0000 |
1109 | +++ entertainerlib/configuration.py 2009-08-27 03:18:41 +0000 |
1110 | @@ -2,475 +2,217 @@ |
1111 | '''Configuration - Class represents Entertainer's configuration''' |
1112 | |
1113 | import os |
1114 | -import sys |
1115 | import shutil |
1116 | import ConfigParser |
1117 | -from ConfigParser import ParsingError, NoSectionError, NoOptionError |
1118 | - |
1119 | +from ConfigParser import NoSectionError, NoOptionError |
1120 | + |
1121 | +from xdg import BaseDirectory |
1122 | + |
1123 | +from entertainerlib.backend.core.message_handler import MessageHandler |
1124 | from entertainerlib.backend.core.message_type_priority import MessageType |
1125 | -from entertainerlib.backend.core.message_handler import MessageHandler |
1126 | - |
1127 | -from entertainerlib.utils.theme import Theme |
1128 | - |
1129 | -SOURCE_CONFIG = { |
1130 | - 'branch' : os.path.abspath(os.path.dirname(__file__) + '/../../cfg'), |
1131 | - # Hardcoded path for a package install |
1132 | - 'package' : "/usr/share/entertainer/cfg" |
1133 | - } |
1134 | +from entertainerlib.db.connection import Database |
1135 | +from entertainerlib.gui.theme import Theme |
1136 | |
1137 | class Configuration(MessageHandler): |
1138 | - """ |
1139 | - Configuration of Entertainer |
1140 | - |
1141 | - This class is an interface to all configuration parameters. All components |
1142 | - of Entertainer should get configuration values through this class. Object |
1143 | - from this class reads current config files and returns values based on |
1144 | - those files. If there are missing lines or other errors in config file, |
1145 | - then object return default values defined in this class. |
1146 | - """ |
1147 | - |
1148 | - CFG_DIR = os.path.expanduser("~/.config/entertainer") |
1149 | - TEST_DIR = None |
1150 | - |
1151 | - # This dictionary keeps track of data values that are written to config |
1152 | - # files but need to be returned to a known state while testing. |
1153 | - _tainted = {} |
1154 | - |
1155 | - # This dictionary keeps track of data values that are tainted in memory |
1156 | - # instead of a config file |
1157 | - _tainted_in_memory = {} |
1158 | + '''Interface to all configuration parameters. All components of Entertainer |
1159 | + should get configuration values through this class.''' |
1160 | |
1161 | _shared_state = {} |
1162 | |
1163 | def __init__(self, test_dir=None): |
1164 | - """ |
1165 | - Read configuration files and setup this object. |
1166 | - """ |
1167 | self.__dict__ = self._shared_state |
1168 | MessageHandler.__init__(self) |
1169 | |
1170 | - if not self._shared_state: |
1171 | - self.TEST_DIR = test_dir |
1172 | - |
1173 | + if not self._shared_state or test_dir is not None: |
1174 | # Set in a production mode or a test mode |
1175 | - if self.TEST_DIR is None: |
1176 | - self.cfg_dir = self.CFG_DIR |
1177 | + if test_dir is None: |
1178 | + self.resources = Resources() |
1179 | else: |
1180 | - self.cfg_dir = self.TEST_DIR |
1181 | - |
1182 | - if not os.path.exists(os.path.expanduser(self.cfg_dir)): |
1183 | - self.create_cfg_dir() |
1184 | - |
1185 | - self.ENTERTAINER_LOG = os.path.join(self.cfg_dir, |
1186 | - u'entertainer.log') |
1187 | - |
1188 | - self.FEED_DB = os.path.join(self.cfg_dir, 'cache/feed.db') |
1189 | - self.IMAGE_DB = os.path.join(self.cfg_dir, 'cache/image.db') |
1190 | - self.MUSIC_DB = os.path.join(self.cfg_dir, 'cache/music.db') |
1191 | - self.VIDEO_DB = os.path.join(self.cfg_dir, 'cache/video.db') |
1192 | - |
1193 | - self.THUMB_DIR = os.path.join(self.cfg_dir, 'cache', 'thumbnails') |
1194 | + self.resources = Resources(config_testing_dir=test_dir) |
1195 | + |
1196 | + self.cache_dir = self.resources.cache_dir |
1197 | + self.config_dir = self.resources.config_dir |
1198 | + self.data_dir = self.resources.data_dir |
1199 | + |
1200 | + self.LOG = os.path.join(self.cache_dir, u'entertainer.log') |
1201 | + |
1202 | + self.MEDIA_DB = Database(os.path.join(self.cache_dir, 'media')) |
1203 | + |
1204 | + self.FEED_DB = os.path.join(self.cache_dir, 'feed.db') |
1205 | + self.IMAGE_DB = os.path.join(self.cache_dir, 'image.db') |
1206 | + self.MUSIC_DB = os.path.join(self.cache_dir, 'music.db') |
1207 | + self.VIDEO_DB = os.path.join(self.cache_dir, 'video.db') |
1208 | + |
1209 | + self.THUMB_DIR = os.path.join(self.cache_dir, 'thumbnails') |
1210 | self.IMAGE_THUMB_DIR = os.path.join(self.THUMB_DIR, 'image') |
1211 | self.VIDEO_THUMB_DIR = os.path.join(self.THUMB_DIR, 'video') |
1212 | - self.ALBUM_ART_DIR = os.path.join(self.cfg_dir, 'cache/album_art') |
1213 | - self.MOVIE_ART_DIR = os.path.join(self.cfg_dir, 'cache/movie_art') |
1214 | - |
1215 | - # Preferences file |
1216 | - self.preferences_conf = os.path.join(self.cfg_dir, |
1217 | - 'preferences.conf') |
1218 | - |
1219 | - # Content file |
1220 | - self.content_conf = os.path.join(self.cfg_dir, 'content.conf') |
1221 | - |
1222 | - self.content_config = ConfigParser.ConfigParser() |
1223 | - self.preferences = ConfigParser.ConfigParser() |
1224 | - try: |
1225 | - self.content_config.readfp(open(self.content_conf)) |
1226 | - self.preferences.readfp(open(self.preferences_conf)) |
1227 | - except ParsingError: |
1228 | - print("ParsingError with configuration file.") |
1229 | - sys.exit(1) |
1230 | - except IOError: |
1231 | - print("IOError: Couldn't read configuration file.") |
1232 | - sys.exit(1) |
1233 | - |
1234 | - self.theme = Theme(self.get_theme_path()) |
1235 | - |
1236 | - self.stage_width = None |
1237 | - self.stage_height = None |
1238 | - |
1239 | - def create_cfg_dir(self): |
1240 | - '''Create a configuration directory and default config files.''' |
1241 | - |
1242 | - def create_cache_hierarchy(config_dir): |
1243 | - '''Create the directories needed for the cache''' |
1244 | - directories = [ |
1245 | - 'cache', |
1246 | - 'cache/album_art', |
1247 | - 'cache/movie_art', |
1248 | - 'cache/thumbnails', |
1249 | - 'cache/thumbnails/image', |
1250 | - 'cache/thumbnails/video' |
1251 | - ] |
1252 | - |
1253 | - for directory in directories: |
1254 | - new_dir = os.path.join(config_dir, directory) |
1255 | - os.mkdir(new_dir) |
1256 | - |
1257 | - config_dir = self.get_cfg_dir() |
1258 | - try: |
1259 | - # Copy configuration data from a dev branch if we can |
1260 | - if os.path.exists(SOURCE_CONFIG['branch']): |
1261 | - shutil.copytree(SOURCE_CONFIG['branch'], config_dir) |
1262 | - create_cache_hierarchy(config_dir) |
1263 | - # Install configuration data from the hardcoded package path |
1264 | - elif os.path.exists(SOURCE_CONFIG['package']): |
1265 | - shutil.copytree(SOURCE_CONFIG['package'], config_dir) |
1266 | - create_cache_hierarchy(config_dir) |
1267 | - else: |
1268 | - print "Couldn't find configuration data. Execution aborted." |
1269 | - sys.exit(1) |
1270 | - except OSError: |
1271 | - print "Couldn't copy configuration data to %s. Execution aborted." \ |
1272 | - % config_dir |
1273 | - sys.exit(1) |
1274 | + self.ALBUM_ART_DIR = os.path.join(self.cache_dir, 'album_art') |
1275 | + self.MOVIE_ART_DIR = os.path.join(self.cache_dir, 'movie_art') |
1276 | + |
1277 | + self.read_config_file() |
1278 | + |
1279 | + self.theme = Theme(self.theme_path) |
1280 | + |
1281 | + self._stage_width = None |
1282 | + self._stage_height = None |
1283 | + |
1284 | + # Network options specify the server type and extra options |
1285 | + self.network_options = { |
1286 | + 'type': 'local', |
1287 | + 'host': 'localhost', |
1288 | + 'port': 55545} |
1289 | + |
1290 | + def read_config_file(self): |
1291 | + '''Read in the config file.''' |
1292 | + self.content_conf = os.path.join(self.config_dir, 'content.conf') |
1293 | + self.content = ConfigParser.ConfigParser() |
1294 | + self.content.readfp(open(self.content_conf)) |
1295 | |
1296 | def update_configuration(self): |
1297 | - """ |
1298 | - Read configuration files again and update this object. |
1299 | - """ |
1300 | - try: |
1301 | - self.content_config.readfp(open(self.content_conf)) |
1302 | - self.preferences.readfp(open(self.preferences_conf)) |
1303 | - except ParsingError: |
1304 | - raise Exception("Syntax error in configuration file.") |
1305 | - except IOError: |
1306 | - raise IOError("Couldn't read config file.") |
1307 | + '''Read configuration file again and update this object.''' |
1308 | + self.content.readfp(open(self.content_conf)) |
1309 | |
1310 | - def write_content_value(self, section, option, value, sanitize=False): |
1311 | + def write_content_value(self, section, option, value): |
1312 | """Write a new value to the content configuration file.""" |
1313 | - if self.TEST_DIR and not sanitize: |
1314 | - self.taint('content', section, option) |
1315 | |
1316 | - try: |
1317 | - self.content_config.set(section, option, value) |
1318 | + def write_value(section, option, value): |
1319 | + '''Actually write the value to the section and option.''' |
1320 | + self.content.set(section, option, value) |
1321 | cfg_file = file(self.content_conf, 'w') |
1322 | - self.content_config.write(cfg_file) |
1323 | + self.content.write(cfg_file) |
1324 | + |
1325 | + try: |
1326 | + write_value(section, option, value) |
1327 | except NoSectionError: |
1328 | - raise Exception("No Section to set in content.conf file") |
1329 | + # Provide an upgrade path to additions of new sections. |
1330 | + shutil.rmtree(self.config_dir) |
1331 | + self.resources.create_configuration() |
1332 | + self.read_config_file() |
1333 | + write_value(section, option, value) |
1334 | except NoOptionError: |
1335 | raise Exception("No Option to set in content.conf file") |
1336 | |
1337 | - def write_preference_value(self, section, option, value, sanitize=False): |
1338 | - """Write a new value to the preferences configuration file.""" |
1339 | - if self.TEST_DIR and not sanitize: |
1340 | - self.taint('preferences', section, option) |
1341 | - |
1342 | - try: |
1343 | - self.preferences.set(section, option, value) |
1344 | - cfg_file = file(self.preferences_conf, 'w') |
1345 | - self.preferences.write(cfg_file) |
1346 | - except NoSectionError: |
1347 | - raise Exception("No Section to set in preferences.conf file") |
1348 | - except NoOptionError: |
1349 | - raise Exception("No Option to set in preferences.conf file") |
1350 | - |
1351 | - def get_stage_width(self): |
1352 | - '''Get the stage width from the preferences''' |
1353 | - if self.stage_width: |
1354 | - return self.stage_width |
1355 | - |
1356 | - try: |
1357 | - self.stage_width = self.preferences.getint("General", "stage_width") |
1358 | - except (NoSectionError, NoOptionError): |
1359 | - self.stage_width = 1366 # Default |
1360 | - |
1361 | - return self.stage_width |
1362 | - |
1363 | - def set_stage_width(self, new_width, sanitize=False): |
1364 | - '''Set the stage width for the in memory instance''' |
1365 | - # Taint data if in test mode |
1366 | - if self.TEST_DIR and not sanitize: |
1367 | - kwargs = {'new_width' : self.get_stage_width(), |
1368 | - 'sanitize' : True} |
1369 | - self.taint_in_memory(self.set_stage_width, kwargs) |
1370 | - |
1371 | - self.stage_width = new_width |
1372 | - |
1373 | - def get_stage_height(self): |
1374 | - '''Get the stage height from the preferences''' |
1375 | - if self.stage_height: |
1376 | - return self.stage_height |
1377 | - |
1378 | - try: |
1379 | - self.stage_height = self.preferences.getint("General", |
1380 | - "stage_height") |
1381 | - except (NoSectionError, NoOptionError): |
1382 | - self.stage_height = 768 # Default |
1383 | - |
1384 | - return self.stage_height |
1385 | - |
1386 | - def set_stage_height(self, new_height, sanitize=False): |
1387 | - '''Set the stage height for the in memory instance''' |
1388 | - # Taint data if in test mode |
1389 | - if self.TEST_DIR and not sanitize: |
1390 | - kwargs = {'new_height' : self.get_stage_height(), |
1391 | - 'sanitize' : True} |
1392 | - self.taint_in_memory(self.set_stage_height, kwargs) |
1393 | - |
1394 | - self.stage_height = new_height |
1395 | - |
1396 | - def get_theme_path(self): |
1397 | - """ |
1398 | - Get absolute path of the used theme. |
1399 | - @return: string (path) |
1400 | - """ |
1401 | - theme_path = os.path.join(self.get_cfg_dir(), 'themes') |
1402 | - try: |
1403 | - theme = self.preferences.get("General", "theme") |
1404 | - except (NoSectionError, NoOptionError): |
1405 | - return os.path.join(theme_path, "Default") |
1406 | - |
1407 | - if os.path.exists(os.path.join(theme_path, theme)): |
1408 | - return os.path.join(theme_path, theme) |
1409 | - else: |
1410 | - return os.path.join(theme_path, "Default") |
1411 | - |
1412 | + def _get_stage_width(self): |
1413 | + '''stage_width property getter.''' |
1414 | + if self._stage_width: |
1415 | + return self._stage_width |
1416 | + |
1417 | + self._stage_width = self.content.getint("General", "stage_width") |
1418 | + return self._stage_width |
1419 | + |
1420 | + def _set_stage_width(self, new_width): |
1421 | + '''stage_width property setter.''' |
1422 | + self._stage_width = new_width |
1423 | + |
1424 | + stage_width = property(_get_stage_width, _set_stage_width) |
1425 | + |
1426 | + def _get_stage_height(self): |
1427 | + '''stage_height property getter.''' |
1428 | + if self._stage_height: |
1429 | + return self._stage_height |
1430 | + |
1431 | + self._stage_height = self.content.getint("General", "stage_height") |
1432 | + return self._stage_height |
1433 | + |
1434 | + def _set_stage_height(self, new_height): |
1435 | + '''stage_height property setter.''' |
1436 | + self._stage_height = new_height |
1437 | + |
1438 | + stage_height = property(_get_stage_height, _set_stage_height) |
1439 | + |
1440 | + @property |
1441 | + def theme_path(self): |
1442 | + '''Path to the currently selected theme.''' |
1443 | + theme_path = os.path.join(self.data_dir, 'themes') |
1444 | + theme = self.content.get("General", "theme") |
1445 | + return os.path.join(theme_path, theme) |
1446 | + |
1447 | + @property |
1448 | def tray_icon_enabled(self): |
1449 | - """ |
1450 | - Display tray icon. |
1451 | - @return: boolean |
1452 | - """ |
1453 | - try: |
1454 | - result = self.preferences.getboolean("General", "display_icon") |
1455 | - except (NoSectionError, NoOptionError): |
1456 | - return False |
1457 | - return result |
1458 | - |
1459 | - def get_feed_fetch_interval(self): |
1460 | - """ |
1461 | - Get time interval (in minutes) for feed fetching. |
1462 | - @return: Integer |
1463 | - """ |
1464 | - try: |
1465 | - result = self.content_config.getint("RSS", "fetch_interval") |
1466 | - except (NoSectionError, NoOptionError): |
1467 | - return 60 # Default is one hour |
1468 | - return result |
1469 | - |
1470 | - def get_port(self): |
1471 | - """ |
1472 | - Get backend server's port number. |
1473 | - @return: Integer |
1474 | - """ |
1475 | - try: |
1476 | - result = self.preferences.getint("General", "backend_port") |
1477 | - except (NoSectionError, NoOptionError): |
1478 | - return 45054 # Default port |
1479 | - return result |
1480 | - |
1481 | - def get_video_folders(self): |
1482 | - """ |
1483 | - Get list of video folders |
1484 | - @return:String Array |
1485 | - """ |
1486 | - try: |
1487 | - video_list = self.content_config.get("Videos", "folders") |
1488 | - result = self._is_valid_media_folder(video_list.split(';')) |
1489 | - except (NoSectionError, NoOptionError): |
1490 | - return [] |
1491 | - return result |
1492 | - |
1493 | - def get_music_folders(self): |
1494 | - """ |
1495 | - Get list of music folders |
1496 | - @return: String Array |
1497 | - """ |
1498 | - try: |
1499 | - music_list = self.content_config.get("Music", "folders") |
1500 | - result = music_list.split(';') |
1501 | - except (NoSectionError, NoOptionError): |
1502 | - return [] |
1503 | - return result |
1504 | - |
1505 | - def get_image_folders(self): |
1506 | - """ |
1507 | - Get list of image folders |
1508 | - @return: String Array |
1509 | - """ |
1510 | - try: |
1511 | - image_list = self.content_config.get("Images", "folders") |
1512 | - result = self._is_valid_media_folder(image_list.split(';')) |
1513 | - except (NoSectionError, NoOptionError): |
1514 | - return [] |
1515 | - return result |
1516 | - |
1517 | - def get_feeds(self): |
1518 | - """ |
1519 | - Get list of feeds. |
1520 | - @return: String Array |
1521 | - """ |
1522 | - try: |
1523 | - rss_feeds = self.content_config.get("RSS", "feeds") |
1524 | - result = rss_feeds.split(';') |
1525 | - except (NoSectionError, NoOptionError): |
1526 | - return [] |
1527 | - return result |
1528 | - |
1529 | - def get_weather_location(self): |
1530 | - """ |
1531 | - Get list of weather location codes. |
1532 | - @return: List of strings |
1533 | - """ |
1534 | - try: |
1535 | - location = self.content_config.get("Weather", "location") |
1536 | - except (NoSectionError, NoOptionError): |
1537 | - location = '' |
1538 | - return location |
1539 | - |
1540 | - def display_weather_in_frontend(self): |
1541 | - """ |
1542 | - Should we display weather in frontend |
1543 | - @return: Boolean |
1544 | - """ |
1545 | - try: |
1546 | - result = self.content_config.getboolean("Weather", |
1547 | - "display_in_menu") |
1548 | - except (NoSectionError, NoOptionError): |
1549 | - return False |
1550 | - return result |
1551 | - |
1552 | - def display_cd_eject_in_frontend(self): |
1553 | - """ |
1554 | - Should we display the cd eject button in frontend |
1555 | - @return: Boolean |
1556 | - """ |
1557 | - try: |
1558 | - result = self.content_config.getboolean("CD", |
1559 | - "display_eject_in_menu") |
1560 | - except (NoSectionError, NoOptionError): |
1561 | - return False |
1562 | - return result |
1563 | - |
1564 | - def download_video_metadata(self): |
1565 | - """ |
1566 | - Get True if video metadata should be downloaded, otherwise False |
1567 | - """ |
1568 | - try: |
1569 | - result = self.content_config.getboolean("Videos", |
1570 | - "download_metadata") |
1571 | - except (NoSectionError, NoOptionError): |
1572 | - return False |
1573 | - return result |
1574 | - |
1575 | + '''Boolean to indicate whether or not to show the tray icon.''' |
1576 | + return self.content.getboolean("General", "display_icon") |
1577 | + |
1578 | + @property |
1579 | + def feed_fetch_interval(self): |
1580 | + '''Time interval (in minutes) for feed fetching.''' |
1581 | + return self.content.getint("RSS", "fetch_interval") |
1582 | + |
1583 | + @property |
1584 | + def port(self): |
1585 | + '''Server's port number.''' |
1586 | + return self.content.getint("General", "backend_port") |
1587 | + |
1588 | + @property |
1589 | + def media_folders(self): |
1590 | + '''Return a list of folders for media.''' |
1591 | + media = self.content.get("Media", "folders") |
1592 | + return self._is_valid_media_folder(media.split(';')) |
1593 | + |
1594 | + @property |
1595 | + def feeds(self): |
1596 | + '''List of feeds.''' |
1597 | + rss_feeds = self.content.get("RSS", "feeds") |
1598 | + return rss_feeds.split(';') |
1599 | + |
1600 | + @property |
1601 | + def weather_location(self): |
1602 | + '''User's weather location.''' |
1603 | + return self.content.get("Weather", "location") |
1604 | + |
1605 | + @property |
1606 | + def display_weather_in_client(self): |
1607 | + '''Boolean to tell if weather should be displayed.''' |
1608 | + return self.content.getboolean("Weather", "display_in_menu") |
1609 | + |
1610 | + @property |
1611 | + def download_metadata(self): |
1612 | + '''Boolean to tell if metadata should be downloaded.''' |
1613 | + return self.content.getboolean("Media", "download_metadata") |
1614 | + |
1615 | + @property |
1616 | def download_album_art(self): |
1617 | - """ |
1618 | - Download album art from the Internet. |
1619 | - @return: boolean |
1620 | - """ |
1621 | - try: |
1622 | - result = self.content_config.getboolean("Music", |
1623 | - "download_album_art") |
1624 | - except (NoSectionError, NoOptionError): |
1625 | - return False |
1626 | - return result |
1627 | + '''Boolean to tell if album art should be downloaded.''' |
1628 | + return self.content.getboolean("Media", "download_album_art") |
1629 | |
1630 | + @property |
1631 | def download_lyrics(self): |
1632 | - """ |
1633 | - Download song lyrics from the Internet. |
1634 | - @return: boolean |
1635 | - """ |
1636 | - try: |
1637 | - result = self.content_config.getboolean("Music", "download_lyrics") |
1638 | - except (NoSectionError, NoOptionError): |
1639 | - return False |
1640 | - return result |
1641 | - |
1642 | - def display_hidden_files_folders(self): |
1643 | - """If True hidden files and folders will be added to image library.""" |
1644 | - try: |
1645 | - result = self.content_config.getboolean("Images", |
1646 | - "display_hidden_files_folders") |
1647 | - except (NoSectionError, NoOptionError): |
1648 | - return False |
1649 | - return result |
1650 | - |
1651 | + '''Boolean to tell if lyrics should be downloaded.''' |
1652 | + return self.content.getboolean("Media", "download_lyrics") |
1653 | + |
1654 | + @property |
1655 | def show_effects(self): |
1656 | - """ |
1657 | - Use animations on user interface. |
1658 | - @return: boolean |
1659 | - """ |
1660 | - try: |
1661 | - result = self.preferences.getboolean("General", "show_effects") |
1662 | - except (NoSectionError, NoOptionError): |
1663 | - return False |
1664 | - return result |
1665 | + '''Boolean to tell if effects should be shown.''' |
1666 | + return self.content.getboolean("General", "show_effects") |
1667 | |
1668 | + @property |
1669 | def transition_effect(self): |
1670 | - """ |
1671 | - Which transition effect should be used when switching screens. |
1672 | - @return: string (name of the effect) |
1673 | - """ |
1674 | - try: |
1675 | - result = self.preferences.get("General", "transition_effect") |
1676 | - except (NoSectionError, NoOptionError): |
1677 | - return False |
1678 | - return result |
1679 | - |
1680 | - def get_theme_name(self): |
1681 | - """ |
1682 | - Get the name of the current theme. |
1683 | - @return: string (theme name) |
1684 | - """ |
1685 | - try: |
1686 | - result = self.preferences.get("General", "theme") |
1687 | - except (NoSectionError, NoOptionError): |
1688 | - result = "Default" |
1689 | - return result |
1690 | - |
1691 | + '''Internal name of the user's selected screen transition style.''' |
1692 | + return self.content.get("General", "transition_effect") |
1693 | + |
1694 | + @property |
1695 | + def theme_name(self): |
1696 | + '''Name of the current theme.''' |
1697 | + return self.content.get("General", "theme") |
1698 | + |
1699 | + @property |
1700 | def start_in_fullscreen(self): |
1701 | '''Boolean to determine whether to start in fullscreen mode or not.''' |
1702 | - try: |
1703 | - result = self.preferences.getboolean("General", |
1704 | - "start_in_fullscreen") |
1705 | - except (NoSectionError, NoOptionError): |
1706 | - return True |
1707 | - return result |
1708 | + return self.content.getboolean("General", "start_in_fullscreen") |
1709 | |
1710 | + @property |
1711 | def start_auto_server(self): |
1712 | - """ |
1713 | - Return True if backend server should auto start |
1714 | - @return: boolean |
1715 | - """ |
1716 | - try: |
1717 | - result = self.preferences.getboolean("General", |
1718 | - "start_server_auto") |
1719 | - except (NoSectionError, NoOptionError): |
1720 | - return False |
1721 | - return result |
1722 | + '''Boolean to tell if the server should be auto started.''' |
1723 | + return self.content.getboolean("General", "start_server_auto") |
1724 | |
1725 | + @property |
1726 | def history_size(self): |
1727 | - """ |
1728 | - Get screen history size. This determines how many screen are kept in |
1729 | - memory when scrolling. |
1730 | - @return: Integer |
1731 | - """ |
1732 | - try: |
1733 | - result = self.preferences.getint("General", "history_size") |
1734 | - except (NoSectionError, NoOptionError): |
1735 | - return 10 |
1736 | - return result |
1737 | + '''Number of screens to hold in history.''' |
1738 | + return self.content.getint("General", "history_size") |
1739 | |
1740 | - def get_slideshow_step(self): |
1741 | - """ |
1742 | - Get the slideshow step in seconds used by the photo slideshow |
1743 | - @return: Integer |
1744 | - """ |
1745 | - try: |
1746 | - result = self.preferences.getint("Photographs", "slideshow_step") |
1747 | - except (NoSectionError, NoOptionError): |
1748 | - return 5 |
1749 | - return result |
1750 | + @property |
1751 | + def slideshow_step(self): |
1752 | + '''Amount of seconds before the slideshow steps to the next image.''' |
1753 | + return self.content.getint("Photographs", "slideshow_step") |
1754 | |
1755 | # Implements MessageHandler interface |
1756 | def handleMessage(self, message): |
1757 | @@ -480,73 +222,99 @@ |
1758 | """ |
1759 | if message.get_type() == MessageType.CONTENT_CONF_UPDATED: |
1760 | self.update_configuration() |
1761 | - elif message.get_type() == MessageType.PREFERENCES_CONF_UPDATED: |
1762 | - self.update_configuration() |
1763 | - |
1764 | - def get_cfg_dir(self): |
1765 | - """ |
1766 | - Get the entertainer configuration dir. |
1767 | - @return: strings |
1768 | - """ |
1769 | - return self.cfg_dir |
1770 | - |
1771 | - def taint(self, kind, section, option): |
1772 | - '''Taint any data that is written to config files so that it can be |
1773 | - returned to a known state later''' |
1774 | - if kind == 'content': |
1775 | - value = self.content_config.get(section, option) |
1776 | - elif kind == 'preferences': |
1777 | - value = self.preferences.get(section, option) |
1778 | - else: |
1779 | - raise Exception( |
1780 | - "Taint type must be either 'content' or 'preferences'") |
1781 | - |
1782 | - key = "%s %s %s" % (kind, section, option) |
1783 | - self._tainted[key] = value |
1784 | - |
1785 | - def taint_in_memory(self, callback, kwargs): |
1786 | - '''Taint any data that is stored in memory. Callback is used as the |
1787 | - key to a tainted_in_memory dictionary whose stored value is a |
1788 | - dictionary of arguments (kwargs) needed to restore the original |
1789 | - value''' |
1790 | - self._tainted_in_memory[callback] = kwargs |
1791 | - |
1792 | - def sanitize(self): |
1793 | - '''Restore all tainted values to their original state and clear the |
1794 | - tainted dictionary''' |
1795 | - # Sanitize values in config files |
1796 | - for key in self._tainted.keys(): |
1797 | - # tokens will contain type, section, and option |
1798 | - tokens = key.split() |
1799 | - if tokens[0] == 'content': |
1800 | - # Write in the sanitize mode so taint checking will be skipped |
1801 | - self.write_content_value(tokens[1], tokens[2], |
1802 | - self._tainted[key], sanitize=True) |
1803 | - elif tokens[0] == 'preferences': |
1804 | - # Write in the sanitize mode so taint checking will be skipped |
1805 | - self.write_preference_value(tokens[1], tokens[2], |
1806 | - self._tainted[key], sanitize=True) |
1807 | - |
1808 | - self.update_configuration() |
1809 | - self._tainted = {} |
1810 | - |
1811 | - # Sanitize values in memory |
1812 | - for callback in self._tainted_in_memory.keys(): |
1813 | - kwargs = self._tainted_in_memory[callback] |
1814 | - callback(**kwargs) |
1815 | - |
1816 | - self._tainted_in_memory = {} |
1817 | |
1818 | def _is_valid_media_folder(self, folder_list): |
1819 | """Return a folder list where eventual cache folders are removed""" |
1820 | - cache_dir = os.path.join(self.get_cfg_dir(), 'cache') |
1821 | valid_folder_list = [] |
1822 | |
1823 | for folder in folder_list: |
1824 | - common_prefix = os.path.commonprefix([cache_dir, folder]) |
1825 | - if common_prefix != cache_dir: |
1826 | - # folder is not a subfolder of cache_dir we accept it |
1827 | + common_prefix = os.path.commonprefix([self.cache_dir, folder]) |
1828 | + if common_prefix != self.cache_dir: |
1829 | + # if folder is not a subfolder of cache_dir, we accept it |
1830 | valid_folder_list.append(folder) |
1831 | |
1832 | return valid_folder_list |
1833 | |
1834 | + |
1835 | +class Resources(object): |
1836 | + '''A Wrapper for the XDG directories. Also handles creation of a new setup |
1837 | + if the Entertainer directories within the XDG directories are missing. This |
1838 | + class is meant solely to support Configuration and should NOT be publicly |
1839 | + used because of testing conflicts that would occur.''' |
1840 | + |
1841 | + def __init__(self, resource='entertainer', config_testing_dir=None): |
1842 | + if config_testing_dir is None: |
1843 | + self.cache_dir = os.path.join(BaseDirectory.xdg_cache_home, |
1844 | + resource) |
1845 | + self.config_dir = os.path.join(BaseDirectory.xdg_config_home, |
1846 | + resource) |
1847 | + self.data_dir = os.path.join(BaseDirectory.xdg_data_home, resource) |
1848 | + else: |
1849 | + # Running in the test suite so don't create the XDG locations. |
1850 | + self.cache_dir = os.path.join(config_testing_dir, 'cache') |
1851 | + self.config_dir = os.path.join(config_testing_dir, 'config') |
1852 | + self.data_dir = os.path.join(config_testing_dir, 'data') |
1853 | + |
1854 | + # Ensure that the directories exist. |
1855 | + if not os.path.exists(self.cache_dir): |
1856 | + self.create_cache_hierarchy() |
1857 | + if not os.path.exists(self.config_dir): |
1858 | + self.create_configuration() |
1859 | + if not os.path.exists(self.data_dir): |
1860 | + self.create_initial_data() |
1861 | + |
1862 | + def create_cache_hierarchy(self): |
1863 | + '''Create the cache hierarchy that is assumed to exist.''' |
1864 | + os.makedirs(os.path.join(self.cache_dir, 'album_art')) |
1865 | + os.makedirs(os.path.join(self.cache_dir, 'movie_art')) |
1866 | + os.makedirs(os.path.join(self.cache_dir, 'thumbnails', 'image')) |
1867 | + os.makedirs(os.path.join(self.cache_dir, 'thumbnails', 'video')) |
1868 | + |
1869 | + def create_configuration(self): |
1870 | + '''Create the user's configuration area and populate with the default |
1871 | + content configuration.''' |
1872 | + |
1873 | + # Copy configuration data from a dev branch if we can |
1874 | + dev_config = os.path.abspath(os.path.dirname(__file__) + '/../cfg') |
1875 | + if os.path.exists(dev_config): |
1876 | + shutil.copytree(dev_config, self.config_dir) |
1877 | + return |
1878 | + |
1879 | + # Must be a proper installation so install from the system location. |
1880 | + installed_config = os.path.join(self.installed_data_dir, 'cfg') |
1881 | + shutil.copytree(installed_config, self.config_dir) |
1882 | + |
1883 | + def create_initial_data(self): |
1884 | + '''Create the initial data directory and populate with the default data |
1885 | + used by Entertainer.''' |
1886 | + os.mkdir(self.data_dir) |
1887 | + |
1888 | + # Copy configuration data from a dev branch if we can |
1889 | + dev_config = os.path.abspath(os.path.dirname(__file__) + '/../themes') |
1890 | + if os.path.exists(dev_config): |
1891 | + shutil.copytree(dev_config, os.path.join(self.data_dir, 'themes')) |
1892 | + return |
1893 | + |
1894 | + # Must be a proper installation so install from the system location. |
1895 | + themes_dir = os.path.join(self.installed_data_dir, 'themes') |
1896 | + shutil.copytree(themes_dir, os.path.join(self.data_dir, 'themes')) |
1897 | + |
1898 | + @property |
1899 | + def installed_data_dir(self): |
1900 | + '''Since different distros decide on the install path differently, scan |
1901 | + through the system data directories to find where Entertainer was |
1902 | + installed.''' |
1903 | + |
1904 | + # Get rid of the user's home data directory because we don't want it. |
1905 | + system_data_dirs = [data_dir for data_dir in BaseDirectory.xdg_data_dirs |
1906 | + if not data_dir.startswith(BaseDirectory.xdg_data_home)] |
1907 | + |
1908 | + installed_data_dir = None |
1909 | + for directory in system_data_dirs: |
1910 | + possible_data_dir = os.path.join(directory, 'entertainer') |
1911 | + if os.path.exists(possible_data_dir): |
1912 | + installed_data_dir = possible_data_dir |
1913 | + break |
1914 | + |
1915 | + return installed_data_dir |
1916 | + |
1917 | |
1918 | === renamed directory 'entertainerlib/backend/core/db' => 'entertainerlib/db' |
1919 | === modified file 'entertainerlib/db/connection.py' |
1920 | --- entertainerlib/backend/core/db/connection.py 2009-05-11 23:37:27 +0000 |
1921 | +++ entertainerlib/db/connection.py 2009-06-17 02:53:31 +0000 |
1922 | @@ -27,7 +27,7 @@ |
1923 | 'PhotoImage': '''CREATE TABLE `photoimage` ( |
1924 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
1925 | filename TEXT, |
1926 | - thumb VARCHAR(32), |
1927 | + thumbnail VARCHAR(32), |
1928 | title TEXT, |
1929 | description TEXT, |
1930 | creation_date DATETIME, |
1931 | |
1932 | === modified file 'entertainerlib/db/models.py' |
1933 | --- entertainerlib/backend/core/db/models.py 2009-04-28 23:54:49 +0000 |
1934 | +++ entertainerlib/db/models.py 2009-06-17 02:53:31 +0000 |
1935 | @@ -6,6 +6,7 @@ |
1936 | from storm.properties import Bool, DateTime, Int, Unicode |
1937 | from storm.references import Reference, ReferenceSet |
1938 | |
1939 | + |
1940 | class BaseModel(Storm): |
1941 | '''Abstract class from which all Entertainer models inherit.''' |
1942 | |
1943 | @@ -96,7 +97,7 @@ |
1944 | __storm_table__ = 'photoimage' |
1945 | id = Int(primary=True) |
1946 | filename = Unicode() |
1947 | - thumb = Unicode() |
1948 | + thumbnail = Unicode() |
1949 | title = Unicode() |
1950 | description = Unicode() |
1951 | creation_date = DateTime() |
1952 | @@ -108,7 +109,7 @@ |
1953 | ret = { |
1954 | 'id': self.id, |
1955 | 'filename': self.filename, |
1956 | - 'thumb': self.thumb, |
1957 | + 'thumbnail': self.thumbnail, |
1958 | 'title': self.title, |
1959 | 'description': self.description, |
1960 | 'creation_date': self.creation_date, |
1961 | |
1962 | === added file 'entertainerlib/dialog.py' |
1963 | --- entertainerlib/dialog.py 1970-01-01 00:00:00 +0000 |
1964 | +++ entertainerlib/dialog.py 2009-08-27 03:18:41 +0000 |
1965 | @@ -0,0 +1,1110 @@ |
1966 | +# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
1967 | +'''Dialogs for Entertainer.''' |
1968 | +# pylint: disable-msg=C0302 |
1969 | + |
1970 | +import os |
1971 | +import shutil |
1972 | +import socket |
1973 | +import sys |
1974 | +import tarfile |
1975 | + |
1976 | +import gtk |
1977 | +import gtk.glade |
1978 | + |
1979 | +from entertainerlib.backend.core.message import Message |
1980 | +from entertainerlib.backend.core.message_bus_proxy import MessageBusProxy |
1981 | +from entertainerlib.backend.core.message_type_priority import MessageType |
1982 | +from entertainerlib.configuration import Configuration |
1983 | +from entertainerlib.logger import Logger |
1984 | +from entertainerlib.gui.theme import Theme |
1985 | +from entertainerlib.backend.components.feeds.feed_utils import (OPMLParser, |
1986 | + FeedConfigTools) |
1987 | +from entertainerlib.weather import Weather |
1988 | + |
1989 | + |
1990 | +class ManagerDialog: |
1991 | + """ |
1992 | + This is a content management tool for Entertainer media center application. |
1993 | + """ |
1994 | + |
1995 | + # Temporary storage for entered URL |
1996 | + url = "" |
1997 | + GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade") |
1998 | + |
1999 | + def __init__(self, stand_alone): |
2000 | + """ |
2001 | + Initialize content management dialog |
2002 | + @param stand_alone: Boolean, Is this dialog running as a stand alone |
2003 | + process |
2004 | + """ |
2005 | + self.stand_alone = stand_alone |
2006 | + self.config = Configuration() |
2007 | + self.themes = [] |
2008 | + self.weather = Weather() |
2009 | + |
2010 | + # Load glade UI |
2011 | + self.gladefile = os.path.join(self.GLADE_DIR, 'manager.glade') |
2012 | + self.widgets = gtk.glade.XML(self.gladefile) |
2013 | + |
2014 | + # Get content management dialog and bind signal callbacks |
2015 | + self.dialog = self.widgets.get_widget("ManagerDialog") |
2016 | + if (self.dialog): |
2017 | + callback_dic = { |
2018 | + # Dialog-wide callbacks |
2019 | + "on_button_open_list_clicked" : |
2020 | + self.on_button_open_list_clicked, |
2021 | + "on_close_button_clicked" : self.on_close_button_clicked, |
2022 | + "on_ManagerDialog_destroy" : self.on_dialog_closed, |
2023 | + |
2024 | + # Media tab |
2025 | + "on_button_remove_media_clicked" : |
2026 | + self.on_button_remove_media_clicked, |
2027 | + "on_button_add_media_clicked" : |
2028 | + self.on_button_add_media_clicked, |
2029 | + "on_button_edit_media_clicked" : |
2030 | + self.on_button_edit_media_clicked, |
2031 | + "on_checkbutton_video_metadata_toggled" : |
2032 | + self.on_checkbutton_video_metadata_toggled, |
2033 | + "on_lyrics_checkbox_toggled" : self.on_lyrics_checkbox_toggled, |
2034 | + "on_art_checkbox_toggled" : self.on_art_checkbox_toggled, |
2035 | + "on_button_media_rebuild_clicked" : |
2036 | + self.on_button_media_rebuild_clicked, |
2037 | + |
2038 | + # Feed tab |
2039 | + "on_button_add_feed_clicked" : |
2040 | + self.on_button_add_feed_clicked, |
2041 | + "on_button_remove_feed_clicked" : |
2042 | + self.on_button_remove_feed_clicked, |
2043 | + "on_button_edit_feed_clicked" : |
2044 | + self.on_button_edit_feed_clicked, |
2045 | + "on_fetch_interval_spinbutton_value_changed" : |
2046 | + self.on_fetch_interval_spinbutton_value_changed, |
2047 | + "on_url_dialog_delete_event" : self.on_url_dialog_delete_event, |
2048 | + "on_url_dialog_ok_button_clicked" : |
2049 | + self.on_url_dialog_ok_button_clicked, |
2050 | + "on_url_dialog_cancel_button_clicked" : |
2051 | + self.on_url_dialog_cancel_button_clicked, |
2052 | + "on_button_feed_rebuild_clicked" : |
2053 | + self.on_button_feed_rebuild_clicked, |
2054 | + |
2055 | + # Weather tab |
2056 | + "on_button_add_weather_clicked" : |
2057 | + self.on_button_add_weather_clicked, |
2058 | + "on_button_remove_weather_clicked" : |
2059 | + self.on_button_remove_weather_clicked, |
2060 | + "on_weather_display_checkbox_toggled" : |
2061 | + self.on_weather_display_checkbox_toggled, |
2062 | + "on_location_find_button_clicked" : |
2063 | + self.on_location_find_button_clicked, |
2064 | + "on_location_cancel_button_clicked" : |
2065 | + self.on_location_cancel_button_clicked, |
2066 | + "on_location_add_button_clicked" : |
2067 | + self.on_location_add_button_clicked, |
2068 | + "on_location_entry_activate" : self.on_location_entry_activate, |
2069 | + |
2070 | + # User Interface tab |
2071 | + "on_theme_list_cursor_changed" : |
2072 | + self.on_theme_list_cursor_changed, |
2073 | + "on_theme_add_button_clicked" : |
2074 | + self.on_theme_add_button_clicked, |
2075 | + "on_theme_remove_button_clicked" : |
2076 | + self.on_theme_remove_button_clicked, |
2077 | + "on_checkbutton_effects_toggled" : |
2078 | + self.on_checkbutton_effects_toggled, |
2079 | + "on_combobox_effects_changed" : |
2080 | + self.on_combobox_effects_changed, |
2081 | + |
2082 | + # General tab |
2083 | + "on_checkbutton_fullscreen_toggled" : |
2084 | + self.on_checkbutton_fullscreen_toggled, |
2085 | + "on_checkbutton_autostart_toggled" : |
2086 | + self.on_checkbutton_autostart_toggled, |
2087 | + "on_checkbutton_systray_icon_toggled" : |
2088 | + self.on_checkbutton_systray_icon_toggled, |
2089 | + "on_spinbutton_slideshow_step_value_changed": |
2090 | + self.on_spinbutton_slideshow_step_value_changed |
2091 | + } |
2092 | + |
2093 | + self.widgets.signal_autoconnect(callback_dic) |
2094 | + |
2095 | + # Initialize dialog widgets with correct values and show dialog |
2096 | + self.init_dialog_values_from_configure_file() |
2097 | + self.dialog.resize(500, 300) |
2098 | + self.dialog.show() |
2099 | + |
2100 | + # Initialize location list in search dialog |
2101 | + result_list = self.widgets.get_widget("location_results_treeview") |
2102 | + store = gtk.ListStore(str) |
2103 | + result_list.set_model(store) |
2104 | + cell_renderer = gtk.CellRendererText() |
2105 | + column = gtk.TreeViewColumn(_("Location"), cell_renderer, text=0) |
2106 | + result_list.append_column(column) |
2107 | + |
2108 | + def on_dialog_closed(self, widget): |
2109 | + """Callback function for dialog's close button""" |
2110 | + try: |
2111 | + proxy = MessageBusProxy(client_name = "Manager GUI") |
2112 | + proxy.connectToMessageBus() |
2113 | + proxy.sendMessage(Message(MessageType.CONTENT_CONF_UPDATED)) |
2114 | + proxy.disconnectFromMessageBus() |
2115 | + except socket.error: |
2116 | + error = gtk.MessageDialog( |
2117 | + None, gtk.DIALOG_MODAL, |
2118 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _( |
2119 | + "Entertainer backend is not running. " |
2120 | + "Cache cannot be rebuilt." |
2121 | + )) |
2122 | + error.run() |
2123 | + error.destroy() |
2124 | + |
2125 | + if(self.stand_alone): |
2126 | + self.dialog.hide() |
2127 | + self.dialog.destroy() |
2128 | + gtk.main_quit() |
2129 | + else: |
2130 | + self.dialog.hide() |
2131 | + self.dialog.destroy() |
2132 | + |
2133 | + def on_close_button_clicked(self, widget): |
2134 | + """Callback function for dialog's close button""" |
2135 | + if(self.stand_alone): |
2136 | + self.dialog.hide() |
2137 | + self.dialog.destroy() |
2138 | + gtk.main_quit() |
2139 | + else: |
2140 | + self.dialog.hide() |
2141 | + self.dialog.destroy() |
2142 | + |
2143 | + def on_button_add_media_clicked(self, widget): |
2144 | + """Opens add URL dialog. """ |
2145 | + widget = self.widgets.get_widget("treeview_media") |
2146 | + model = widget.get_model() |
2147 | + # Open "Select folder" dialog |
2148 | + dialog = gtk.FileChooserDialog(_("Select folder"), None, |
2149 | + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, |
2150 | + (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, |
2151 | + gtk.STOCK_OPEN,gtk.RESPONSE_OK), |
2152 | + None) |
2153 | + status = dialog.run() |
2154 | + # If folder was selected we add it to model and update config file |
2155 | + if(status == gtk.RESPONSE_OK): |
2156 | + self.add_to_model_and_config(dialog.get_current_folder(), model, |
2157 | + self.media_folders, "Media") |
2158 | + dialog.destroy() |
2159 | + |
2160 | + def on_button_remove_media_clicked(self, widget): |
2161 | + """Remove currently selected folder from media folders""" |
2162 | + widget = self.widgets.get_widget("treeview_media") |
2163 | + model = widget.get_model() |
2164 | + selection = widget.get_selection().get_selected() |
2165 | + if selection[1] == None: |
2166 | + return |
2167 | + rm_folder = model.get_value(selection[1], 0) |
2168 | + self.media_folders.remove(rm_folder) |
2169 | + str_folders = ";".join(self.media_folders) |
2170 | + self.config.write_content_value("Media", "folders", str_folders) |
2171 | + model.remove(selection[1]) |
2172 | + |
2173 | + def on_button_edit_media_clicked(self, widget): |
2174 | + """Edit currently selected folder""" |
2175 | + widget = self.widgets.get_widget("treeview_media") |
2176 | + url_dialog = self.widgets.get_widget("url_dialog") |
2177 | + url_entry = self.widgets.get_widget("url_entry") |
2178 | + model = widget.get_model() |
2179 | + selection = widget.get_selection().get_selected() |
2180 | + if selection[1] == None: |
2181 | + return |
2182 | + folder = model.get_value(selection[1], 0) |
2183 | + url_entry.set_text(folder) |
2184 | + url_dialog.set_title(_("Edit URL")) |
2185 | + status = url_dialog.run() |
2186 | + if status == gtk.RESPONSE_OK and os.path.exists(self.url): |
2187 | + # Update list model |
2188 | + model.set_value(selection[1], 0, self.url) |
2189 | + # Update configure file |
2190 | + pos = self.media_folders.index(folder) |
2191 | + self.media_folders.remove(folder) |
2192 | + self.media_folders.insert(pos, self.url) |
2193 | + str_folders = ";".join(self.media_folders) |
2194 | + self.config.write_content_value("Media", "folders", |
2195 | + str_folders) |
2196 | + |
2197 | + def on_checkbutton_autostart_toggled(self, widget): |
2198 | + '''Server autostart checkbox toggled.''' |
2199 | + self.config.write_content_value("General", "start_server_auto", |
2200 | + widget.get_active()) |
2201 | + |
2202 | + def on_checkbutton_fullscreen_toggled(self, widget): |
2203 | + '''Start in fullscreen checkbox toggled.''' |
2204 | + self.config.write_content_value("General", "start_in_fullscreen", |
2205 | + widget.get_active()) |
2206 | + |
2207 | + def on_checkbutton_systray_icon_toggled(self, widget): |
2208 | + """System Tray Icon checkbox toggled""" |
2209 | + self.config.write_content_value("General", "display_icon", |
2210 | + widget.get_active()) |
2211 | + |
2212 | + def on_checkbutton_video_metadata_toggled(self, widget): |
2213 | + """ |
2214 | + Download video file metadata from internet |
2215 | + @param widget: GTK-Widget |
2216 | + """ |
2217 | + self.config.write_content_value("Media", "download_metadata", |
2218 | + widget.get_active()) |
2219 | + |
2220 | + def on_button_add_feed_clicked(self, widget): |
2221 | + """Opens add feed dialog. """ |
2222 | + widget = self.widgets.get_widget("treeview_feeds") |
2223 | + url_dialog = self.widgets.get_widget("url_dialog") |
2224 | + model = widget.get_model() |
2225 | + # Open dialog |
2226 | + url_dialog.set_title(_("Add RSS-feed")) |
2227 | + status = url_dialog.run() |
2228 | + # If folder was selected we add it to model and update config file |
2229 | + if(status == gtk.RESPONSE_OK): |
2230 | + model.append([self.url]) |
2231 | + self.feeds.append(self.url) |
2232 | + str_folders = ";".join(self.feeds) |
2233 | + self.config.write_content_value("RSS", "feeds", str_folders) |
2234 | + |
2235 | + def on_button_remove_feed_clicked(self, widget): |
2236 | + """Remove currently selected reed from RSS-feeds""" |
2237 | + widget = self.widgets.get_widget("treeview_feeds") |
2238 | + model = widget.get_model() |
2239 | + selection = widget.get_selection().get_selected() |
2240 | + if selection[1] == None: |
2241 | + return |
2242 | + rm_folder = model.get_value(selection[1], 0) |
2243 | + self.feeds.remove(rm_folder) |
2244 | + str_folders = ";".join(self.feeds) |
2245 | + self.config.write_content_value("RSS", "feeds", str_folders) |
2246 | + model.remove(selection[1]) |
2247 | + |
2248 | + def on_button_edit_feed_clicked(self, widget): |
2249 | + """Edit currently selected feed""" |
2250 | + widget = self.widgets.get_widget("treeview_feeds") |
2251 | + url_dialog = self.widgets.get_widget("url_dialog") |
2252 | + url_entry = self.widgets.get_widget("url_entry") |
2253 | + model = widget.get_model() |
2254 | + selection = widget.get_selection().get_selected() |
2255 | + if selection[1] == None: |
2256 | + return |
2257 | + feed = model.get_value(selection[1], 0) |
2258 | + url_entry.set_text(feed) |
2259 | + url_dialog.set_title(_("Edit feed")) |
2260 | + status = url_dialog.run() |
2261 | + if status == gtk.RESPONSE_OK: |
2262 | + # Update list model |
2263 | + model.set_value(selection[1], 0, self.url) |
2264 | + # Update configure file |
2265 | + pos = self.feeds.index(feed) |
2266 | + self.feeds.remove(feed) |
2267 | + self.feeds.insert(pos, self.url) |
2268 | + str_feeds = ";".join(self.feeds) |
2269 | + self.config.write_content_value("RSS", "feeds", str_feeds) |
2270 | + |
2271 | + def on_button_open_list_clicked(self, widget): |
2272 | + """Opens the open feed source dialog""" |
2273 | + open_dialog = OpenFeedSourceDialog( |
2274 | + self.widgets.get_widget("treeview_feeds"), self.feeds) |
2275 | + open_dialog.dialog.connect("destroy", open.on_closeButton_clicked) |
2276 | + open_dialog.dialog.hide() |
2277 | + open_dialog.dialog.destroy() |
2278 | + |
2279 | + def on_fetch_interval_spinbutton_value_changed(self, widget): |
2280 | + self.config.write_content_value("RSS", "fetch_interval", |
2281 | + widget.get_value_as_int()) |
2282 | + |
2283 | + def on_spinbutton_slideshow_step_value_changed(self, widget): |
2284 | + """Activation of slideshow effects""" |
2285 | + self.config.write_content_value("Photographs", "slideshow_step", |
2286 | + int(widget.get_value())) |
2287 | + |
2288 | + def on_lyrics_checkbox_toggled(self, widget): |
2289 | + self.config.write_content_value("Media", "download_lyrics", |
2290 | + widget.get_active()) |
2291 | + |
2292 | + def on_art_checkbox_toggled(self, widget): |
2293 | + self.config.write_content_value("Media", "download_album_art", |
2294 | + widget.get_active()) |
2295 | + |
2296 | + def on_url_dialog_ok_button_clicked(self, widget): |
2297 | + """URL dialog OK button pressed. Sets self.url""" |
2298 | + url_dialog = self.widgets.get_widget("url_dialog") |
2299 | + url_entry = self.widgets.get_widget("url_entry") |
2300 | + url_dialog.hide() |
2301 | + self.url = url_entry.get_text() |
2302 | + url_entry.set_text("") |
2303 | + url_dialog.response(gtk.RESPONSE_OK) |
2304 | + |
2305 | + def on_url_dialog_cancel_button_clicked(self, widget): |
2306 | + """URL dialog cancelled. Hides dialog""" |
2307 | + url_dialog = self.widgets.get_widget("url_dialog") |
2308 | + url_entry = self.widgets.get_widget("url_entry") |
2309 | + url_dialog.hide() |
2310 | + url_entry.set_text("") |
2311 | + url_dialog.response(gtk.RESPONSE_CANCEL) |
2312 | + |
2313 | + def on_url_dialog_delete_event(self, widget, data): |
2314 | + """Dialog's X clicked. Hides dialog""" |
2315 | + url_dialog = self.widgets.get_widget("url_dialog") |
2316 | + url_entry = self.widgets.get_widget("url_entry") |
2317 | + url_dialog.hide() |
2318 | + url_entry.set_text("") |
2319 | + url_dialog.response(gtk.RESPONSE_CANCEL) |
2320 | + return True |
2321 | + |
2322 | + def on_button_add_weather_clicked(self, widget): |
2323 | + """ |
2324 | + Open location search dialog |
2325 | + @param widget: GTK-Widget |
2326 | + """ |
2327 | + location_dialog = self.widgets.get_widget("weather_search_dialog") |
2328 | + location_dialog.set_title(_("Add location")) |
2329 | + |
2330 | + # Clear results |
2331 | + result_list = self.widgets.get_widget("location_results_treeview") |
2332 | + model = result_list.get_model() |
2333 | + model.clear() |
2334 | + |
2335 | + status = location_dialog.run() |
2336 | + if(status == gtk.RESPONSE_OK): |
2337 | + print "Added" |
2338 | + |
2339 | + def on_button_remove_weather_clicked(self, widget): |
2340 | + """ |
2341 | + Remove currently selected weather location from the location list |
2342 | + @param widget: GTK-Widget |
2343 | + """ |
2344 | + widget = self.widgets.get_widget("treeview_locations") |
2345 | + model = widget.get_model() |
2346 | + self.weather_locations = [] |
2347 | + str_folders = "" |
2348 | + self.config.write_content_value("Weather", "location", str_folders) |
2349 | + model.clear() |
2350 | + |
2351 | + def on_weather_display_checkbox_toggled(self, widget): |
2352 | + """ |
2353 | + Checkbox that defines should we use weather conditions |
2354 | + @param widget: GTK-Widget |
2355 | + """ |
2356 | + self.config.write_content_value("Weather", "display_in_menu", |
2357 | + widget.get_active()) |
2358 | + if widget.get_active(): |
2359 | + self.widgets.get_widget("button_add_weather").set_sensitive(True) |
2360 | + self.widgets.get_widget( |
2361 | + "button_remove_weather").set_sensitive(True) |
2362 | + self.widgets.get_widget("treeview_locations").set_sensitive(True) |
2363 | + else: |
2364 | + self.widgets.get_widget("button_add_weather").set_sensitive(False) |
2365 | + self.widgets.get_widget( |
2366 | + "button_remove_weather").set_sensitive(False) |
2367 | + self.widgets.get_widget("treeview_locations").set_sensitive(False) |
2368 | + |
2369 | + def on_location_find_button_clicked(self, widget): |
2370 | + """ |
2371 | + Find location by search string |
2372 | + @param widget: GTK-Widget |
2373 | + """ |
2374 | + add_button = self.widgets.get_widget("location_add_button") |
2375 | + search_term = self.widgets.get_widget("location_entry").get_text() |
2376 | + result_list = self.widgets.get_widget("location_results_treeview") |
2377 | + model = result_list.get_model() |
2378 | + model.clear() |
2379 | + if search_term != "": |
2380 | + self.weather.location = search_term |
2381 | + self.weather.refresh() |
2382 | + results = self.weather.forecasts |
2383 | + if len(results) > 0: |
2384 | + add_button.set_sensitive(True) |
2385 | + model.append([search_term]) |
2386 | + result_list.set_cursor(0) |
2387 | + else: |
2388 | + model.clear() |
2389 | + model.append([_("Location Not Found!")]) |
2390 | + add_button.set_sensitive(False) |
2391 | + |
2392 | + def on_location_cancel_button_clicked(self, widget): |
2393 | + """ |
2394 | + Close location search dialog without taking any actions.0 |
2395 | + @param widget: GTK-Widget |
2396 | + """ |
2397 | + location_dialog = self.widgets.get_widget("weather_search_dialog") |
2398 | + location_entry = self.widgets.get_widget("location_entry") |
2399 | + location_dialog.hide() |
2400 | + location_entry.set_text("") |
2401 | + location_dialog.response(gtk.RESPONSE_CANCEL) |
2402 | + |
2403 | + def on_location_add_button_clicked(self, widget): |
2404 | + """ |
2405 | + Add selected location to location list and close search dialog |
2406 | + @param widget: GTK-Widget |
2407 | + """ |
2408 | + self.weather_locations = [] |
2409 | + result_list = self.widgets.get_widget("location_results_treeview") |
2410 | + model = result_list.get_model() |
2411 | + selection = result_list.get_selection().get_selected() |
2412 | + if selection[1] == None: |
2413 | + return |
2414 | + location_string = model.get_value(selection[1], 0) |
2415 | + |
2416 | + location_list = self.widgets.get_widget("treeview_locations") |
2417 | + loc_model = location_list.get_model() |
2418 | + loc_model.clear() |
2419 | + loc_model.append([location_string]) |
2420 | + |
2421 | + self.weather_locations.append(location_string) |
2422 | + str_locations = ";".join(self.weather_locations) |
2423 | + self.config.write_content_value("Weather", "location", str_locations) |
2424 | + |
2425 | + location_dialog = self.widgets.get_widget("weather_search_dialog") |
2426 | + location_entry = self.widgets.get_widget("location_entry") |
2427 | + location_dialog.hide() |
2428 | + location_entry.set_text("") |
2429 | + location_dialog.response(gtk.RESPONSE_CANCEL) |
2430 | + |
2431 | + def on_location_entry_activate(self, widget): |
2432 | + """ |
2433 | + User hit enter on location entry to start search |
2434 | + @param widget: GTK-Widget |
2435 | + """ |
2436 | + self.on_location_find_button_clicked(widget) |
2437 | + |
2438 | + def on_button_media_rebuild_clicked(self, widget): |
2439 | + '''Rebuild media cache requested.''' |
2440 | + try: |
2441 | + proxy = MessageBusProxy(client_name = "Manager GUI") |
2442 | + proxy.connectToMessageBus() |
2443 | + proxy.sendMessage(Message(MessageType.REBUILD_IMAGE_CACHE)) |
2444 | + proxy.sendMessage(Message(MessageType.REBUILD_MUSIC_CACHE)) |
2445 | + proxy.sendMessage(Message(MessageType.REBUILD_VIDEO_CACHE)) |
2446 | + proxy.disconnectFromMessageBus() |
2447 | + except socket.error: |
2448 | + error = gtk.MessageDialog( |
2449 | + None, gtk.DIALOG_MODAL, |
2450 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _( |
2451 | + "Entertainer backend is not running. " |
2452 | + "Cache cannot be rebuilt." |
2453 | + )) |
2454 | + error.run() |
2455 | + error.destroy() |
2456 | + |
2457 | + def on_button_feed_rebuild_clicked(self, widget): |
2458 | + """ |
2459 | + Rebuild feed cache requested |
2460 | + @param widget: GTK-Widget |
2461 | + """ |
2462 | + #We need the user to confirm the rebuild feed cache request |
2463 | + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, |
2464 | + gtk.BUTTONS_OK_CANCEL, |
2465 | + _("This will completely remove any feed entries in the cache!")) |
2466 | + status = dialog.run() |
2467 | + #If user has ok'd the request send the message to the message bus |
2468 | + if(status == gtk.RESPONSE_OK): |
2469 | + try: |
2470 | + proxy = MessageBusProxy(client_name = "Manager GUI") |
2471 | + proxy.connectToMessageBus() |
2472 | + proxy.sendMessage(Message(MessageType.REBUILD_FEED_CACHE)) |
2473 | + proxy.disconnectFromMessageBus() |
2474 | + except socket.error: |
2475 | + error = gtk.MessageDialog( |
2476 | + None, gtk.DIALOG_MODAL, |
2477 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _( |
2478 | + "Entertainer backend is not running. " |
2479 | + "Cache cannot be rebuilt." |
2480 | + )) |
2481 | + error.run() |
2482 | + error.destroy() |
2483 | + |
2484 | + dialog.destroy() |
2485 | + |
2486 | + def on_theme_add_button_clicked(self, widget): |
2487 | + """Add theme button clicked""" |
2488 | + themelist = self.widgets.get_widget("theme_list") |
2489 | + model = themelist.get_model() |
2490 | + # Open "Select folder" dialog |
2491 | + dialog = gtk.FileChooserDialog(_("Select theme package file"), |
2492 | + None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, |
2493 | + gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK), None) |
2494 | + file_filter = gtk.FileFilter() |
2495 | + file_filter.set_name(_("Theme package (tar.gz)")) |
2496 | + file_filter.add_pattern("*.tar.gz") |
2497 | + dialog.add_filter(file_filter) |
2498 | + status = dialog.run() |
2499 | + |
2500 | + # If theme was selected with file chooser |
2501 | + if(status == gtk.RESPONSE_OK): |
2502 | + package = dialog.get_filename() |
2503 | + tar = tarfile.open(package, 'r:gz') # Open tar.gz package |
2504 | + |
2505 | + # Make sure that package contains configuration file (is theme) |
2506 | + content = tar.getnames() |
2507 | + theme_name = None |
2508 | + is_theme = False |
2509 | + for element in content: |
2510 | + if element[-10:] == "theme.conf": |
2511 | + theme_name = element[:-11] |
2512 | + is_theme = True |
2513 | + |
2514 | + # Install them |
2515 | + if is_theme: |
2516 | + tar.extractall(os.path.join(self.config.data_dir, 'themes')) |
2517 | + model.insert(len(model), [theme_name]) |
2518 | + else: |
2519 | + error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, |
2520 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Invalid theme file!")) |
2521 | + error.run() |
2522 | + error.destroy() |
2523 | + |
2524 | + dialog.destroy() |
2525 | + |
2526 | + def on_theme_list_cursor_changed(self, widget): |
2527 | + """Executed when theme is changed in theme list. Update preview.""" |
2528 | + # Get currently selected theme |
2529 | + themelist = self.widgets.get_widget("theme_list") |
2530 | + model = themelist.get_model() |
2531 | + selection = themelist.get_selection().get_selected() |
2532 | + name = model.get_value(selection[1], 0) |
2533 | + themedir = os.path.join(self.config.data_dir, 'themes', name) |
2534 | + theme = Theme(theme_path=themedir) |
2535 | + |
2536 | + # Update preview |
2537 | + image = self.widgets.get_widget("theme_image") |
2538 | + image.set_from_file(os.path.join(themedir, "thumbnail.png")) |
2539 | + name = self.widgets.get_widget("name_label") |
2540 | + name.set_text(theme.getName()) |
2541 | + author = self.widgets.get_widget("author_label") |
2542 | + author.set_text(theme.getAuthor()) |
2543 | + license_label = self.widgets.get_widget("license_label") |
2544 | + license_label.set_text(theme.getLicence()) |
2545 | + copyright_label = self.widgets.get_widget("copyright_label") |
2546 | + copyright_label.set_text(theme.getCopyright()) |
2547 | + comment = self.widgets.get_widget("comment_label") |
2548 | + comment.set_text(theme.getComment()) |
2549 | + |
2550 | + self.config.write_content_value("General", "theme", name.get_text()) |
2551 | + |
2552 | + def on_theme_remove_button_clicked(self, widget): |
2553 | + """Remove theme button clicked""" |
2554 | + # Get currently selected theme |
2555 | + themelist = self.widgets.get_widget("theme_list") |
2556 | + model = themelist.get_model() |
2557 | + selection = themelist.get_selection().get_selected() |
2558 | + name = model.get_value(selection[1], 0) |
2559 | + |
2560 | + confirm = gtk.MessageDialog(None, |
2561 | + gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, |
2562 | + _("Are you sure you want to delete\ntheme: %(name)s") % \ |
2563 | + {'name': name}) |
2564 | + status = confirm.run() |
2565 | + confirm.destroy() |
2566 | + if(status == gtk.RESPONSE_YES): |
2567 | + themedir = os.path.join(self.config.data_dir, 'themes', name) |
2568 | + shutil.rmtree(themedir) |
2569 | + model.remove(selection[1]) |
2570 | + themelist.set_cursor(0) |
2571 | + self.themes.remove(name) |
2572 | + |
2573 | + def on_checkbutton_effects_toggled(self, widget): |
2574 | + """Effect checkbox toggled""" |
2575 | + combobox = self.widgets.get_widget("combobox_effects") |
2576 | + combobox.set_sensitive(widget.get_active()) |
2577 | + self.config.write_content_value("General", "show_effects", |
2578 | + widget.get_active()) |
2579 | + |
2580 | + def on_combobox_effects_changed(self, widget): |
2581 | + """User changed effect for screen transitions""" |
2582 | + text = widget.get_active_text() |
2583 | + if text == _("No effect"): |
2584 | + english_text = "No effect" |
2585 | + if text == _("Crossfade"): |
2586 | + english_text = "Crossfade" |
2587 | + if text == _("Zoom and fade"): |
2588 | + english_text = "Zoom and fade" |
2589 | + if text == _("Slide"): |
2590 | + english_text = "Slide" |
2591 | + self.config.write_content_value("General", "transition_effect", |
2592 | + english_text) |
2593 | + |
2594 | + def init_dialog_values_from_configure_file(self): |
2595 | + """Read configuration and set dialog widget values with read values. |
2596 | + """ |
2597 | + # == Videos == |
2598 | + medialist_widget = self.widgets.get_widget("treeview_media") |
2599 | + mediastore = gtk.ListStore(str) |
2600 | + |
2601 | + cell_renderer = gtk.CellRendererText() |
2602 | + column = gtk.TreeViewColumn(_("Folders"), cell_renderer, text=0) |
2603 | + medialist_widget.append_column(column) |
2604 | + |
2605 | + self.media_folders = self.config.media_folders |
2606 | + |
2607 | + # Fill model with folders read from config file |
2608 | + self.init_model(mediastore, self.media_folders) |
2609 | + |
2610 | + medialist_widget.set_model(mediastore) |
2611 | + |
2612 | + # Checkboxes |
2613 | + metadata_checkbox = self.widgets.get_widget("video_metadata_checkbox") |
2614 | + metadata_checkbox.set_active(self.config.download_metadata) |
2615 | + |
2616 | + art_checkbox = self.widgets.get_widget("art_checkbox") |
2617 | + art_checkbox.set_active(self.config.download_album_art) |
2618 | + |
2619 | + lyrics_checkbox = self.widgets.get_widget("lyrics_checkbox") |
2620 | + lyrics_checkbox.set_active(self.config.download_lyrics) |
2621 | + |
2622 | + # == RSS-feeds == |
2623 | + feedlist_widget = self.widgets.get_widget("treeview_feeds") |
2624 | + feed_model = gtk.ListStore(str) |
2625 | + |
2626 | + rss_cell = gtk.CellRendererText() |
2627 | + rss_column = gtk.TreeViewColumn(_("Feeds"), rss_cell, text=0) |
2628 | + feedlist_widget.append_column(rss_column) |
2629 | + |
2630 | + self.feeds = self.config.feeds |
2631 | + |
2632 | + # Fill model with folders read from config file |
2633 | + for i in range(len(self.feeds)): |
2634 | + feed_model.insert(i, [self.feeds[i]]) |
2635 | + |
2636 | + feedlist_widget.set_model(feed_model) |
2637 | + |
2638 | + # Interval spinner |
2639 | + interval_spinner = self.widgets.get_widget("fetch_interval_spinbutton") |
2640 | + interval_val = self.config.feed_fetch_interval |
2641 | + if interval_val < 15: |
2642 | + interval_val = 15 |
2643 | + elif interval_val > 900: |
2644 | + interval_val = 900 |
2645 | + interval_spinner.set_value(interval_val) |
2646 | + |
2647 | + # == Weather == |
2648 | + locationlist_widget = self.widgets.get_widget("treeview_locations") |
2649 | + location_model = gtk.ListStore(str) |
2650 | + |
2651 | + loc_cell = gtk.CellRendererText() |
2652 | + location_column = gtk.TreeViewColumn(_("Location"), loc_cell, text=0) |
2653 | + locationlist_widget.append_column(location_column) |
2654 | + |
2655 | + self.weather_location = self.config.weather_location |
2656 | + |
2657 | + # Fill model with location read from config file |
2658 | + location_model.insert(0, [self.weather_location]) |
2659 | + locationlist_widget.set_model(location_model) |
2660 | + |
2661 | + weather_display_checkbox = self.widgets.get_widget( |
2662 | + "weather_display_checkbox") |
2663 | + display_val = self.config.display_weather_in_client |
2664 | + weather_display_checkbox.set_active(display_val) |
2665 | + if not display_val: |
2666 | + self.widgets.get_widget("button_add_weather").set_sensitive(False) |
2667 | + self.widgets.get_widget("button_remove_weather").set_sensitive( |
2668 | + False) |
2669 | + self.widgets.get_widget("treeview_locations").set_sensitive(False) |
2670 | + |
2671 | + # == User Interface == |
2672 | + self.load_themes() |
2673 | + current_theme = self.config.theme_name |
2674 | + |
2675 | + themelist_widget = self.widgets.get_widget("theme_list") |
2676 | + model = gtk.ListStore(str) |
2677 | + |
2678 | + cell_renderer = gtk.CellRendererText() |
2679 | + column = gtk.TreeViewColumn("Themes", cell_renderer, text=0) |
2680 | + themelist_widget.append_column(column) |
2681 | + |
2682 | + # Fill model with installed themes |
2683 | + for i in range(len(self.themes)): |
2684 | + model.insert(i, [self.themes[i]]) |
2685 | + |
2686 | + themelist_widget.set_model(model) |
2687 | + |
2688 | + # Set current theme selected in theme list |
2689 | + index = model.get_iter_first() |
2690 | + unselected = True |
2691 | + index_counter = 0 |
2692 | + while(unselected): |
2693 | + name = model.get_value(index, 0) |
2694 | + if name == current_theme: |
2695 | + unselected = False |
2696 | + themelist_widget.set_cursor(index_counter) |
2697 | + index = model.iter_next(index) |
2698 | + index_counter += 1 |
2699 | + |
2700 | + effect_checkbox = self.widgets.get_widget("checkbutton_effects") |
2701 | + effect_combobox = self.widgets.get_widget("combobox_effects") |
2702 | + if self.config.show_effects: |
2703 | + effect_checkbox.set_active(True) |
2704 | + effect_combobox.set_sensitive(True) |
2705 | + else: |
2706 | + effect_checkbox.set_active(False) |
2707 | + effect_combobox.set_sensitive(False) |
2708 | + |
2709 | + # Set Effect Combobox value (Text values are set in glade file) |
2710 | + effect = self.config.transition_effect |
2711 | + if effect == "No effect": |
2712 | + effect_combobox.set_active(0) |
2713 | + if effect == "Crossfade": |
2714 | + effect_combobox.set_active(1) |
2715 | + if effect == "Zoom and fade": |
2716 | + effect_combobox.set_active(2) |
2717 | + if effect == "Slide": |
2718 | + effect_combobox.set_active(3) |
2719 | + |
2720 | + # == General == |
2721 | + checkbutton_fullscreen = self.widgets.get_widget( |
2722 | + "checkbutton_fullscreen") |
2723 | + if self.config.start_in_fullscreen: |
2724 | + checkbutton_fullscreen.set_active(True) |
2725 | + else: |
2726 | + checkbutton_fullscreen.set_active(False) |
2727 | + |
2728 | + checkbutton_autostart = self.widgets.get_widget("checkbutton_autostart") |
2729 | + if self.config.start_auto_server: |
2730 | + checkbutton_autostart.set_active(True) |
2731 | + else: |
2732 | + checkbutton_autostart.set_active(False) |
2733 | + |
2734 | + checkbutton_systray_icon = self.widgets.get_widget( |
2735 | + "checkbutton_systray_icon") |
2736 | + if self.config.tray_icon_enabled: |
2737 | + checkbutton_systray_icon.set_active(True) |
2738 | + else: |
2739 | + checkbutton_systray_icon.set_active(False) |
2740 | + |
2741 | + spinbutton_slideshow_step = self.widgets.get_widget( |
2742 | + "spinbutton_slideshow_step") |
2743 | + spinbutton_slideshow_step.set_value(self.config.slideshow_step) |
2744 | + |
2745 | + def add_to_model_and_config(self, selected_folder, model, folders, kind): |
2746 | + """ |
2747 | + Add selected_folder to the model and the folders list while updating |
2748 | + the configuration item section specified by type |
2749 | + """ |
2750 | + if not selected_folder in folders: |
2751 | + model.append([selected_folder]) |
2752 | + |
2753 | + if(folders == None): |
2754 | + folders = [selected_folder] |
2755 | + else: |
2756 | + folders.append(selected_folder) |
2757 | + |
2758 | + if "" in folders: |
2759 | + folders.remove("") |
2760 | + str_folders = ";".join(folders) |
2761 | + self.config.write_content_value(kind, "folders", str_folders) |
2762 | + |
2763 | + def init_model(self, model, items): |
2764 | + """Fill model with items from supplied list""" |
2765 | + for i in range(len(items)): |
2766 | + if not str(items[i]).strip() == "": |
2767 | + model.insert(i, [items[i]]) |
2768 | + |
2769 | + def load_themes(self): |
2770 | + """Load themes""" |
2771 | + themes = os.listdir(os.path.join(self.config.data_dir, 'themes')) |
2772 | + for element in themes: |
2773 | + theme = os.path.join(self.config.data_dir, 'themes', element) |
2774 | + if os.path.isdir(theme): |
2775 | + self.themes.append(element) |
2776 | + |
2777 | + |
2778 | +class LogViewer: |
2779 | + """ |
2780 | + Implements dialog that allows user to see logged events. |
2781 | + |
2782 | + This dialog is used to check Entertainer logfiles. It reads all data from |
2783 | + selected file and saves rows to self.log_rows. Then it filters unwanted |
2784 | + rows away by calling self.filterMessages(). This method adds rows to |
2785 | + ListStore, which is the model of TreeView object. |
2786 | + |
2787 | + Combobox and refresh -button actions read files again |
2788 | + Checkbox actions just filter current rows again |
2789 | + """ |
2790 | + |
2791 | + GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade") |
2792 | + |
2793 | + # Is this dialog running as a stand alone process |
2794 | + __STAND_ALONE = None |
2795 | + |
2796 | + widgets = None |
2797 | + dialog = None |
2798 | + log_store = None |
2799 | + log_rows = [] |
2800 | + |
2801 | + gladefile = os.path.join(GLADE_DIR, "log_dialog.glade") |
2802 | + |
2803 | + def __init__(self, stand_alone): |
2804 | + self.logfile_entertainer = Configuration().LOG |
2805 | + self.logger = Logger().getLogger('utils.log_viewer') |
2806 | + |
2807 | + self.__STAND_ALONE = stand_alone |
2808 | + try: |
2809 | + self.widgets = gtk.glade.XML(self.gladefile) |
2810 | + except RuntimeError: |
2811 | + self.logger.critical("Couldn't open glade file: " + self.gladefile) |
2812 | + sys.exit(1) |
2813 | + callback_dic = { |
2814 | + "on_close_log_button_clicked" : self.on_close_log_button_clicked, |
2815 | + "on_log_refresh_button_clicked" : self.update_log_rows, |
2816 | + "on_checkbutton_debug_toggled" : self.filter_messages, |
2817 | + "on_checkbutton_critical_toggled" : self.filter_messages, |
2818 | + "on_checkbutton_error_toggled" : self.filter_messages, |
2819 | + "on_checkbutton_warning_toggled" : self.filter_messages, |
2820 | + "on_checkbutton_info_toggled" : self.filter_messages } |
2821 | + |
2822 | + self.widgets.signal_autoconnect(callback_dic) |
2823 | + |
2824 | + # Create log treeview |
2825 | + treeview = self.widgets.get_widget("treeview_log") |
2826 | + cell_renderer1 = gtk.CellRendererText() |
2827 | + cell_renderer2 = gtk.CellRendererText() |
2828 | + cell_renderer3 = gtk.CellRendererText() |
2829 | + cell_renderer4 = gtk.CellRendererText() |
2830 | + |
2831 | + column1 = gtk.TreeViewColumn("Date") |
2832 | + column1.pack_start(cell_renderer1, True) |
2833 | + column1.set_attributes(cell_renderer1, text = 0) |
2834 | + |
2835 | + column2 = gtk.TreeViewColumn("Time") |
2836 | + column2.pack_start(cell_renderer2, True) |
2837 | + column2.set_attributes(cell_renderer2, text = 1) |
2838 | + |
2839 | + column3 = gtk.TreeViewColumn("Type") |
2840 | + column3.pack_start(cell_renderer3, True) |
2841 | + column3.set_attributes(cell_renderer3, text = 2) |
2842 | + |
2843 | + column4 = gtk.TreeViewColumn("Message") |
2844 | + column4.pack_end(cell_renderer4, True) |
2845 | + column4.set_attributes(cell_renderer4, text = 3) |
2846 | + |
2847 | + treeview.append_column(column1) |
2848 | + treeview.append_column(column2) |
2849 | + treeview.append_column(column3) |
2850 | + treeview.append_column(column4) |
2851 | + treeview.set_headers_visible(True) |
2852 | + |
2853 | + # Set model to view and read data from logfile |
2854 | + self.log_store = gtk.ListStore(str, str, str, str) |
2855 | + treeview.set_model(self.log_store) |
2856 | + self.update_log_rows() |
2857 | + |
2858 | + # Show Log viewer dialog |
2859 | + self.dialog = self.widgets.get_widget("LogDialog") |
2860 | + self.dialog.resize(750, 500) |
2861 | + self.dialog.connect("destroy", self.on_close_log_button_clicked) |
2862 | + self.dialog.show() |
2863 | + |
2864 | + def update_log_rows(self, widget=None): |
2865 | + """Read logfile and udpate treeview""" |
2866 | + self.log_rows[:] = [] |
2867 | + |
2868 | + try: |
2869 | + for line in open(self.logfile_entertainer, 'r'): |
2870 | + try: |
2871 | + line_table = line.split() |
2872 | + message = ' '.join(line_table[3:]) |
2873 | + row = line_table[:3] + [message] |
2874 | + parsed_row = parse_row(row) |
2875 | + self.log_rows.append(parsed_row) |
2876 | + except IndexError: |
2877 | + print "Cannot parse log line: ", line |
2878 | + except IOError: |
2879 | + print "Cannot find logfile: ", self.logfile_entertainer |
2880 | + |
2881 | + # Reverse so that the latest message is at top |
2882 | + self.log_rows.reverse() |
2883 | + # Filter unwated message types |
2884 | + self.filter_messages() |
2885 | + |
2886 | + def filter_messages(self, widget = None): |
2887 | + """Checks which message types should be displayed on treeview""" |
2888 | + if self.log_store: |
2889 | + self.log_store.clear() |
2890 | + |
2891 | + debug = self.widgets.get_widget("checkbutton_debug").get_active() |
2892 | + critical = self.widgets.get_widget("checkbutton_critical").get_active() |
2893 | + error = self.widgets.get_widget("checkbutton_error").get_active() |
2894 | + warning = self.widgets.get_widget("checkbutton_warning").get_active() |
2895 | + info = self.widgets.get_widget("checkbutton_info").get_active() |
2896 | + |
2897 | + for element in self.log_rows: |
2898 | + if element[2] == "DEBUG" and debug: |
2899 | + self.log_store.append(element) |
2900 | + elif element[2] == "CRITICAL" and critical: |
2901 | + self.log_store.append(element) |
2902 | + elif element[2] == "ERROR" and error: |
2903 | + self.log_store.append(element) |
2904 | + elif element[2] == "WARNING" and warning: |
2905 | + self.log_store.append(element) |
2906 | + elif element[2] == "INFO" and info: |
2907 | + self.log_store.append(element) |
2908 | + |
2909 | + # Signal handlers |
2910 | + def on_close_log_button_clicked(self, widget): |
2911 | + """ |
2912 | + If running as a stand alone process, quit. |
2913 | + Otherwise only destroy dialog. |
2914 | + """ |
2915 | + self.dialog.hide() |
2916 | + self.dialog.destroy() |
2917 | + if(self.__STAND_ALONE): |
2918 | + gtk.main_quit() |
2919 | + |
2920 | + |
2921 | +def parse_row(row): |
2922 | + """ |
2923 | + This parses the input list into a list suitable for the logviewer |
2924 | + @author Joshua Scotton |
2925 | + @param row The input list [Date, Time, Class, Type + Description] |
2926 | + """ |
2927 | + if row[3][:5] == "DEBUG": |
2928 | + return [row[0], row[1], "DEBUG", |
2929 | + row[2] + ": " + row[3][5:]] |
2930 | + elif row[3][:8] == "CRITICAL": |
2931 | + return [row[0], row[1], "CRITICAL", |
2932 | + row[2] + ": " + row[3][8:]] |
2933 | + elif row[3][:5] == "ERROR": |
2934 | + return [row[0], row[1], "ERROR", |
2935 | + row[2] + ": " + row[3][5:]] |
2936 | + elif row[3][:7] == "WARNING": |
2937 | + return [row[0], row[1], "WARNING", |
2938 | + row[2] + ": " + row[3][7:]] |
2939 | + elif row[3][:4] == "INFO": |
2940 | + return [row[0], row[1], "INFO", |
2941 | + row[2] + ": " + row[3][4:]] |
2942 | + |
2943 | + |
2944 | +class OpenFeedSourceDialog: |
2945 | + '''Feed source reader dialog''' |
2946 | + |
2947 | + GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade") |
2948 | + |
2949 | + widgets = None |
2950 | + dialog = None |
2951 | + tree_widget = None |
2952 | + feeds = None |
2953 | + url = None |
2954 | + |
2955 | + def __init__(self, the_widget, the_feeds): |
2956 | + """initialises the gtk window and displays it""" |
2957 | + |
2958 | + #feeds is a pointer to a list of feeds from the config file |
2959 | + self.feeds = the_feeds |
2960 | + |
2961 | + #needed so we can add feeds to the feed list widget |
2962 | + self.tree_widget = the_widget |
2963 | + |
2964 | + # Load glade UI |
2965 | + self.gladefile = os.path.join(self.GLADE_DIR, |
2966 | + "open_feed_source_dialog.glade") |
2967 | + self.widgets = gtk.glade.XML(self.gladefile) |
2968 | + |
2969 | + # Get content management dialog and bind signal callbacks |
2970 | + self.dialog = self.widgets.get_widget("open_source_dialog") |
2971 | + if (self.dialog): |
2972 | + callback_dic = { |
2973 | + "on_fileOpen_clicked" : self.on_fileOpen_clicked, |
2974 | + "on_lifereaButton_clicked" : self.on_lifereaButton_clicked, |
2975 | + "on_enterURL_clicked" : self.on_enterURL_clicked, |
2976 | + "on_closeButton_clicked" : self.on_closeButton_clicked, |
2977 | + "on_url_dialog_ok_button_clicked" : |
2978 | + self.on_url_dialog_ok_button_clicked, |
2979 | + "on_url_dialog_cancel_button_clicked" : |
2980 | + self.on_url_dialog_cancel_button_clicked, |
2981 | + "on_url_dialog_delete_event" : self.on_url_dialog_delete_event |
2982 | + } |
2983 | + |
2984 | + self.widgets.signal_autoconnect(callback_dic) |
2985 | + |
2986 | + # Initilize dialog widgets with correct values and show dialog |
2987 | + self.dialog.resize(300, 200) |
2988 | + self.dialog.run() |
2989 | + |
2990 | + def on_fileOpen_clicked(self, widget): |
2991 | + """Opens add file dialog and then adds all feeds in the opml file |
2992 | + selected """ |
2993 | + |
2994 | + #get the model for the feed list widget so we can add the new feeds |
2995 | + model = self.tree_widget.get_model() |
2996 | + |
2997 | + #create select file dialog |
2998 | + dialog = gtk.FileChooserDialog(_("Select OPML file"), None, |
2999 | + gtk.FILE_CHOOSER_ACTION_OPEN, |
3000 | + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, |
3001 | + gtk.RESPONSE_OK)) |
3002 | + |
3003 | + #set dialog up to filter for only opml files |
3004 | + file_filter = gtk.FileFilter() |
3005 | + file_filter.set_name(_("OPML files")) |
3006 | + file_filter.add_pattern("*.opml") |
3007 | + dialog.add_filter(file_filter) |
3008 | + |
3009 | + #set dialog up to allow multiple selections |
3010 | + dialog.set_select_multiple(True) |
3011 | + |
3012 | + #run the dialog |
3013 | + status = dialog.run() |
3014 | + |
3015 | + # if file was selected, get list of feeds from it and add to |
3016 | + #model and update config file |
3017 | + if(status == gtk.RESPONSE_OK): |
3018 | + FeedConfigTools().add_file_feeds_to_widget( |
3019 | + dialog.get_filenames(), model, self.feeds) |
3020 | + dialog.destroy() |
3021 | + |
3022 | + def on_lifereaButton_clicked(self, widget): |
3023 | + """Adds any liferea feeds it finds to the feed widget and config file |
3024 | + """ |
3025 | + #get the model |
3026 | + model = self.tree_widget.get_model() |
3027 | + #get the liferea feeds and then send everything to the |
3028 | + #add_file_feeds_to_widget method |
3029 | + FeedConfigTools().add_file_feeds_to_widget( |
3030 | + [OPMLParser().get_liferea_opml()], model, self.feeds) |
3031 | + |
3032 | + def on_enterURL_clicked(self, widget): |
3033 | + """gets a opml file link from a user and adds any feeds it finds to the |
3034 | + feed widget and config file""" |
3035 | + url_dialog = self.widgets.get_widget("url_dialog") |
3036 | + model = self.tree_widget.get_model() |
3037 | + # Open dialog |
3038 | + url_dialog.set_title(_("Add OPML File")) |
3039 | + status = url_dialog.run() |
3040 | + # If folder was selected we add it to model and update config file |
3041 | + if(status == gtk.RESPONSE_OK): |
3042 | + FeedConfigTools().add_file_feeds_to_widget([self.url], |
3043 | + model, self.feeds) |
3044 | + |
3045 | + def on_closeButton_clicked(self, widget): |
3046 | + self.dialog.hide() |
3047 | + self.dialog.destroy() |
3048 | + |
3049 | + def on_url_dialog_ok_button_clicked(self, widget): |
3050 | + """URL dialog OK button pressed. Sets self.url""" |
3051 | + url_dialog = self.widgets.get_widget("url_dialog") |
3052 | + url_entry = self.widgets.get_widget("url_entry") |
3053 | + url_dialog.hide() |
3054 | + self.url = url_entry.get_text() |
3055 | + url_entry.set_text("") |
3056 | + url_dialog.response(gtk.RESPONSE_OK) |
3057 | + |
3058 | + def on_url_dialog_cancel_button_clicked(self, widget): |
3059 | + """URL dialog cancelled. Hides dialog""" |
3060 | + url_dialog = self.widgets.get_widget("url_dialog") |
3061 | + url_entry = self.widgets.get_widget("url_entry") |
3062 | + url_dialog.hide() |
3063 | + url_entry.set_text("") |
3064 | + url_dialog.response(gtk.RESPONSE_CANCEL) |
3065 | + |
3066 | + def on_url_dialog_delete_event(self, widget, data): |
3067 | + """Dialog's X clicked. Hides dialog""" |
3068 | + url_dialog = self.widgets.get_widget("url_dialog") |
3069 | + url_entry = self.widgets.get_widget("url_entry") |
3070 | + url_dialog.hide() |
3071 | + url_entry.set_text("") |
3072 | + url_dialog.response(gtk.RESPONSE_CANCEL) |
3073 | + return True |
3074 | + |
3075 | + |
3076 | |
3077 | === added file 'entertainerlib/download.py' |
3078 | --- entertainerlib/download.py 1970-01-01 00:00:00 +0000 |
3079 | +++ entertainerlib/download.py 2009-07-14 04:24:07 +0000 |
3080 | @@ -0,0 +1,455 @@ |
3081 | +# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2 |
3082 | +'''Downloader classes.''' |
3083 | + |
3084 | +import locale |
3085 | +import os |
3086 | +import re |
3087 | +import socket |
3088 | +import threading |
3089 | +import urllib |
3090 | +from xml.dom import minidom |
3091 | + |
3092 | +import gobject |
3093 | + |
3094 | +# Amazon licence for Entertainer |
3095 | +LICENSE_KEY = "1YCWYZ0SPPAJ27YZZ482" |
3096 | +DEFAULT_LOCALE = "en_US" |
3097 | +ASSOCIATE = "webservices-20" |
3098 | + |
3099 | +# We are not allowed to batch more than 2 requests at once |
3100 | +# http://docs.amazonwebservices.com/AWSEcommerceService/4-0/ |
3101 | +# PgCombiningOperations.html |
3102 | +MAX_BATCH_JOBS = 2 |
3103 | + |
3104 | + |
3105 | +class AlbumArtDownloader(threading.Thread): |
3106 | + """ |
3107 | + Search and download album art from the internet. |
3108 | + |
3109 | + This class is heavily based on Rhythmbox - AlbumArt plugin's class |
3110 | + 'AmazonCoverArtSearch'. That plugin is released under GPLv2 (or higher) |
3111 | + and it's copyrights belong to Gareth Murphy and Martin Szulecki. |
3112 | + |
3113 | + See more: http://www.gnome.org/projects/rhythmbox/ |
3114 | + |
3115 | + If you want better cover search, please contribute to Rhyhtmbox project. |
3116 | + """ |
3117 | + |
3118 | + def __init__(self, album, artist, art_file_path, callback = None): |
3119 | + """ |
3120 | + Initialize album art downloader |
3121 | + @param album: Album title |
3122 | + @param artist: Artist name |
3123 | + @param art_file_path: Path where albumart is saved |
3124 | + @param callback: Callback function that is called after search if set |
3125 | + """ |
3126 | + threading.Thread.__init__(self) |
3127 | + self.setName("AlbumArt Downloader") |
3128 | + self.callback_function = callback # Callback function |
3129 | + self.album = album # Album title |
3130 | + self.artist = artist # Artist name |
3131 | + # Album art files are in this directory |
3132 | + self.path = art_file_path |
3133 | + (self.tld, self.encoding) = self.__get_locale () |
3134 | + |
3135 | + def run(self): |
3136 | + """Start searching and downloading albumart.""" |
3137 | + self.search() |
3138 | + |
3139 | + def __get_locale (self): |
3140 | + '''Get locale information from user\'s machine''' |
3141 | + # "JP is the only locale that correctly takes UTF8 input. |
3142 | + # All other locales use LATIN1." |
3143 | + # http://developer.amazonwebservices.com/connect/ |
3144 | + # entry.jspa?externalID=1295&categoryID=117 |
3145 | + supported_locales = { |
3146 | + "en_US" : ("com", "latin1"), |
3147 | + "en_GB" : ("co.uk", "latin1"), |
3148 | + "de" : ("de", "latin1"), |
3149 | + "ja" : ("jp", "utf8") |
3150 | + } |
3151 | + |
3152 | + lc_id = DEFAULT_LOCALE |
3153 | + default = locale.getdefaultlocale ()[0] |
3154 | + if default: |
3155 | + if supported_locales.has_key (default): |
3156 | + lc_id = default |
3157 | + else: |
3158 | + lang = default.split("_")[0] |
3159 | + if supported_locales.has_key (lang): |
3160 | + lc_id = lang |
3161 | + |
3162 | + return supported_locales[lc_id] |
3163 | + |
3164 | + def __valid_match (self, item): |
3165 | + '''Determine if item matches tag criteria''' |
3166 | + return (hasattr (item, "LargeImage") or hasattr (item, "MediumImage")) \ |
3167 | + and hasattr (item, "ItemAttributes") |
3168 | + |
3169 | + def __tidy_up_string (self, str_input): |
3170 | + """ |
3171 | + Tidy up string. Remove spaces, convert to lowercase and replace chars. |
3172 | + """ |
3173 | + # Lowercase |
3174 | + str_input = str_input.lower () |
3175 | + # Strip |
3176 | + str_input = str_input.strip () |
3177 | + |
3178 | + # TODO: Convert accented to unaccented |
3179 | + str_input = str_input.replace (" - ", " ") |
3180 | + str_input = str_input.replace (": ", " ") |
3181 | + str_input = str_input.replace (" & ", " and ") |
3182 | + |
3183 | + return str_input |
3184 | + |
3185 | + def search(self): |
3186 | + """Search album art from Amazon""" |
3187 | + self.searching = True |
3188 | + self.cancel = False |
3189 | + self.keywords = [] |
3190 | + |
3191 | + st_artist = self.artist or u'Unknown' |
3192 | + st_album = self.album or u'Unknown' |
3193 | + |
3194 | + if st_artist == st_album == u'Unknown': |
3195 | + self.on_search_completed (None) |
3196 | + return |
3197 | + |
3198 | + # Tidy up |
3199 | + |
3200 | + # Replace quote characters |
3201 | + # don't replace single quote: could be important punctuation |
3202 | + for char in ["\""]: |
3203 | + st_artist = st_artist.replace (char, '') |
3204 | + st_album = st_album.replace (char, '') |
3205 | + |
3206 | + |
3207 | + self.st_album = st_album |
3208 | + self.st_artist = st_artist |
3209 | + |
3210 | + # Remove variants of Disc/CD [1-9] from album title before search |
3211 | + for exp in ["\([Dd]isc *[1-9]+\)", "\([Cc][Dd] *[1-9]+\)"]: |
3212 | + p = re.compile (exp) |
3213 | + st_album = p.sub ('', st_album) |
3214 | + |
3215 | + st_album_no_vol = st_album |
3216 | + for exp in ["\(*[Vv]ol.*[1-9]+\)*"]: |
3217 | + p = re.compile (exp) |
3218 | + st_album_no_vol = p.sub ('', st_album_no_vol) |
3219 | + |
3220 | + self.st_album_no_vol = st_album_no_vol |
3221 | + |
3222 | + # Save current search's entry properties |
3223 | + self.search_album = st_album |
3224 | + self.search_artist = st_artist |
3225 | + self.search_album_no_vol = st_album_no_vol |
3226 | + |
3227 | + # TODO: Improve to decrease wrong cover downloads, maybe add severity? |
3228 | + # Assemble list of search keywords (and thus search queries) |
3229 | + if st_album == u'Unknown': |
3230 | + self.keywords.append ("%s Best of" % (st_artist)) |
3231 | + self.keywords.append ("%s Greatest Hits" % (st_artist)) |
3232 | + self.keywords.append ("%s Essential" % (st_artist)) |
3233 | + self.keywords.append ("%s Collection" % (st_artist)) |
3234 | + self.keywords.append ("%s" % (st_artist)) |
3235 | + elif st_artist == u'Unknown': |
3236 | + self.keywords.append ("%s" % (st_album)) |
3237 | + if st_album_no_vol != st_artist: |
3238 | + self.keywords.append ("%s" % (st_album_no_vol)) |
3239 | + self.keywords.append ("Various %s" % (st_album)) |
3240 | + else: |
3241 | + if st_album != st_artist: |
3242 | + self.keywords.append ("%s %s" % (st_artist, st_album)) |
3243 | + if st_album_no_vol != st_album: |
3244 | + self.keywords.append ("%s %s" % |
3245 | + (st_artist, st_album_no_vol)) |
3246 | + self.keywords.append ("Various %s" % (st_album)) |
3247 | + self.keywords.append ("%s" % (st_artist)) |
3248 | + |
3249 | + # Initiate asynchronous search |
3250 | + self.search_next () |
3251 | + |
3252 | + def search_next(self): |
3253 | + """Search again, because the last one didn't find any covers.""" |
3254 | + if len (self.keywords) == 0: |
3255 | + # No keywords left to search -> no results |
3256 | + self.on_search_completed (None) |
3257 | + return False |
3258 | + |
3259 | + self.searching = True |
3260 | + |
3261 | + url = "http://ecs.amazonaws." + self.tld + "/onca/xml" \ |
3262 | + "?Service=AWSECommerceService" \ |
3263 | + "&AWSAccessKeyId=" + LICENSE_KEY + \ |
3264 | + "&AssociateTag=" + ASSOCIATE + \ |
3265 | + "&ResponseGroup=Images,ItemAttributes" \ |
3266 | + "&Operation=ItemSearch" \ |
3267 | + "&ItemSearch.Shared.SearchIndex=Music" |
3268 | + |
3269 | + job = 1 |
3270 | + while job <= MAX_BATCH_JOBS and len (self.keywords) > 0: |
3271 | + keyword = self.keywords.pop (0) |
3272 | + keyword = keyword.encode (self.encoding, "ignore") |
3273 | + keyword = keyword.strip () |
3274 | + keyword = urllib.quote (keyword) |
3275 | + url += "&ItemSearch.%d.Keywords=%s" % (job, keyword) |
3276 | + job += 1 |
3277 | + |
3278 | + # Retrieve search for keyword |
3279 | + temp = urllib.urlopen(url) |
3280 | + search_results = temp.read() |
3281 | + self.on_search_response(search_results) |
3282 | + return True |
3283 | + |
3284 | + def __unmarshal(self, element): |
3285 | + rc = object() |
3286 | + child_elements = [e for e in element.childNodes if isinstance (e, |
3287 | + minidom.Element)] |
3288 | + if child_elements: |
3289 | + for child in child_elements: |
3290 | + key = child.tagName |
3291 | + if hasattr (rc, key): |
3292 | + if not isinstance (getattr (rc, key), list): |
3293 | + setattr (rc, key, [getattr (rc, key)]) |
3294 | + getattr (rc, key).append (self.__unmarshal (child)) |
3295 | + # get_best_match_urls() wants a list, even if there is only |
3296 | + # one item/artist |
3297 | + elif child.tagName in ("Items", "Item", "Artist"): |
3298 | + setattr (rc, key, [self.__unmarshal(child)]) |
3299 | + else: |
3300 | + setattr (rc, key, self.__unmarshal(child)) |
3301 | + else: |
3302 | + rc = "".join ([e.data for e in element.childNodes if isinstance (e, |
3303 | + minidom.Text)]) |
3304 | + return rc |
3305 | + |
3306 | + def on_search_response (self, result_data): |
3307 | + '''Check search results |
3308 | + |
3309 | + If results are not good, we search again with the next keyword. |
3310 | + ''' |
3311 | + if result_data is None: |
3312 | + self.search_next() |
3313 | + return |
3314 | + |
3315 | + try: |
3316 | + xmldoc = minidom.parseString(result_data) |
3317 | + except (TypeError, AttributeError): |
3318 | + self.search_next() |
3319 | + return |
3320 | + |
3321 | + data = self.__unmarshal (xmldoc) |
3322 | + if not hasattr (data, "ItemSearchResponse") or \ |
3323 | + not hasattr (data.ItemSearchResponse, "Items"): |
3324 | + # Something went wrong ... |
3325 | + self.search_next () |
3326 | + else: |
3327 | + # We got some search results |
3328 | + self.on_search_results (data.ItemSearchResponse.Items) |
3329 | + |
3330 | + def on_search_results (self, results): |
3331 | + '''Results were found, now we need to take action.''' |
3332 | + self.on_search_completed (results) |
3333 | + |
3334 | + def on_search_completed (self, result): |
3335 | + """ |
3336 | + Search completed and results found. |
3337 | + |
3338 | + Download large album art image from the first result and save it to |
3339 | + the disk. This function diverges greatly from the rhythmbox |
3340 | + implementation in order to avoid their loader and CoverArtDatabase |
3341 | + """ |
3342 | + self.searching = False |
3343 | + image_urls = self.get_best_match_urls(result) |
3344 | + if len(image_urls) == 0: |
3345 | + return |
3346 | + image_url = image_urls[0] |
3347 | + image_file = urllib.urlopen(image_url) |
3348 | + # base64 encode artist and album so there can be a '/' in the artist |
3349 | + # or album |
3350 | + artist_album = self.artist + " - " + self.album |
3351 | + artist_album = artist_album.encode("base64") |
3352 | + |
3353 | + dest = open(os.path.join(self.path, artist_album + ".jpg"),'w') |
3354 | + dest.write(image_file.read()) |
3355 | + dest.close() |
3356 | + |
3357 | + if self.callback_function is not None: |
3358 | + self.callback_function(self.artist, self.album) |
3359 | + |
3360 | + def get_best_match_urls (self, search_results): |
3361 | + """Return tuple of URL's to large and medium cover of the best match""" |
3362 | + # Default to "no match", our results must match our criteria |
3363 | + |
3364 | + # This code comes from Rhythmbox so we can't control the use of 'filter' |
3365 | + # pylint: disable-msg=W0141 |
3366 | + |
3367 | + if not search_results: |
3368 | + return [] |
3369 | + |
3370 | + best_match = None |
3371 | + |
3372 | + for result in search_results: |
3373 | + if not hasattr (result, "Item"): |
3374 | + # Search was unsuccessful, try next batch job |
3375 | + continue |
3376 | + |
3377 | + items = filter(self.__valid_match, result.Item) |
3378 | + if self.search_album != u'Unknown': |
3379 | + album_check = self.__tidy_up_string (self.search_album) |
3380 | + for item in items: |
3381 | + if not hasattr (item.ItemAttributes, "Title"): |
3382 | + continue |
3383 | + |
3384 | + album = self.__tidy_up_string (item.ItemAttributes.Title) |
3385 | + if album == album_check: |
3386 | + # Found exact album, can not get better than that |
3387 | + best_match = item |
3388 | + break |
3389 | + # If we already found a best_match, just keep checking for |
3390 | + # exact one. Check the results for both an album name that |
3391 | + # contains the name we're searching for, and an album name |
3392 | + # that's a substring of the name we're searching for |
3393 | + elif (best_match is None) and \ |
3394 | + (album.find (album_check) != -1 or |
3395 | + album_check.find (album) != -1): |
3396 | + best_match = item |
3397 | + |
3398 | + # If we still have no definite hit, use first result where artist |
3399 | + # matches |
3400 | + if (self.search_album == u'Unknown' and \ |
3401 | + self.search_artist != u'Unknown'): |
3402 | + artist_check = self.__tidy_up_string (self.search_artist) |
3403 | + if best_match is None: |
3404 | + # Check if artist appears in the Artists list |
3405 | + hit = False |
3406 | + for item in items: |
3407 | + if not hasattr (item.ItemAttributes, "Artist"): |
3408 | + continue |
3409 | + |
3410 | + for artist in item.ItemAttributes.Artist: |
3411 | + artist = self.__tidy_up_string (artist) |
3412 | + if artist.find (artist_check) != -1: |
3413 | + best_match = item |
3414 | + hit = True |
3415 | + break |
3416 | + if hit: |
3417 | + break |
3418 | + |
3419 | + urls = [getattr (best_match, size).URL for size in ("LargeImage", |
3420 | + "MediumImage") |
3421 | + if hasattr (best_match, size)] |
3422 | + if urls: |
3423 | + return urls |
3424 | + |
3425 | + # No search was successful |
3426 | + return [] |
3427 | + |
3428 | + |
3429 | +class LyricsDownloader(object): |
3430 | + """ |
3431 | + Search and download song lyrics from the internet. |
3432 | + Update music cache if lyrics found. |
3433 | + """ |
3434 | + |
3435 | + # The permanent user ID from Lyricsfly |
3436 | + # NOTICE: This is the personal user ID for Entertainer, if you want to |
3437 | + # experiment with the API from lyricsfly you can get an ID here => |
3438 | + # http://lyricsfly.com/api/, don't use this one as abuse of our key may |
3439 | + # invalidate it. |
3440 | + _LYRICSFLY_KEY = 'YzIxOTM4M2NkNGQ4MmRmODEtZW50ZXJ0YWluZXItcHJvamVjdC5jb20=' |
3441 | + |
3442 | + def __init__(self, title, artist, callback): |
3443 | + """ |
3444 | + Initialize lyrics downloader. |
3445 | + @param title: Title of the track |
3446 | + @param artist: Artist of the track |
3447 | + @param callback: Callback function which is called when search is over. |
3448 | + lyrics are given as a parameter to this callback function. |
3449 | + """ |
3450 | + self.title = title.lower() |
3451 | + self.artist = artist.lower() |
3452 | + self.callback = callback |
3453 | + |
3454 | + def search(self): |
3455 | + """ |
3456 | + Search lyrics and download if found. Search is done asynchronously. |
3457 | + This method returns immediately and set callback is called when search |
3458 | + is over. |
3459 | + """ |
3460 | + gobject.timeout_add(2000, self._async_search) |
3461 | + |
3462 | + def _async_search(self): |
3463 | + """ |
3464 | + Search lyrics and download if found |
3465 | + """ |
3466 | + lyrics = "" |
3467 | + self._clean_up_artist_title() |
3468 | + lyrics_xml = self._get_lyrics_xml() |
3469 | + if lyrics_xml is not None: |
3470 | + lyrics = self._parse_lyrics_xml(lyrics_xml) |
3471 | + self.callback(lyrics) |
3472 | + |
3473 | + def _clean_up_artist_title(self): |
3474 | + """ |
3475 | + Clean up the artist and title. |
3476 | + """ |
3477 | + # Clean spaces |
3478 | + self.title = self.title.strip() |
3479 | + self.artist = self.artist.strip() |
3480 | + |
3481 | + # Convert title and artist to use in url, special symbols have to be |
3482 | + # replaced by a '%' not '%xx' |
3483 | + # TODO: Find out what the special symbols are (', &, ...) |
3484 | + # not letters, digits, spaces and ()$^*=:;|#@}{][!,.-_\ |
3485 | + self.artist = urllib.quote(self.artist.encode('utf-8'), |
3486 | + "'&()$^*=:;|#@}{][!,\\") |
3487 | + self.title = urllib.quote(self.title.encode('utf-8'), |
3488 | + "'&()$^*=:;|#@}{][!,\\") |
3489 | + |
3490 | + self.artist = self.artist.replace("'", "%").replace("&", "%") |
3491 | + self.title = self.title.replace("'", "%").replace("&", "%") |
3492 | + |
3493 | + def _get_lyrics_xml(self): |
3494 | + """ |
3495 | + Download the lyrics XML-file. |
3496 | + """ |
3497 | + lyrics_xml = None |
3498 | + |
3499 | + # timeout in seconds |
3500 | + timeout = 5 |
3501 | + socket.setdefaulttimeout(timeout) |
3502 | + |
3503 | + url = "http://lyricsfly.com/api/api.php?i=%s&a=%s&t=%s" \ |
3504 | + % (self._LYRICSFLY_KEY.decode('base64'), self.artist, self.title) |
3505 | + try: |
3506 | + temp = urllib.urlopen(url) |
3507 | + lyrics_xml = temp.read() |
3508 | + except IOError: |
3509 | + return None |
3510 | + |
3511 | + return lyrics_xml |
3512 | + |
3513 | + def _parse_lyrics_xml(self, lyrics_xml): |
3514 | + """Parse lyrics XML and return lyrics string""" |
3515 | + xmldoc = minidom.parseString(lyrics_xml).documentElement |
3516 | + |
3517 | + # Get the lyric from the XML file |
3518 | + try: |
3519 | + lyrics = xmldoc.getElementsByTagName('tx')[0].firstChild.nodeValue |
3520 | + except IndexError: |
3521 | + return '' |
3522 | + |
3523 | + # Clean spaces and enters |
3524 | + lyrics = lyrics.strip().replace('\n', '') |
3525 | + lyrics = lyrics.replace('[br]', '\n') |
3526 | + |
3527 | + # Add the artist and title to the top of the lyric |
3528 | + lyrics = xmldoc.getElementsByTagName('ar')[0].firstChild.nodeValue + \ |
3529 | + ' - ' + xmldoc.getElementsByTagName('tt')[0].firstChild.nodeValue + \ |
3530 | + '\n\n' + lyrics |
3531 | + |
3532 | + xmldoc.unlink() |
3533 | + |
3534 | + return lyrics |
3535 | + |
3536 | |
3537 | === renamed directory 'entertainerlib/utils/glade' => 'entertainerlib/glade' |
3538 | === renamed file 'entertainerlib/utils/glade/entertainer-content-management.glade' => 'entertainerlib/glade/manager.glade' |
3539 | --- entertainerlib/utils/glade/entertainer-content-management.glade 2009-05-31 16:36:18 +0000 |
3540 | +++ entertainerlib/glade/manager.glade 2009-08-23 01:01:18 +0000 |
3541 | @@ -1,1481 +1,2170 @@ |
3542 | -<?xml version="1.0"?> |
3543 | +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> |
3544 | +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> |
3545 | + |
3546 | <glade-interface> |
3547 | - <!-- interface-requires gtk+ 2.16 --> |
3548 | - <!-- interface-naming-policy toplevel-contextual --> |
3549 | - <widget class="GtkDialog" id="ContentManagementDialog"> |
3550 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
3551 | - <property name="border_width">5</property> |
3552 | - <property name="title" translatable="yes">Content management</property> |
3553 | - <property name="modal">True</property> |
3554 | - <property name="window_position">center</property> |
3555 | - <property name="icon_name">applications-multimedia</property> |
3556 | - <property name="type_hint">dialog</property> |
3557 | - <property name="has_separator">False</property> |
3558 | - <signal name="destroy" handler="on_ContentManagementDialog_destroy"/> |
3559 | - <child internal-child="vbox"> |
3560 | - <widget class="GtkVBox" id="dialog-layout"> |
3561 | - <property name="visible">True</property> |
3562 | - <property name="spacing">2</property> |
3563 | - <child> |
3564 | - <widget class="GtkNotebook" id="tabs"> |
3565 | - <property name="visible">True</property> |
3566 | - <property name="can_focus">True</property> |
3567 | - <property name="has_focus">True</property> |
3568 | - <property name="can_default">True</property> |
3569 | - <property name="has_default">True</property> |
3570 | - <child> |
3571 | - <widget class="GtkVBox" id="vbox_videos"> |
3572 | - <property name="visible">True</property> |
3573 | - <child> |
3574 | - <widget class="GtkVBox" id="vbox1"> |
3575 | - <property name="visible">True</property> |
3576 | - <child> |
3577 | - <widget class="GtkFrame" id="frame_video_lib1"> |
3578 | - <property name="visible">True</property> |
3579 | - <property name="border_width">5</property> |
3580 | - <property name="label_xalign">0</property> |
3581 | - <property name="shadow_type">none</property> |
3582 | - <child> |
3583 | - <widget class="GtkAlignment" id="alignment1"> |
3584 | - <property name="visible">True</property> |
3585 | - <property name="left_padding">12</property> |
3586 | - <child> |
3587 | - <widget class="GtkVBox" id="vbox2"> |
3588 | - <property name="visible">True</property> |
3589 | - <child> |
3590 | - <widget class="GtkLabel" id="label_video_folder_tip1"> |
3591 | - <property name="visible">True</property> |
3592 | - <property name="xalign">0</property> |
3593 | - <property name="label" translatable="yes">Entertainer video library scans data from the listed folders.</property> |
3594 | - <property name="single_line_mode">True</property> |
3595 | - </widget> |
3596 | - <packing> |
3597 | - <property name="expand">False</property> |
3598 | - <property name="padding">5</property> |
3599 | - <property name="position">0</property> |
3600 | - </packing> |
3601 | - </child> |
3602 | - <child> |
3603 | - <widget class="GtkHBox" id="hbox2"> |
3604 | - <property name="visible">True</property> |
3605 | - <child> |
3606 | - <widget class="GtkScrolledWindow" id="scrolledwindow1"> |
3607 | - <property name="visible">True</property> |
3608 | - <property name="can_focus">True</property> |
3609 | - <property name="hscrollbar_policy">automatic</property> |
3610 | - <property name="vscrollbar_policy">automatic</property> |
3611 | - <property name="shadow_type">in</property> |
3612 | - <child> |
3613 | - <widget class="GtkTreeView" id="treeview_videos"> |
3614 | - <property name="visible">True</property> |
3615 | - <property name="can_focus">True</property> |
3616 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
3617 | - <property name="enable_search">False</property> |
3618 | - </widget> |
3619 | - </child> |
3620 | - </widget> |
3621 | - <packing> |
3622 | - <property name="position">0</property> |
3623 | - </packing> |
3624 | - </child> |
3625 | - <child> |
3626 | - <widget class="GtkVButtonBox" id="vbuttonbox1"> |
3627 | - <property name="visible">True</property> |
3628 | - <property name="spacing">5</property> |
3629 | - <property name="layout_style">start</property> |
3630 | - <child> |
3631 | - <widget class="GtkButton" id="button_add_videos"> |
3632 | - <property name="label">gtk-add</property> |
3633 | - <property name="visible">True</property> |
3634 | - <property name="can_focus">True</property> |
3635 | - <property name="has_focus">True</property> |
3636 | - <property name="can_default">True</property> |
3637 | - <property name="has_default">True</property> |
3638 | - <property name="receives_default">False</property> |
3639 | - <property name="use_stock">True</property> |
3640 | - <signal name="clicked" handler="on_button_add_videos_clicked"/> |
3641 | - </widget> |
3642 | - <packing> |
3643 | - <property name="expand">False</property> |
3644 | - <property name="fill">False</property> |
3645 | - <property name="position">0</property> |
3646 | - </packing> |
3647 | - </child> |
3648 | - <child> |
3649 | - <widget class="GtkButton" id="button_remove_videos"> |
3650 | - <property name="label">gtk-remove</property> |
3651 | - <property name="visible">True</property> |
3652 | - <property name="can_focus">True</property> |
3653 | - <property name="receives_default">False</property> |
3654 | - <property name="use_stock">True</property> |
3655 | - <signal name="clicked" handler="on_button_remove_videos_clicked"/> |
3656 | - </widget> |
3657 | - <packing> |
3658 | - <property name="expand">False</property> |
3659 | - <property name="fill">False</property> |
3660 | - <property name="position">1</property> |
3661 | - </packing> |
3662 | - </child> |
3663 | - <child> |
3664 | - <widget class="GtkButton" id="button_edit_videos"> |
3665 | - <property name="label">gtk-edit</property> |
3666 | - <property name="visible">True</property> |
3667 | - <property name="can_focus">True</property> |
3668 | - <property name="receives_default">False</property> |
3669 | - <property name="use_stock">True</property> |
3670 | - <signal name="clicked" handler="on_button_edit_videos_clicked"/> |
3671 | - </widget> |
3672 | - <packing> |
3673 | - <property name="expand">False</property> |
3674 | - <property name="fill">False</property> |
3675 | - <property name="position">2</property> |
3676 | - </packing> |
3677 | - </child> |
3678 | - </widget> |
3679 | - <packing> |
3680 | - <property name="expand">False</property> |
3681 | - <property name="padding">5</property> |
3682 | - <property name="position">1</property> |
3683 | - </packing> |
3684 | - </child> |
3685 | - </widget> |
3686 | - <packing> |
3687 | - <property name="position">1</property> |
3688 | - </packing> |
3689 | - </child> |
3690 | - </widget> |
3691 | - </child> |
3692 | - </widget> |
3693 | - </child> |
3694 | - <child> |
3695 | - <widget class="GtkLabel" id="label_video_library1"> |
3696 | - <property name="visible">True</property> |
3697 | - <property name="label" translatable="yes"><b>Video library</b></property> |
3698 | - <property name="use_markup">True</property> |
3699 | - </widget> |
3700 | - <packing> |
3701 | - <property name="type">label_item</property> |
3702 | - </packing> |
3703 | - </child> |
3704 | - </widget> |
3705 | - <packing> |
3706 | - <property name="position">0</property> |
3707 | - </packing> |
3708 | - </child> |
3709 | - <child> |
3710 | - <widget class="GtkFrame" id="frame1"> |
3711 | - <property name="visible">True</property> |
3712 | - <property name="label_xalign">0</property> |
3713 | - <property name="shadow_type">none</property> |
3714 | - <child> |
3715 | - <widget class="GtkAlignment" id="alignment2"> |
3716 | - <property name="visible">True</property> |
3717 | - <property name="left_padding">12</property> |
3718 | - <child> |
3719 | - <widget class="GtkCheckButton" id="video_metadata_checkbox"> |
3720 | - <property name="label" translatable="yes">Download movie and TV-series metadata</property> |
3721 | - <property name="visible">True</property> |
3722 | - <property name="can_focus">True</property> |
3723 | - <property name="receives_default">False</property> |
3724 | - <property name="use_underline">True</property> |
3725 | - <property name="draw_indicator">True</property> |
3726 | - <signal name="toggled" handler="on_checkbutton_video_metadata_toggled"/> |
3727 | - </widget> |
3728 | - </child> |
3729 | - </widget> |
3730 | - </child> |
3731 | - <child> |
3732 | - <widget class="GtkLabel" id="label2"> |
3733 | - <property name="visible">True</property> |
3734 | - <property name="label" translatable="yes"><b>Video library settings</b></property> |
3735 | - <property name="use_markup">True</property> |
3736 | - </widget> |
3737 | - <packing> |
3738 | - <property name="type">label_item</property> |
3739 | - </packing> |
3740 | - </child> |
3741 | - </widget> |
3742 | - <packing> |
3743 | - <property name="position">1</property> |
3744 | - </packing> |
3745 | - </child> |
3746 | - </widget> |
3747 | - <packing> |
3748 | - <property name="position">0</property> |
3749 | - </packing> |
3750 | - </child> |
3751 | - </widget> |
3752 | - </child> |
3753 | - <child> |
3754 | - <widget class="GtkLabel" id="label_video3"> |
3755 | - <property name="visible">True</property> |
3756 | - <property name="can_focus">True</property> |
3757 | - <property name="has_focus">True</property> |
3758 | - <property name="has_default">True</property> |
3759 | - <property name="label" translatable="yes">Videos</property> |
3760 | - </widget> |
3761 | - <packing> |
3762 | - <property name="tab_fill">False</property> |
3763 | - <property name="type">tab</property> |
3764 | - </packing> |
3765 | - </child> |
3766 | - <child> |
3767 | - <widget class="GtkVBox" id="vbox_music"> |
3768 | - <property name="visible">True</property> |
3769 | - <child> |
3770 | - <widget class="GtkFrame" id="frame_music_folders3"> |
3771 | - <property name="visible">True</property> |
3772 | - <property name="border_width">5</property> |
3773 | - <property name="label_xalign">0</property> |
3774 | - <property name="shadow_type">none</property> |
3775 | - <child> |
3776 | - <widget class="GtkAlignment" id="alignment9"> |
3777 | - <property name="visible">True</property> |
3778 | - <property name="left_padding">12</property> |
3779 | - <child> |
3780 | - <widget class="GtkVBox" id="vbox12"> |
3781 | - <property name="visible">True</property> |
3782 | - <child> |
3783 | - <widget class="GtkLabel" id="label8"> |
3784 | - <property name="visible">True</property> |
3785 | - <property name="xalign">0</property> |
3786 | - <property name="label" translatable="yes">Entertainer music library scans data from the listed folders.</property> |
3787 | - </widget> |
3788 | - <packing> |
3789 | - <property name="expand">False</property> |
3790 | - <property name="padding">5</property> |
3791 | - <property name="position">0</property> |
3792 | - </packing> |
3793 | - </child> |
3794 | - <child> |
3795 | - <widget class="GtkHBox" id="hbox12"> |
3796 | - <property name="visible">True</property> |
3797 | - <child> |
3798 | - <widget class="GtkScrolledWindow" id="scrolledwindow5"> |
3799 | - <property name="visible">True</property> |
3800 | - <property name="can_focus">True</property> |
3801 | - <property name="hscrollbar_policy">automatic</property> |
3802 | - <property name="vscrollbar_policy">automatic</property> |
3803 | - <property name="shadow_type">in</property> |
3804 | - <child> |
3805 | - <widget class="GtkTreeView" id="treeview_music"> |
3806 | - <property name="visible">True</property> |
3807 | - <property name="can_focus">True</property> |
3808 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
3809 | - <property name="enable_search">False</property> |
3810 | - </widget> |
3811 | - </child> |
3812 | - </widget> |
3813 | - <packing> |
3814 | - <property name="position">0</property> |
3815 | - </packing> |
3816 | - </child> |
3817 | - <child> |
3818 | - <widget class="GtkVButtonBox" id="vbuttonbox6"> |
3819 | - <property name="visible">True</property> |
3820 | - <property name="spacing">5</property> |
3821 | - <property name="layout_style">start</property> |
3822 | - <child> |
3823 | - <widget class="GtkButton" id="button_add_music"> |
3824 | - <property name="label">gtk-add</property> |
3825 | - <property name="visible">True</property> |
3826 | - <property name="can_focus">True</property> |
3827 | - <property name="receives_default">False</property> |
3828 | - <property name="use_stock">True</property> |
3829 | - <signal name="clicked" handler="on_button_add_music_clicked"/> |
3830 | - </widget> |
3831 | - <packing> |
3832 | - <property name="expand">False</property> |
3833 | - <property name="fill">False</property> |
3834 | - <property name="position">0</property> |
3835 | - </packing> |
3836 | - </child> |
3837 | - <child> |
3838 | - <widget class="GtkButton" id="button_remove_music"> |
3839 | - <property name="label">gtk-remove</property> |
3840 | - <property name="visible">True</property> |
3841 | - <property name="can_focus">True</property> |
3842 | - <property name="receives_default">False</property> |
3843 | - <property name="use_stock">True</property> |
3844 | - <signal name="clicked" handler="on_button_remove_music_clicked"/> |
3845 | - </widget> |
3846 | - <packing> |
3847 | - <property name="expand">False</property> |
3848 | - <property name="fill">False</property> |
3849 | - <property name="position">1</property> |
3850 | - </packing> |
3851 | - </child> |
3852 | - <child> |
3853 | - <widget class="GtkButton" id="button_edit_music"> |
3854 | - <property name="label">gtk-edit</property> |
3855 | - <property name="visible">True</property> |
3856 | - <property name="can_focus">True</property> |
3857 | - <property name="receives_default">False</property> |
3858 | - <property name="use_stock">True</property> |
3859 | - <signal name="clicked" handler="on_button_edit_music_clicked"/> |
3860 | - </widget> |
3861 | - <packing> |
3862 | - <property name="expand">False</property> |
3863 | - <property name="fill">False</property> |
3864 | - <property name="position">2</property> |
3865 | - </packing> |
3866 | - </child> |
3867 | - </widget> |
3868 | - <packing> |
3869 | - <property name="expand">False</property> |
3870 | - <property name="padding">5</property> |
3871 | - <property name="position">1</property> |
3872 | - </packing> |
3873 | - </child> |
3874 | - </widget> |
3875 | - <packing> |
3876 | - <property name="position">1</property> |
3877 | - </packing> |
3878 | - </child> |
3879 | - </widget> |
3880 | - </child> |
3881 | - </widget> |
3882 | - </child> |
3883 | - <child> |
3884 | - <widget class="GtkLabel" id="label_music_folders3"> |
3885 | - <property name="visible">True</property> |
3886 | - <property name="label" translatable="yes"><b>Music library</b></property> |
3887 | - <property name="use_markup">True</property> |
3888 | - </widget> |
3889 | - <packing> |
3890 | - <property name="type">label_item</property> |
3891 | - </packing> |
3892 | - </child> |
3893 | - </widget> |
3894 | - <packing> |
3895 | - <property name="position">0</property> |
3896 | - </packing> |
3897 | - </child> |
3898 | - <child> |
3899 | - <widget class="GtkFrame" id="frame_music_lib_settings3"> |
3900 | - <property name="visible">True</property> |
3901 | - <property name="border_width">5</property> |
3902 | - <property name="label_xalign">0</property> |
3903 | - <property name="shadow_type">none</property> |
3904 | - <child> |
3905 | - <widget class="GtkAlignment" id="alignment10"> |
3906 | - <property name="visible">True</property> |
3907 | - <property name="left_padding">12</property> |
3908 | - <child> |
3909 | - <widget class="GtkVBox" id="vbox13"> |
3910 | - <property name="visible">True</property> |
3911 | - <property name="homogeneous">True</property> |
3912 | - <child> |
3913 | - <widget class="GtkCheckButton" id="art_checkbox"> |
3914 | - <property name="label" translatable="yes">Download album cover art</property> |
3915 | - <property name="visible">True</property> |
3916 | - <property name="can_focus">True</property> |
3917 | - <property name="receives_default">False</property> |
3918 | - <property name="use_underline">True</property> |
3919 | - <property name="draw_indicator">True</property> |
3920 | - <signal name="toggled" handler="on_art_checkbox_toggled"/> |
3921 | - </widget> |
3922 | - <packing> |
3923 | - <property name="padding">5</property> |
3924 | - <property name="position">0</property> |
3925 | - </packing> |
3926 | - </child> |
3927 | - <child> |
3928 | - <widget class="GtkCheckButton" id="lyrics_checkbox"> |
3929 | - <property name="label" translatable="yes">Download song lyrics</property> |
3930 | - <property name="visible">True</property> |
3931 | - <property name="can_focus">True</property> |
3932 | - <property name="receives_default">False</property> |
3933 | - <property name="use_underline">True</property> |
3934 | - <property name="draw_indicator">True</property> |
3935 | - <signal name="toggled" handler="on_lyrics_checkbox_toggled"/> |
3936 | - </widget> |
3937 | - <packing> |
3938 | - <property name="padding">5</property> |
3939 | - <property name="position">1</property> |
3940 | - </packing> |
3941 | - </child> |
3942 | - </widget> |
3943 | - </child> |
3944 | - </widget> |
3945 | - </child> |
3946 | - <child> |
3947 | - <widget class="GtkLabel" id="label9"> |
3948 | - <property name="visible">True</property> |
3949 | - <property name="label" translatable="yes"><b>Music library settings</b></property> |
3950 | - <property name="use_markup">True</property> |
3951 | - </widget> |
3952 | - <packing> |
3953 | - <property name="type">label_item</property> |
3954 | - </packing> |
3955 | - </child> |
3956 | - </widget> |
3957 | - <packing> |
3958 | - <property name="expand">False</property> |
3959 | - <property name="position">1</property> |
3960 | - </packing> |
3961 | - </child> |
3962 | - </widget> |
3963 | - <packing> |
3964 | - <property name="position">1</property> |
3965 | - </packing> |
3966 | - </child> |
3967 | - <child> |
3968 | - <widget class="GtkLabel" id="label10"> |
3969 | - <property name="visible">True</property> |
3970 | - <property name="label" translatable="yes">Music</property> |
3971 | - </widget> |
3972 | - <packing> |
3973 | - <property name="position">1</property> |
3974 | - <property name="tab_fill">False</property> |
3975 | - <property name="type">tab</property> |
3976 | - </packing> |
3977 | - </child> |
3978 | - <child> |
3979 | - <widget class="GtkVBox" id="vbox14"> |
3980 | - <property name="visible">True</property> |
3981 | - <child> |
3982 | - <widget class="GtkFrame" id="frame_image_folders3"> |
3983 | - <property name="visible">True</property> |
3984 | - <property name="border_width">5</property> |
3985 | - <property name="label_xalign">0</property> |
3986 | - <property name="shadow_type">none</property> |
3987 | - <child> |
3988 | - <widget class="GtkAlignment" id="alignment11"> |
3989 | - <property name="visible">True</property> |
3990 | - <property name="left_padding">12</property> |
3991 | - <child> |
3992 | - <widget class="GtkVBox" id="vbox15"> |
3993 | - <property name="visible">True</property> |
3994 | - <child> |
3995 | - <widget class="GtkLabel" id="label_music_tip3"> |
3996 | - <property name="visible">True</property> |
3997 | - <property name="xalign">0</property> |
3998 | - <property name="label" translatable="yes">Entertainer image library scans data from the listed folders.</property> |
3999 | - </widget> |
4000 | - <packing> |
4001 | - <property name="expand">False</property> |
4002 | - <property name="padding">5</property> |
4003 | - <property name="position">0</property> |
4004 | - </packing> |
4005 | - </child> |
4006 | - <child> |
4007 | - <widget class="GtkHBox" id="hbox14"> |
4008 | - <property name="visible">True</property> |
4009 | - <child> |
4010 | - <widget class="GtkScrolledWindow" id="scrolledwindow6"> |
4011 | - <property name="visible">True</property> |
4012 | - <property name="can_focus">True</property> |
4013 | - <property name="hscrollbar_policy">automatic</property> |
4014 | - <property name="vscrollbar_policy">automatic</property> |
4015 | - <property name="shadow_type">in</property> |
4016 | - <child> |
4017 | - <widget class="GtkTreeView" id="treeview_images"> |
4018 | - <property name="visible">True</property> |
4019 | - <property name="can_focus">True</property> |
4020 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4021 | - <property name="enable_search">False</property> |
4022 | - </widget> |
4023 | - </child> |
4024 | - </widget> |
4025 | - <packing> |
4026 | - <property name="position">0</property> |
4027 | - </packing> |
4028 | - </child> |
4029 | - <child> |
4030 | - <widget class="GtkVButtonBox" id="vbuttonbox7"> |
4031 | - <property name="visible">True</property> |
4032 | - <property name="spacing">5</property> |
4033 | - <property name="layout_style">start</property> |
4034 | - <child> |
4035 | - <widget class="GtkButton" id="button_add_images"> |
4036 | - <property name="label">gtk-add</property> |
4037 | - <property name="visible">True</property> |
4038 | - <property name="can_focus">True</property> |
4039 | - <property name="receives_default">False</property> |
4040 | - <property name="use_stock">True</property> |
4041 | - <signal name="clicked" handler="on_button_add_images_clicked"/> |
4042 | - </widget> |
4043 | - <packing> |
4044 | - <property name="expand">False</property> |
4045 | - <property name="fill">False</property> |
4046 | - <property name="position">0</property> |
4047 | - </packing> |
4048 | - </child> |
4049 | - <child> |
4050 | - <widget class="GtkButton" id="button_remove_images"> |
4051 | - <property name="label">gtk-remove</property> |
4052 | - <property name="visible">True</property> |
4053 | - <property name="can_focus">True</property> |
4054 | - <property name="receives_default">False</property> |
4055 | - <property name="use_stock">True</property> |
4056 | - <signal name="clicked" handler="on_button_remove_images_clicked"/> |
4057 | - </widget> |
4058 | - <packing> |
4059 | - <property name="expand">False</property> |
4060 | - <property name="fill">False</property> |
4061 | - <property name="position">1</property> |
4062 | - </packing> |
4063 | - </child> |
4064 | - <child> |
4065 | - <widget class="GtkButton" id="button_edit_images"> |
4066 | - <property name="label">gtk-edit</property> |
4067 | - <property name="visible">True</property> |
4068 | - <property name="can_focus">True</property> |
4069 | - <property name="receives_default">False</property> |
4070 | - <property name="use_stock">True</property> |
4071 | - <signal name="clicked" handler="on_button_edit_images_clicked"/> |
4072 | - </widget> |
4073 | - <packing> |
4074 | - <property name="expand">False</property> |
4075 | - <property name="fill">False</property> |
4076 | - <property name="position">2</property> |
4077 | - </packing> |
4078 | - </child> |
4079 | - </widget> |
4080 | - <packing> |
4081 | - <property name="expand">False</property> |
4082 | - <property name="padding">5</property> |
4083 | - <property name="position">1</property> |
4084 | - </packing> |
4085 | - </child> |
4086 | - </widget> |
4087 | - <packing> |
4088 | - <property name="position">1</property> |
4089 | - </packing> |
4090 | - </child> |
4091 | - </widget> |
4092 | - </child> |
4093 | - </widget> |
4094 | - </child> |
4095 | - <child> |
4096 | - <widget class="GtkLabel" id="label11"> |
4097 | - <property name="visible">True</property> |
4098 | - <property name="label" translatable="yes"><b>Image library</b></property> |
4099 | - <property name="use_markup">True</property> |
4100 | - </widget> |
4101 | - <packing> |
4102 | - <property name="type">label_item</property> |
4103 | - </packing> |
4104 | - </child> |
4105 | - </widget> |
4106 | - <packing> |
4107 | - <property name="position">0</property> |
4108 | - </packing> |
4109 | - </child> |
4110 | - <child> |
4111 | - <widget class="GtkFrame" id="frame_image_lib_settings3"> |
4112 | - <property name="visible">True</property> |
4113 | - <property name="border_width">5</property> |
4114 | - <property name="label_xalign">0</property> |
4115 | - <property name="shadow_type">none</property> |
4116 | - <child> |
4117 | - <widget class="GtkAlignment" id="alignment12"> |
4118 | - <property name="visible">True</property> |
4119 | - <property name="left_padding">12</property> |
4120 | - <child> |
4121 | - <widget class="GtkVBox" id="vbox16"> |
4122 | - <property name="visible">True</property> |
4123 | - <child> |
4124 | - <placeholder/> |
4125 | - </child> |
4126 | - <child> |
4127 | - <widget class="GtkCheckButton" id="hidden_files_folders_checkbox"> |
4128 | - <property name="label" translatable="yes">Display hidden files and folders in image library</property> |
4129 | - <property name="visible">True</property> |
4130 | - <property name="can_focus">True</property> |
4131 | - <property name="receives_default">False</property> |
4132 | - <property name="use_underline">True</property> |
4133 | - <property name="draw_indicator">True</property> |
4134 | - <signal name="toggled" handler="on_hidden_files_folders_checkbox_toggled"/> |
4135 | - </widget> |
4136 | - <packing> |
4137 | - <property name="padding">5</property> |
4138 | - <property name="position">1</property> |
4139 | - </packing> |
4140 | - </child> |
4141 | - </widget> |
4142 | - </child> |
4143 | - </widget> |
4144 | - </child> |
4145 | - <child> |
4146 | - <widget class="GtkLabel" id="label_image_lib_settings3"> |
4147 | - <property name="visible">True</property> |
4148 | - <property name="label" translatable="yes"><b>Image library settings</b></property> |
4149 | - <property name="use_markup">True</property> |
4150 | - </widget> |
4151 | - <packing> |
4152 | - <property name="type">label_item</property> |
4153 | - </packing> |
4154 | - </child> |
4155 | - </widget> |
4156 | - <packing> |
4157 | - <property name="expand">False</property> |
4158 | - <property name="position">1</property> |
4159 | - </packing> |
4160 | - </child> |
4161 | - </widget> |
4162 | - <packing> |
4163 | - <property name="position">2</property> |
4164 | - </packing> |
4165 | - </child> |
4166 | - <child> |
4167 | - <widget class="GtkLabel" id="label_images3"> |
4168 | - <property name="visible">True</property> |
4169 | - <property name="label" translatable="yes">Images</property> |
4170 | - </widget> |
4171 | - <packing> |
4172 | - <property name="position">2</property> |
4173 | - <property name="tab_fill">False</property> |
4174 | - <property name="type">tab</property> |
4175 | - </packing> |
4176 | - </child> |
4177 | - <child> |
4178 | - <widget class="GtkVBox" id="vbox_feeds"> |
4179 | - <property name="visible">True</property> |
4180 | - <child> |
4181 | - <widget class="GtkFrame" id="frame_rss_feeds3"> |
4182 | - <property name="visible">True</property> |
4183 | - <property name="border_width">5</property> |
4184 | - <property name="label_xalign">0</property> |
4185 | - <property name="shadow_type">none</property> |
4186 | - <child> |
4187 | - <widget class="GtkAlignment" id="alignment13"> |
4188 | - <property name="visible">True</property> |
4189 | - <property name="left_padding">12</property> |
4190 | - <child> |
4191 | - <widget class="GtkVBox" id="vbox17"> |
4192 | - <property name="visible">True</property> |
4193 | - <child> |
4194 | - <widget class="GtkLabel" id="label_rss_tip3"> |
4195 | - <property name="visible">True</property> |
4196 | - <property name="xalign">0</property> |
4197 | - <property name="label" translatable="yes">Below is a list of RSS feeds that are displayd in Entertainer.</property> |
4198 | - </widget> |
4199 | - <packing> |
4200 | - <property name="expand">False</property> |
4201 | - <property name="padding">5</property> |
4202 | - <property name="position">0</property> |
4203 | - </packing> |
4204 | - </child> |
4205 | - <child> |
4206 | - <widget class="GtkHBox" id="hbox16"> |
4207 | - <property name="visible">True</property> |
4208 | - <child> |
4209 | - <widget class="GtkScrolledWindow" id="scrolledwindow_rss3"> |
4210 | - <property name="visible">True</property> |
4211 | - <property name="can_focus">True</property> |
4212 | - <property name="hscrollbar_policy">automatic</property> |
4213 | - <property name="vscrollbar_policy">automatic</property> |
4214 | - <property name="shadow_type">in</property> |
4215 | - <child> |
4216 | - <widget class="GtkTreeView" id="treeview_feeds"> |
4217 | - <property name="visible">True</property> |
4218 | - <property name="can_focus">True</property> |
4219 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4220 | - <property name="enable_search">False</property> |
4221 | - </widget> |
4222 | - </child> |
4223 | - </widget> |
4224 | - <packing> |
4225 | - <property name="position">0</property> |
4226 | - </packing> |
4227 | - </child> |
4228 | - <child> |
4229 | - <widget class="GtkVButtonBox" id="vbuttonbox8"> |
4230 | - <property name="visible">True</property> |
4231 | - <property name="spacing">5</property> |
4232 | - <property name="layout_style">start</property> |
4233 | - <child> |
4234 | - <widget class="GtkButton" id="button_add_feed"> |
4235 | - <property name="label">gtk-add</property> |
4236 | - <property name="visible">True</property> |
4237 | - <property name="can_focus">True</property> |
4238 | - <property name="receives_default">False</property> |
4239 | - <property name="tooltip" translatable="yes">Add RSS Feed</property> |
4240 | - <property name="use_stock">True</property> |
4241 | - <signal name="clicked" handler="on_button_add_feed_clicked"/> |
4242 | - </widget> |
4243 | - <packing> |
4244 | - <property name="expand">False</property> |
4245 | - <property name="fill">False</property> |
4246 | - <property name="position">0</property> |
4247 | - </packing> |
4248 | - </child> |
4249 | - <child> |
4250 | - <widget class="GtkButton" id="button_remove_feed"> |
4251 | - <property name="label">gtk-remove</property> |
4252 | - <property name="visible">True</property> |
4253 | - <property name="can_focus">True</property> |
4254 | - <property name="receives_default">False</property> |
4255 | - <property name="tooltip" translatable="yes">Remove RSS Feed</property> |
4256 | - <property name="use_stock">True</property> |
4257 | - <signal name="clicked" handler="on_button_remove_feed_clicked"/> |
4258 | - </widget> |
4259 | - <packing> |
4260 | - <property name="expand">False</property> |
4261 | - <property name="fill">False</property> |
4262 | - <property name="position">1</property> |
4263 | - </packing> |
4264 | - </child> |
4265 | - <child> |
4266 | - <widget class="GtkButton" id="button_edit_feed"> |
4267 | - <property name="label">gtk-edit</property> |
4268 | - <property name="visible">True</property> |
4269 | - <property name="can_focus">True</property> |
4270 | - <property name="receives_default">False</property> |
4271 | - <property name="tooltip" translatable="yes">Edit current item</property> |
4272 | - <property name="use_stock">True</property> |
4273 | - <signal name="clicked" handler="on_button_edit_feed_clicked"/> |
4274 | - </widget> |
4275 | - <packing> |
4276 | - <property name="expand">False</property> |
4277 | - <property name="fill">False</property> |
4278 | - <property name="position">2</property> |
4279 | - </packing> |
4280 | - </child> |
4281 | - <child> |
4282 | - <widget class="GtkButton" id="button_open_list"> |
4283 | - <property name="label">gtk-open</property> |
4284 | - <property name="visible">True</property> |
4285 | - <property name="can_focus">True</property> |
4286 | - <property name="receives_default">False</property> |
4287 | - <property name="tooltip" translatable="yes">Add feeds from a datasource</property> |
4288 | - <property name="use_stock">True</property> |
4289 | - <signal name="clicked" handler="on_button_open_list_clicked"/> |
4290 | - </widget> |
4291 | - <packing> |
4292 | - <property name="expand">False</property> |
4293 | - <property name="fill">False</property> |
4294 | - <property name="position">3</property> |
4295 | - </packing> |
4296 | - </child> |
4297 | - </widget> |
4298 | - <packing> |
4299 | - <property name="expand">False</property> |
4300 | - <property name="padding">5</property> |
4301 | - <property name="position">1</property> |
4302 | - </packing> |
4303 | - </child> |
4304 | - </widget> |
4305 | - <packing> |
4306 | - <property name="position">1</property> |
4307 | - </packing> |
4308 | - </child> |
4309 | - </widget> |
4310 | - </child> |
4311 | - </widget> |
4312 | - </child> |
4313 | - <child> |
4314 | - <widget class="GtkLabel" id="label_rss_sources3"> |
4315 | - <property name="visible">True</property> |
4316 | - <property name="label" translatable="yes"><b>RSS feeds</b></property> |
4317 | - <property name="use_markup">True</property> |
4318 | - </widget> |
4319 | - <packing> |
4320 | - <property name="type">label_item</property> |
4321 | - </packing> |
4322 | - </child> |
4323 | - </widget> |
4324 | - <packing> |
4325 | - <property name="position">0</property> |
4326 | - </packing> |
4327 | - </child> |
4328 | - <child> |
4329 | - <widget class="GtkFrame" id="frame_feed_settings3"> |
4330 | - <property name="visible">True</property> |
4331 | - <property name="border_width">5</property> |
4332 | - <property name="label_xalign">0</property> |
4333 | - <property name="shadow_type">none</property> |
4334 | - <child> |
4335 | - <widget class="GtkAlignment" id="alignment14"> |
4336 | - <property name="visible">True</property> |
4337 | - <property name="left_padding">12</property> |
4338 | - <child> |
4339 | - <widget class="GtkVBox" id="vbox18"> |
4340 | - <property name="visible">True</property> |
4341 | - <child> |
4342 | - <widget class="GtkHBox" id="hbox17"> |
4343 | - <property name="visible">True</property> |
4344 | - <child> |
4345 | - <widget class="GtkLabel" id="label12"> |
4346 | - <property name="visible">True</property> |
4347 | - <property name="label" translatable="yes">Update all feeds every</property> |
4348 | - </widget> |
4349 | - <packing> |
4350 | - <property name="expand">False</property> |
4351 | - <property name="position">0</property> |
4352 | - </packing> |
4353 | - </child> |
4354 | - <child> |
4355 | - <widget class="GtkSpinButton" id="fetch_interval_spinbutton"> |
4356 | - <property name="visible">True</property> |
4357 | - <property name="can_focus">True</property> |
4358 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4359 | - <property name="adjustment">60 15 900 1 10 0</property> |
4360 | - <property name="climb_rate">1</property> |
4361 | - <property name="numeric">True</property> |
4362 | - <signal name="value_changed" handler="on_fetch_interval_spinbutton_value_changed"/> |
4363 | - </widget> |
4364 | - <packing> |
4365 | - <property name="expand">False</property> |
4366 | - <property name="padding">5</property> |
4367 | - <property name="position">1</property> |
4368 | - </packing> |
4369 | - </child> |
4370 | - <child> |
4371 | - <widget class="GtkLabel" id="label13"> |
4372 | - <property name="visible">True</property> |
4373 | - <property name="label" translatable="yes">minutes.</property> |
4374 | - </widget> |
4375 | - <packing> |
4376 | - <property name="expand">False</property> |
4377 | - <property name="position">2</property> |
4378 | - </packing> |
4379 | - </child> |
4380 | - </widget> |
4381 | - <packing> |
4382 | - <property name="expand">False</property> |
4383 | - <property name="padding">10</property> |
4384 | - <property name="position">0</property> |
4385 | - </packing> |
4386 | - </child> |
4387 | - </widget> |
4388 | - </child> |
4389 | - </widget> |
4390 | - </child> |
4391 | - <child> |
4392 | - <widget class="GtkLabel" id="label_rss_settings3"> |
4393 | - <property name="visible">True</property> |
4394 | - <property name="label" translatable="yes"><b>Feed settings</b></property> |
4395 | - <property name="use_markup">True</property> |
4396 | - </widget> |
4397 | - <packing> |
4398 | - <property name="type">label_item</property> |
4399 | - </packing> |
4400 | - </child> |
4401 | - </widget> |
4402 | - <packing> |
4403 | - <property name="expand">False</property> |
4404 | - <property name="position">1</property> |
4405 | - </packing> |
4406 | - </child> |
4407 | - </widget> |
4408 | - <packing> |
4409 | - <property name="position">3</property> |
4410 | - </packing> |
4411 | - </child> |
4412 | - <child> |
4413 | - <widget class="GtkLabel" id="label_rss3"> |
4414 | - <property name="visible">True</property> |
4415 | - <property name="label" translatable="yes">RSS feeds</property> |
4416 | - </widget> |
4417 | - <packing> |
4418 | - <property name="position">3</property> |
4419 | - <property name="tab_fill">False</property> |
4420 | - <property name="type">tab</property> |
4421 | - </packing> |
4422 | - </child> |
4423 | - <child> |
4424 | - <widget class="GtkVBox" id="vbox5"> |
4425 | - <property name="visible">True</property> |
4426 | - <child> |
4427 | - <widget class="GtkFrame" id="frame_video_lib3"> |
4428 | - <property name="visible">True</property> |
4429 | - <property name="border_width">5</property> |
4430 | - <property name="label_xalign">0</property> |
4431 | - <property name="shadow_type">none</property> |
4432 | - <child> |
4433 | - <widget class="GtkAlignment" id="alignment5"> |
4434 | - <property name="visible">True</property> |
4435 | - <property name="left_padding">12</property> |
4436 | - <child> |
4437 | - <widget class="GtkVBox" id="weather_location_list_area"> |
4438 | - <property name="visible">True</property> |
4439 | - <child> |
4440 | - <widget class="GtkLabel" id="label_weather_tip"> |
4441 | - <property name="visible">True</property> |
4442 | - <property name="xalign">0</property> |
4443 | - <property name="label" translatable="yes">Get weather conditions from the locations listed below.</property> |
4444 | - <property name="single_line_mode">True</property> |
4445 | - </widget> |
4446 | - <packing> |
4447 | - <property name="expand">False</property> |
4448 | - <property name="padding">5</property> |
4449 | - <property name="position">0</property> |
4450 | - </packing> |
4451 | - </child> |
4452 | - <child> |
4453 | - <widget class="GtkHBox" id="hbox4"> |
4454 | - <property name="visible">True</property> |
4455 | - <child> |
4456 | - <widget class="GtkScrolledWindow" id="scrolledwindow3"> |
4457 | - <property name="visible">True</property> |
4458 | - <property name="can_focus">True</property> |
4459 | - <property name="hscrollbar_policy">automatic</property> |
4460 | - <property name="vscrollbar_policy">automatic</property> |
4461 | - <property name="shadow_type">in</property> |
4462 | - <child> |
4463 | - <widget class="GtkTreeView" id="treeview_locations"> |
4464 | - <property name="visible">True</property> |
4465 | - <property name="can_focus">True</property> |
4466 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4467 | - <property name="enable_search">False</property> |
4468 | - </widget> |
4469 | - </child> |
4470 | - </widget> |
4471 | - <packing> |
4472 | - <property name="position">0</property> |
4473 | - </packing> |
4474 | - </child> |
4475 | - <child> |
4476 | - <widget class="GtkVButtonBox" id="vbuttonbox3"> |
4477 | - <property name="visible">True</property> |
4478 | - <property name="spacing">5</property> |
4479 | - <property name="layout_style">start</property> |
4480 | - <child> |
4481 | - <widget class="GtkButton" id="button_add_weather"> |
4482 | - <property name="label">gtk-new</property> |
4483 | - <property name="visible">True</property> |
4484 | - <property name="can_focus">True</property> |
4485 | - <property name="has_focus">True</property> |
4486 | - <property name="can_default">True</property> |
4487 | - <property name="has_default">True</property> |
4488 | - <property name="receives_default">False</property> |
4489 | - <property name="use_stock">True</property> |
4490 | - <signal name="clicked" handler="on_button_add_weather_clicked"/> |
4491 | - </widget> |
4492 | - <packing> |
4493 | - <property name="expand">False</property> |
4494 | - <property name="fill">False</property> |
4495 | - <property name="position">0</property> |
4496 | - </packing> |
4497 | - </child> |
4498 | - <child> |
4499 | - <widget class="GtkButton" id="button_remove_weather"> |
4500 | - <property name="label">gtk-clear</property> |
4501 | - <property name="visible">True</property> |
4502 | - <property name="can_focus">True</property> |
4503 | - <property name="receives_default">False</property> |
4504 | - <property name="use_stock">True</property> |
4505 | - <signal name="clicked" handler="on_button_remove_weather_clicked"/> |
4506 | - </widget> |
4507 | - <packing> |
4508 | - <property name="expand">False</property> |
4509 | - <property name="fill">False</property> |
4510 | - <property name="position">1</property> |
4511 | - </packing> |
4512 | - </child> |
4513 | - </widget> |
4514 | - <packing> |
4515 | - <property name="expand">False</property> |
4516 | - <property name="padding">5</property> |
4517 | - <property name="position">1</property> |
4518 | - </packing> |
4519 | - </child> |
4520 | - </widget> |
4521 | - <packing> |
4522 | - <property name="position">1</property> |
4523 | - </packing> |
4524 | - </child> |
4525 | - </widget> |
4526 | - </child> |
4527 | - </widget> |
4528 | - </child> |
4529 | - <child> |
4530 | - <widget class="GtkLabel" id="label_weather_options"> |
4531 | - <property name="visible">True</property> |
4532 | - <property name="label" translatable="yes"><b>Weather locations</b></property> |
4533 | - <property name="use_markup">True</property> |
4534 | - </widget> |
4535 | - <packing> |
4536 | - <property name="type">label_item</property> |
4537 | - </packing> |
4538 | - </child> |
4539 | - </widget> |
4540 | - <packing> |
4541 | - <property name="position">0</property> |
4542 | - </packing> |
4543 | - </child> |
4544 | - <child> |
4545 | - <widget class="GtkFrame" id="frame4"> |
4546 | - <property name="visible">True</property> |
4547 | - <property name="label_xalign">0</property> |
4548 | - <property name="shadow_type">none</property> |
4549 | - <child> |
4550 | - <widget class="GtkAlignment" id="alignment7"> |
4551 | - <property name="visible">True</property> |
4552 | - <property name="left_padding">12</property> |
4553 | - <child> |
4554 | - <widget class="GtkVBox" id="vbox7"> |
4555 | - <property name="visible">True</property> |
4556 | - <child> |
4557 | - <widget class="GtkCheckButton" id="weather_display_checkbox"> |
4558 | - <property name="label" translatable="yes">Display weather option in main menu</property> |
4559 | - <property name="visible">True</property> |
4560 | - <property name="can_focus">True</property> |
4561 | - <property name="receives_default">False</property> |
4562 | - <property name="use_underline">True</property> |
4563 | - <property name="active">True</property> |
4564 | - <property name="draw_indicator">True</property> |
4565 | - <signal name="toggled" handler="on_weather_display_checkbox_toggled"/> |
4566 | - </widget> |
4567 | - <packing> |
4568 | - <property name="fill">False</property> |
4569 | - <property name="position">0</property> |
4570 | - </packing> |
4571 | - </child> |
4572 | - </widget> |
4573 | - </child> |
4574 | - </widget> |
4575 | - </child> |
4576 | - <child> |
4577 | - <widget class="GtkLabel" id="label7"> |
4578 | - <property name="visible">True</property> |
4579 | - <property name="label" translatable="yes"><b>Weather settings</b></property> |
4580 | - <property name="use_markup">True</property> |
4581 | - </widget> |
4582 | - <packing> |
4583 | - <property name="type">label_item</property> |
4584 | - </packing> |
4585 | - </child> |
4586 | - </widget> |
4587 | - <packing> |
4588 | - <property name="position">1</property> |
4589 | - </packing> |
4590 | - </child> |
4591 | - </widget> |
4592 | - <packing> |
4593 | - <property name="position">4</property> |
4594 | - </packing> |
4595 | - </child> |
4596 | - <child> |
4597 | - <widget class="GtkLabel" id="label_weather"> |
4598 | - <property name="visible">True</property> |
4599 | - <property name="yalign">0.40999999642372131</property> |
4600 | - <property name="label" translatable="yes">Weather</property> |
4601 | - </widget> |
4602 | - <packing> |
4603 | - <property name="position">4</property> |
4604 | - <property name="tab_fill">False</property> |
4605 | - <property name="type">tab</property> |
4606 | - </packing> |
4607 | - </child> |
4608 | - <child> |
4609 | - <widget class="GtkFrame" id="frame2"> |
4610 | - <property name="visible">True</property> |
4611 | - <property name="border_width">5</property> |
4612 | - <property name="label_xalign">0</property> |
4613 | - <property name="shadow_type">none</property> |
4614 | - <child> |
4615 | - <widget class="GtkAlignment" id="alignment3"> |
4616 | - <property name="visible">True</property> |
4617 | - <property name="left_padding">12</property> |
4618 | - <child> |
4619 | - <widget class="GtkVBox" id="vbox3"> |
4620 | - <property name="visible">True</property> |
4621 | - <child> |
4622 | - <widget class="GtkLabel" id="label5"> |
4623 | - <property name="visible">True</property> |
4624 | - <property name="xpad">7</property> |
4625 | - <property name="ypad">7</property> |
4626 | - <property name="label" translatable="yes">Rebuilding cache means that all data is removed and then |
4627 | -folders are indexed again. This fixes broken media cache.</property> |
4628 | - </widget> |
4629 | - <packing> |
4630 | - <property name="position">0</property> |
4631 | - </packing> |
4632 | - </child> |
4633 | - <child> |
4634 | - <widget class="GtkButton" id="button_video_rebuild"> |
4635 | - <property name="label" translatable="yes">Rebuild video cache</property> |
4636 | - <property name="visible">True</property> |
4637 | - <property name="can_focus">True</property> |
4638 | - <property name="receives_default">False</property> |
4639 | - <property name="border_width">5</property> |
4640 | - <property name="use_underline">True</property> |
4641 | - <signal name="clicked" handler="on_button_video_rebuild_clicked"/> |
4642 | - </widget> |
4643 | - <packing> |
4644 | - <property name="position">1</property> |
4645 | - </packing> |
4646 | - </child> |
4647 | - <child> |
4648 | - <widget class="GtkButton" id="button_music_rebuild"> |
4649 | - <property name="label" translatable="yes">Rebuild music cache</property> |
4650 | - <property name="visible">True</property> |
4651 | - <property name="can_focus">True</property> |
4652 | - <property name="receives_default">False</property> |
4653 | - <property name="border_width">5</property> |
4654 | - <property name="use_underline">True</property> |
4655 | - <signal name="clicked" handler="on_button_music_rebuild_clicked"/> |
4656 | - </widget> |
4657 | - <packing> |
4658 | - <property name="position">2</property> |
4659 | - </packing> |
4660 | - </child> |
4661 | - <child> |
4662 | - <widget class="GtkButton" id="button_image_rebuild"> |
4663 | - <property name="label" translatable="yes">Rebuild image cache</property> |
4664 | - <property name="visible">True</property> |
4665 | - <property name="can_focus">True</property> |
4666 | - <property name="receives_default">False</property> |
4667 | - <property name="border_width">5</property> |
4668 | - <property name="use_underline">True</property> |
4669 | - <signal name="clicked" handler="on_button_image_rebuild_clicked"/> |
4670 | - </widget> |
4671 | - <packing> |
4672 | - <property name="position">3</property> |
4673 | - </packing> |
4674 | - </child> |
4675 | - <child> |
4676 | - <widget class="GtkButton" id="button_feed_rebuild"> |
4677 | - <property name="label" translatable="yes">Rebuild RSS-feed cache</property> |
4678 | - <property name="visible">True</property> |
4679 | - <property name="can_focus">True</property> |
4680 | - <property name="receives_default">False</property> |
4681 | - <property name="border_width">5</property> |
4682 | - <property name="use_underline">True</property> |
4683 | - <property name="focus_on_click">False</property> |
4684 | - <signal name="clicked" handler="on_button_feed_rebuild_clicked"/> |
4685 | - </widget> |
4686 | - <packing> |
4687 | - <property name="position">4</property> |
4688 | - </packing> |
4689 | - </child> |
4690 | - </widget> |
4691 | - </child> |
4692 | - </widget> |
4693 | - </child> |
4694 | - <child> |
4695 | - <widget class="GtkLabel" id="label4"> |
4696 | - <property name="visible">True</property> |
4697 | - <property name="label" translatable="yes"><b>Media cache management</b></property> |
4698 | - <property name="use_markup">True</property> |
4699 | - </widget> |
4700 | - <packing> |
4701 | - <property name="type">label_item</property> |
4702 | - </packing> |
4703 | - </child> |
4704 | - </widget> |
4705 | - <packing> |
4706 | - <property name="position">5</property> |
4707 | - </packing> |
4708 | - </child> |
4709 | - <child> |
4710 | - <widget class="GtkLabel" id="label3"> |
4711 | - <property name="visible">True</property> |
4712 | - <property name="label" translatable="yes">Reset</property> |
4713 | - </widget> |
4714 | - <packing> |
4715 | - <property name="position">5</property> |
4716 | - <property name="tab_fill">False</property> |
4717 | - <property name="type">tab</property> |
4718 | - </packing> |
4719 | - </child> |
4720 | - </widget> |
4721 | - <packing> |
4722 | - <property name="position">1</property> |
4723 | - </packing> |
4724 | - </child> |
4725 | - <child internal-child="action_area"> |
4726 | - <widget class="GtkHButtonBox" id="dialog-closebutton-area"> |
4727 | - <property name="visible">True</property> |
4728 | - <property name="layout_style">end</property> |
4729 | - <child> |
4730 | - <widget class="GtkButton" id="close_button"> |
4731 | - <property name="label">gtk-close</property> |
4732 | - <property name="visible">True</property> |
4733 | - <property name="can_focus">True</property> |
4734 | - <property name="receives_default">False</property> |
4735 | - <property name="use_stock">True</property> |
4736 | - <signal name="clicked" handler="on_close_button_clicked"/> |
4737 | - </widget> |
4738 | - <packing> |
4739 | - <property name="expand">False</property> |
4740 | - <property name="fill">False</property> |
4741 | - <property name="position">0</property> |
4742 | - </packing> |
4743 | - </child> |
4744 | - </widget> |
4745 | - <packing> |
4746 | - <property name="expand">False</property> |
4747 | - <property name="pack_type">end</property> |
4748 | - <property name="position">0</property> |
4749 | - </packing> |
4750 | - </child> |
4751 | - </widget> |
4752 | - </child> |
4753 | - </widget> |
4754 | - <widget class="GtkDialog" id="url_dialog"> |
4755 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4756 | - <property name="border_width">5</property> |
4757 | - <property name="resizable">False</property> |
4758 | - <property name="modal">True</property> |
4759 | - <property name="window_position">center-on-parent</property> |
4760 | - <property name="destroy_with_parent">True</property> |
4761 | - <property name="icon_name">gtk-edit</property> |
4762 | - <property name="type_hint">normal</property> |
4763 | - <property name="skip_taskbar_hint">True</property> |
4764 | - <property name="has_separator">False</property> |
4765 | - <signal name="delete_event" handler="on_url_dialog_delete_event"/> |
4766 | - <child internal-child="vbox"> |
4767 | - <widget class="GtkVBox" id="dialog-vbox2"> |
4768 | - <property name="visible">True</property> |
4769 | - <property name="spacing">2</property> |
4770 | - <child> |
4771 | - <widget class="GtkHBox" id="hbox1"> |
4772 | - <property name="visible">True</property> |
4773 | - <child> |
4774 | - <widget class="GtkLabel" id="label1"> |
4775 | - <property name="visible">True</property> |
4776 | - <property name="label" translatable="yes">URL:</property> |
4777 | - </widget> |
4778 | - <packing> |
4779 | - <property name="expand">False</property> |
4780 | - <property name="padding">5</property> |
4781 | - <property name="position">0</property> |
4782 | - </packing> |
4783 | - </child> |
4784 | - <child> |
4785 | - <widget class="GtkEntry" id="url_entry"> |
4786 | - <property name="visible">True</property> |
4787 | - <property name="can_focus">True</property> |
4788 | - <property name="has_focus">True</property> |
4789 | - <property name="can_default">True</property> |
4790 | - <property name="has_default">True</property> |
4791 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4792 | - </widget> |
4793 | - <packing> |
4794 | - <property name="position">1</property> |
4795 | - </packing> |
4796 | - </child> |
4797 | - </widget> |
4798 | - <packing> |
4799 | - <property name="position">1</property> |
4800 | - </packing> |
4801 | - </child> |
4802 | - <child internal-child="action_area"> |
4803 | - <widget class="GtkHButtonBox" id="dialog-action_area2"> |
4804 | - <property name="visible">True</property> |
4805 | - <property name="layout_style">end</property> |
4806 | - <child> |
4807 | - <widget class="GtkButton" id="url_dialog_cancel_button"> |
4808 | - <property name="label">gtk-cancel</property> |
4809 | - <property name="visible">True</property> |
4810 | - <property name="can_focus">True</property> |
4811 | - <property name="receives_default">False</property> |
4812 | - <property name="use_stock">True</property> |
4813 | - <signal name="clicked" handler="on_url_dialog_cancel_button_clicked"/> |
4814 | - </widget> |
4815 | - <packing> |
4816 | - <property name="expand">False</property> |
4817 | - <property name="fill">False</property> |
4818 | - <property name="position">0</property> |
4819 | - </packing> |
4820 | - </child> |
4821 | - <child> |
4822 | - <widget class="GtkButton" id="url_dialog_ok_button"> |
4823 | - <property name="label">gtk-ok</property> |
4824 | - <property name="visible">True</property> |
4825 | - <property name="can_focus">True</property> |
4826 | - <property name="receives_default">False</property> |
4827 | - <property name="use_stock">True</property> |
4828 | - <signal name="clicked" handler="on_url_dialog_ok_button_clicked"/> |
4829 | - </widget> |
4830 | - <packing> |
4831 | - <property name="expand">False</property> |
4832 | - <property name="fill">False</property> |
4833 | - <property name="position">1</property> |
4834 | - </packing> |
4835 | - </child> |
4836 | - </widget> |
4837 | - <packing> |
4838 | - <property name="expand">False</property> |
4839 | - <property name="pack_type">end</property> |
4840 | - <property name="position">0</property> |
4841 | - </packing> |
4842 | - </child> |
4843 | - </widget> |
4844 | - </child> |
4845 | - </widget> |
4846 | - <widget class="GtkDialog" id="weather_search_dialog"> |
4847 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4848 | - <property name="border_width">5</property> |
4849 | - <property name="window_position">center-on-parent</property> |
4850 | - <property name="type_hint">dialog</property> |
4851 | - <property name="has_separator">False</property> |
4852 | - <child internal-child="vbox"> |
4853 | - <widget class="GtkVBox" id="vbox20"> |
4854 | - <property name="visible">True</property> |
4855 | - <property name="spacing">2</property> |
4856 | - <child> |
4857 | - <widget class="GtkVBox" id="vbox21"> |
4858 | - <property name="visible">True</property> |
4859 | - <child> |
4860 | - <widget class="GtkFrame" id="frame6"> |
4861 | - <property name="visible">True</property> |
4862 | - <property name="label_xalign">0</property> |
4863 | - <property name="shadow_type">none</property> |
4864 | - <child> |
4865 | - <widget class="GtkAlignment" id="alignment16"> |
4866 | - <property name="visible">True</property> |
4867 | - <property name="top_padding">5</property> |
4868 | - <property name="left_padding">12</property> |
4869 | - <child> |
4870 | - <widget class="GtkHBox" id="hbox18"> |
4871 | - <property name="visible">True</property> |
4872 | - <child> |
4873 | - <widget class="GtkLabel" id="label19"> |
4874 | - <property name="visible">True</property> |
4875 | - <property name="label" translatable="yes">City:</property> |
4876 | - </widget> |
4877 | - <packing> |
4878 | - <property name="expand">False</property> |
4879 | - <property name="position">0</property> |
4880 | - </packing> |
4881 | - </child> |
4882 | - <child> |
4883 | - <widget class="GtkEntry" id="location_entry"> |
4884 | - <property name="visible">True</property> |
4885 | - <property name="can_focus">True</property> |
4886 | - <property name="has_focus">True</property> |
4887 | - <property name="can_default">True</property> |
4888 | - <property name="has_default">True</property> |
4889 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4890 | - <signal name="activate" handler="on_location_entry_activate"/> |
4891 | - </widget> |
4892 | - <packing> |
4893 | - <property name="padding">5</property> |
4894 | - <property name="position">1</property> |
4895 | - </packing> |
4896 | - </child> |
4897 | - <child> |
4898 | - <widget class="GtkButton" id="location_find_button"> |
4899 | - <property name="label">gtk-find</property> |
4900 | - <property name="visible">True</property> |
4901 | - <property name="can_focus">True</property> |
4902 | - <property name="receives_default">False</property> |
4903 | - <property name="use_stock">True</property> |
4904 | - <signal name="clicked" handler="on_location_find_button_clicked"/> |
4905 | - </widget> |
4906 | - <packing> |
4907 | - <property name="padding">5</property> |
4908 | - <property name="position">2</property> |
4909 | - </packing> |
4910 | - </child> |
4911 | - </widget> |
4912 | - </child> |
4913 | - </widget> |
4914 | - </child> |
4915 | - <child> |
4916 | - <widget class="GtkLabel" id="label20"> |
4917 | - <property name="visible">True</property> |
4918 | - <property name="label" translatable="yes"><b>Search for location</b></property> |
4919 | - <property name="use_markup">True</property> |
4920 | - </widget> |
4921 | - <packing> |
4922 | - <property name="type">label_item</property> |
4923 | - </packing> |
4924 | - </child> |
4925 | - </widget> |
4926 | - <packing> |
4927 | - <property name="expand">False</property> |
4928 | - <property name="padding">5</property> |
4929 | - <property name="position">0</property> |
4930 | - </packing> |
4931 | - </child> |
4932 | - <child> |
4933 | - <widget class="GtkFrame" id="frame7"> |
4934 | - <property name="visible">True</property> |
4935 | - <property name="label_xalign">0</property> |
4936 | - <property name="shadow_type">none</property> |
4937 | - <child> |
4938 | - <widget class="GtkAlignment" id="alignment17"> |
4939 | - <property name="visible">True</property> |
4940 | - <property name="left_padding">12</property> |
4941 | - <child> |
4942 | - <widget class="GtkScrolledWindow" id="scrolledwindow7"> |
4943 | - <property name="visible">True</property> |
4944 | - <property name="can_focus">True</property> |
4945 | - <property name="border_width">5</property> |
4946 | - <property name="hscrollbar_policy">never</property> |
4947 | - <property name="vscrollbar_policy">automatic</property> |
4948 | - <property name="shadow_type">etched-in</property> |
4949 | - <child> |
4950 | - <widget class="GtkTreeView" id="location_results_treeview"> |
4951 | - <property name="visible">True</property> |
4952 | - <property name="can_focus">True</property> |
4953 | - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> |
4954 | - </widget> |
4955 | - </child> |
4956 | - </widget> |
4957 | - </child> |
4958 | - </widget> |
4959 | - </child> |
4960 | - <child> |
4961 | - <widget class="GtkLabel" id="label21"> |
4962 | - <property name="visible">True</property> |
4963 | - <property name="label" translatable="yes"><b>Search result</b></property> |
4964 | - <property name="use_markup">True</property> |
4965 | - </widget> |
4966 | - <packing> |
4967 | - <property name="type">label_item</property> |
4968 | - </packing> |
4969 | - </child> |
4970 | - </widget> |
4971 | - <packing> |
4972 | - <property name="padding">5</property> |
4973 | - <property name="position">1</property> |
4974 | - </packing> |
4975 | - </child> |
4976 | - </widget> |
4977 | - <packing> |
4978 | - <property name="position">1</property> |
4979 | - </packing> |
4980 | - </child> |
4981 | - <child internal-child="action_area"> |
4982 | - <widget class="GtkHButtonBox" id="hbuttonbox1"> |
4983 | - <property name="visible">True</property> |
4984 | - <property name="layout_style">end</property> |
4985 | - <child> |
4986 | - <widget class="GtkButton" id="location_button_cancel"> |
4987 | - <property name="label">gtk-cancel</property> |
4988 | - <property name="visible">True</property> |
4989 | - <property name="can_focus">True</property> |
4990 | - <property name="receives_default">False</property> |
4991 | - <property name="use_stock">True</property> |
4992 | - <signal name="clicked" handler="on_location_cancel_button_clicked"/> |
4993 | - </widget> |
4994 | - <packing> |
4995 | - <property name="expand">False</property> |
4996 | - <property name="fill">False</property> |
4997 | - <property name="position">0</property> |
4998 | - </packing> |
4999 | - </child> |
5000 | - <child> |
The diff has been truncated for viewing.
Dependent on working-sdist.
This is just some work to clean out a bunch of junk in the backend and try to separate the client from the backend in a more clear manner.
I did a bunch of analysis of what we are/aren't using in terms of messages. This led to some nice cleanup and removal of some methods that simply weren't needed.
I know this may not seem like important work, but it means that there will be less pain when it comes time to completely remove elements of the backend that exist in the client code. Less contact points means less work in the future.