Merge lp:~janez-troha/nuvola-player/8tracks into lp:nuvola-player/2.5.x

Proposed by dz0ny
Status: Merged
Approved by: Jiří Janoušek
Approved revision: 107
Merged at revision: 108
Proposed branch: lp:~janez-troha/nuvola-player/8tracks
Merge into: lp:nuvola-player/2.5.x
Diff against target: 743 lines (+619/-35)
5 files modified
data/google-music-frame/scripts/eighttracks.js (+233/-0)
src/8tracks.vala (+307/-0)
src/application.vala (+5/-1)
src/window.vala (+73/-34)
wscript (+1/-0)
To merge this branch: bzr merge lp:~janez-troha/nuvola-player/8tracks
Reviewer Review Type Date Requested Status
Jiří Janoušek Approve
Review via email: mp+82771@code.launchpad.net

Description of the change

Hi, I've integrated 8tracks.com service, but had to modify the switch service GUI slightly.

To post a comment you must log in.
Revision history for this message
Jiří Janoušek (fenryxo) wrote :

Thanks for your contribution to the Nuvola Player!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/google-music-frame/images/8tracks.jpg'
2Binary files data/google-music-frame/images/8tracks.jpg 1970-01-01 00:00:00 +0000 and data/google-music-frame/images/8tracks.jpg 2011-11-19 12:08:23 +0000 differ
3=== added file 'data/google-music-frame/scripts/eighttracks.js'
4--- data/google-music-frame/scripts/eighttracks.js 1970-01-01 00:00:00 +0000
5+++ data/google-music-frame/scripts/eighttracks.js 2011-11-19 12:08:23 +0000
6@@ -0,0 +1,233 @@
7+/*
8+8tracks.com
9+
10+Copyright 2011 Janez Troha <janez.troha@gmail.com>
11+
12+This program is free software: you can redistribute it and/or modify it
13+under the terms of the GNU General Public License version 3, as published
14+by the Free Software Foundation.
15+
16+This program is distributed in the hope that it will be useful, but
17+WITHOUT ANY WARRANTY; without even the implied warranties of
18+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
19+PURPOSE. See the GNU General Public License for more details.
20+
21+You should have received a copy of the GNU General Public License along
22+with this program. If not, see <http://www.gnu.org/licenses/>.
23+*/
24+
25+(function(main_obj){
26+
27+var hasClass = function(elm, klass){
28+ var classes = elm.className.split(" ");
29+ for(var j=0; j < classes.length; j++){
30+ console.log(classes[j]);
31+ if(classes[j] == klass) return true;
32+ }
33+ return false;
34+}
35+
36+var toArray = function(obj) {
37+ return Array.prototype.slice.call(obj);
38+}
39+
40+var gmf_bind = function(scope, fn) {
41+ return function () {
42+ return fn.apply(scope, toArray(arguments));
43+ };
44+}
45+
46+
47+
48+/* used to propagate exception to main program */
49+var $throw_exception = function(message){
50+ console.log("GMF::exception::" + message);
51+}
52+
53+try{
54+ /*
55+ * WebKit.WebView.console_message signal doesn't provide
56+ * debug/error/warn flag for console.debug/error/warn().
57+ * Therefore we use these wrappers.
58+ */
59+ var $log_debug = function(message){
60+ console.log("GMF::debug::" + message);
61+ }
62+
63+ var $log_info = function(message){
64+ console.log("GMF::info::" + message);
65+ }
66+
67+ var $log_warn = function(message){
68+ console.log("GMF::warn::" + message);
69+ }
70+
71+ var $log_error = function(message){
72+ console.log("GMF::error::" + message);
73+ }
74+
75+
76+ var $GMF = function(){
77+ this.SEPARATOR = "§#@!@#§";
78+ this.changed_callbacks = [];
79+ this.album_art = null;
80+ this.artist = null;
81+ this.song = null;
82+ this.state = "none";
83+ this.can_prev = false;
84+ this.can_next = false;
85+ this.timeout = null;
86+ this.iframe = document.createElement("iframe");
87+ this.iframe.name = "GMF_iframe";
88+ this.iframe.style.display = "none";
89+ document.body.appendChild(this.iframe);
90+ var foo = gmf_bind(this, this.saveState);
91+ this.changed_callbacks.push(foo);
92+ this.update();
93+ this.timeout = setInterval(gmf_bind(this, this.setCallback), 500);
94+ };
95+
96+ $GMF.prototype.setCallback = function(){
97+ try{
98+ this.update(window.player.set);
99+ //clearInterval(this.timeout);
100+ }
101+ catch(e){
102+
103+ }
104+ }
105+
106+ $GMF.prototype.update = function(current_song){
107+ $log_debug("Current song: " + current_song);
108+ if(!current_song){
109+ try{
110+ current_song = window.player.set;
111+ }
112+ catch(e){
113+ console.log(e.message);
114+ }
115+ }
116+ if(!current_song) return;
117+
118+ var album_art = null;
119+ var artist = null;
120+ var song = null;
121+ var state = null;
122+ try{
123+ var info = current_song.track;
124+ if(info){
125+ song = info.name;
126+ album = info.release_name;
127+ artist = info.performer;
128+ album_art = window.mix.cover_urls.sq133;
129+ this.can_next = current_song.skip_allowed;
130+ }
131+
132+ if(window.player.is_playing()){
133+ state = "playing";
134+ }
135+ else {
136+ state = "paused";
137+ }
138+ //~ console.log(current_song.status + " " + song + " by " + artist + " from " + album + ": " + album_art);
139+ }
140+ catch(e){
141+ album_art = null;
142+ artist = null;
143+ song = null;
144+ state = null;
145+ }
146+
147+ var changed = false;
148+ if(state !== this.state){
149+ this.state = state;
150+ changed = true;
151+ }
152+ if(album_art !== this.album_art){
153+ this.album_art = album_art;
154+ changed = true;
155+ }
156+ if(artist !== this.artist){
157+ this.artist = artist;
158+ changed = true;
159+ }
160+ if(song !== this.song){
161+ this.song = song;
162+ changed = true;
163+ }
164+
165+ if(changed){
166+ this.saveState();
167+ }
168+ console.log(this.state + ", " + this.can_next + ", " + this.can_prev + ", " + this.album_art + ", " + this.artist + ", " + this.song);
169+ }
170+
171+ $GMF.prototype.saveState = function(){
172+ var state = this.state
173+ + this.SEPARATOR + (this.can_prev ? "true" : "false")
174+ + this.SEPARATOR + (this.can_next ? "true" : "false");
175+ if(this.album_art !== null || this.artist !== null || this.song !== null){
176+ state += this.SEPARATOR + (this.album_art === null ? "" : this.album_art)
177+ + this.SEPARATOR + (this.artist === null ? "" : this.artist)
178+ + this.SEPARATOR + (this.song === null ? "" : this.song);
179+ }
180+
181+ var doc = this.iframe.contentDocument;
182+ if(doc.title != state) doc.title = state;
183+ }
184+
185+ $GMF.prototype.command = function(cmd){
186+ try{
187+ console.log("kaj naj: "+cmd);
188+
189+ if (cmd == "togglePlayPause") {
190+ window.$j('#player_play_button').trigger('click');
191+ }
192+ if (cmd == "next") {
193+ window.$j('#player_skip_button').trigger('click');
194+ }
195+
196+ }
197+ catch(e){
198+ $throw_exception(e.message);
199+ }
200+ }
201+
202+ $GMF.prototype._triggerMouseEvent = function(elm, name){
203+ var event = document.createEvent('MouseEvents');
204+ event.initMouseEvent(name, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, elm);
205+ elm.dispatchEvent(event);
206+ }
207+
208+ $GMF.prototype._clickOnElement = function(elm){
209+ this._triggerMouseEvent(elm, 'mouseover');
210+ this._triggerMouseEvent(elm, 'mousedown');
211+ this._triggerMouseEvent(elm, 'mouseup');
212+ }
213+
214+ $GMF.prototype.thumbsUp = function(){
215+ try{
216+ window.Grooveshark.voteCurrentSong(1);
217+ }
218+ catch(e){
219+ $throw_exception(e.message);
220+ }
221+ }
222+
223+ $GMF.prototype.thumbsDown = function(){
224+ try{
225+ window.Grooveshark.voteCurrentSong(-1);
226+ }
227+ catch(e){
228+ $throw_exception(e.message);
229+ }
230+ }
231+
232+
233+ main_obj.$GMF = new $GMF(); // Singleton ;-)
234+}
235+catch(e){
236+ $throw_exception(e.message);
237+}
238+
239+})(window);
240\ No newline at end of file
241
242=== added file 'src/8tracks.vala'
243--- src/8tracks.vala 1970-01-01 00:00:00 +0000
244+++ src/8tracks.vala 2011-11-19 12:08:23 +0000
245@@ -0,0 +1,307 @@
246+/*
247+Google Music Frame :: Frame
248+
249+Copyright 2011 Jiří Janoušek <janousek.jiri@gmail.com>
250+
251+This program is free software: you can redistribute it and/or modify it
252+under the terms of the GNU General Public License version 3, as published
253+by the Free Software Foundation.
254+
255+This program is distributed in the hope that it will be useful, but
256+WITHOUT ANY WARRANTY; without even the implied warranties of
257+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
258+PURPOSE. See the GNU General Public License for more details.
259+
260+You should have received a copy of the GNU General Public License along
261+with this program. If not, see <http://www.gnu.org/licenses/>.
262+*/
263+
264+using WebKit;
265+using Soup;
266+using Gee;
267+using Fenryxo;
268+using Fenryxo.Logger;
269+using Fenryxo.Config;
270+using Fenryxo.Path;
271+using Fenryxo.Widgets;
272+
273+namespace GMF.Services{
274+
275+
276+
277+ /**
278+ * Frame with Google Music web interface.
279+ * Handles state changes received from WebView and produced by script
280+ * loaded in WebView, console logging, WebView requests, user scripts management, etc.
281+ */
282+ public class EightTracks: Service{
283+ /**
284+ * Constructs new Frame for application
285+ *
286+ * @param app Application object
287+ */
288+ public class EightTracks(GMF.Application app, GMF.Frame frame, Storage storage){
289+ base(app, frame, storage);
290+ this.home_url = "http://8tracks.com/";
291+ this.base_url = "8tracks.com";
292+ this.id = "eighttracks";
293+ this.secure = false;
294+ if(this.secure){
295+ log_message("Secure connection check is enabled.");
296+ }
297+ else{
298+ log_warning("Secure connection check is disabled.");
299+ }
300+ }
301+
302+ /**
303+ * Loads Google Music home page
304+ */
305+ public override void go_home(){
306+ var uri = this.home_url;
307+ if(this.secure && uri.has_prefix("http://")){
308+ uri = "https://" + uri.substring(7);
309+ log_message("Redirect to %s", uri);
310+ }
311+ else if(!this.secure && uri.has_prefix("https://")){
312+ uri = "http://" + uri.substring(8);
313+ log_message("Redirect to %s", uri);
314+ }
315+ this.frame.open(uri);
316+ }
317+
318+ protected override void on_frame_loaded(WebFrame frame){
319+ unowned string uri = frame.get_uri();
320+ log_debug("Loading scripts for frame with uri: %s", uri ?? "null");
321+ /* check frame and its URI to load scripts only
322+ * in Google Music web interface */
323+ if(uri == null
324+ || frame != this.frame.get_main_frame()
325+ || (!uri.has_prefix("http://" + this.base_url)
326+ && !uri.has_prefix("https://" + this.base_url))) return;
327+
328+ this.frame.load_scripts(this.id);
329+ }
330+
331+ /**
332+ * Execute Google Music player action
333+ *
334+ * @param cmd Action
335+ * @throw FrameError on failure
336+ */
337+ private void command(string cmd) throws FrameError{
338+ log_debug("Frame command %s", cmd);
339+ this.frame.execute("$GMF.command('" + cmd + "');");
340+ }
341+
342+ /**
343+ * Skips to the next track in the tracklist.
344+ *
345+ * If there is no next track (and endless playback and track repeat are both off),
346+ * playback will be stopped. If playback is paused or stopped, it remains that way.
347+ *
348+ * @param notify if true a notification will be shown if needed
349+ */
350+ public override void next_song(bool notify=true){
351+ try{
352+ this.skip_notification = ! notify;
353+ this.command("next");
354+ }
355+ catch(FrameError e){
356+ log_critical("Player: nextSong command failed: %s", e.message);
357+ if(!notify) this.skip_notification = false;
358+ }
359+ }
360+
361+ /***
362+ * Skips to the previous track in the tracklist.
363+ *
364+ * If there is no previous track (and endless playback and track repeat are both off),
365+ * playback will be stopped. If playback is paused or stopped, it remains that way.
366+ *
367+ * @param notify if true a notification will be shown if needed
368+ */
369+ public override void previous_song(bool notify=true){
370+ try{
371+ this.skip_notification = ! notify;
372+ this.command("previous");
373+ }
374+ catch(FrameError e){
375+ log_critical("Player: prevSong command failed: %s", e.message);
376+ if(!notify) this.skip_notification = false;
377+ }
378+ }
379+
380+ /**
381+ * Pauses playback.
382+ *
383+ * If playback is already paused, this has no effect. Calling Play after this should
384+ * cause playback to start again from the same position.
385+ *
386+ * @param notify if true a notification will be shown if needed
387+ */
388+ public override void pause(bool notify=true){
389+ /* No notifications are shown on pause action,
390+ * therefore notify param has no effect
391+ */
392+ try{
393+ this.command("pause");
394+ }
395+ catch(FrameError e){
396+ log_critical("Player: pauseSong command failed: %s", e.message);
397+ }
398+ }
399+
400+ /**
401+ * Pauses playback.
402+ * If playback is already paused, resumes playback.
403+ * If playback is stopped, starts playback.
404+ *
405+ * @param notify if true a notification will be shown if needed
406+ */
407+ public override void toggle_play(bool notify=true){
408+ try{
409+ if(this.playback_state != "playing") this.skip_notification = ! notify;
410+ this.command("togglePlayPause");
411+ }
412+ catch(FrameError e){
413+ log_critical("Player: playPause command failed: %s", e.message);
414+ if(!notify) this.skip_notification = false;
415+ }
416+ }
417+
418+ /**
419+ * Stops playback.
420+ *
421+ * If playback is already stopped, this has no effect. Calling Play after this should
422+ * cause playback to start again from the beginning of the track.
423+ *
424+ * @param notify if true a notification will be shown if needed
425+ */
426+ public override void stop(bool notify=true){
427+ /* No notifications are shown on pause action,
428+ * therefore notify param has no effect
429+ */
430+ this.pause(notify);
431+ }
432+
433+ /**
434+ * Starts or resumes playback.
435+ *
436+ * If already playing, this has no effect.
437+ * If there is no track to play, this has no effect.
438+ *
439+ * @param notify if true a notification will be shown if needed
440+ */
441+ public override void play(bool notify=true){
442+ try{
443+ this.skip_notification = ! notify;
444+ if(!this.playing) this.command("playPause");
445+ }
446+ catch(FrameError e){
447+ log_critical("Player: playPause command failed: %s", e.message);
448+ if(!notify) this.skip_notification = false;
449+ }
450+ }
451+
452+ public override void thumbs_up(bool notify=true){
453+ try{
454+ //~ this.skip_notification = ! notify;
455+ this.frame.execute("$GMF.thumbsUp();");
456+ }
457+ catch(FrameError e){
458+ log_critical("Player: thumbUp command failed: %s", e.message);
459+ //~ if(!notify) this.skip_notification = false;
460+ }
461+ }
462+
463+ public override void thumbs_down(bool notify=true){
464+ try{
465+ //~ this.skip_notification = ! notify;
466+ this.frame.execute("$GMF.thumbsDown();");
467+ }
468+ catch(FrameError e){
469+ log_critical("Player: thumbUp command failed: %s", e.message);
470+ //~ if(!notify) this.skip_notification = false;
471+ }
472+ }
473+
474+ /**
475+ * Open new window in the default web browser
476+ */
477+ protected override bool on_new_window(WebFrame frame, NetworkRequest req,
478+ WebNavigationAction action, WebPolicyDecision decision){
479+ unowned string uri = req.get_uri();
480+ log_debug("New window request: %s", uri);
481+
482+ if(("accounts.google.com/" in uri)
483+ || ("google.com/accounts" in uri)){
484+ this.frame.open(uri);
485+ }
486+ else{
487+ this.open_in_web_browser(uri);
488+ }
489+ decision.ignore();
490+ return true;
491+ }
492+
493+ /**
494+ * Decide whether open page in frame or in the default browser
495+ */
496+ protected override bool on_new_page(WebFrame frame, NetworkRequest req,
497+ WebNavigationAction action, WebPolicyDecision decision){
498+ string uri = req.get_uri();
499+ log_debug("New page request: URI: %s, frame URI: %s",
500+ uri, frame.get_uri() ?? "null");
501+
502+ if(frame != this.frame.get_main_frame()){
503+ // don't care about pages in <iframe> elements
504+ return false;
505+ }
506+
507+ /* Open Flash install page in the default browser */
508+ if(uri.has_prefix("http://get.adobe.com/flashplayer")){
509+ this.open_in_web_browser(uri);
510+ decision.ignore();
511+ return true;
512+ }
513+
514+
515+
516+
517+ /* Open Google services in the default browser (except login page) */
518+ if(("google.com" in uri)
519+ && !("music.google.com" in uri)
520+ && !("accounts.google.com/" in uri)
521+ && !("google.com/accounts" in uri)){
522+ this.open_in_web_browser(uri);
523+ decision.ignore();
524+ return true;
525+ }
526+
527+ if(uri.has_prefix("https://" + this.base_url)
528+ || uri.has_prefix("http://" + this.base_url)){
529+ if(this.secure && uri.has_prefix("http://")){
530+ // Wanna HTTPS
531+ uri = "https://" + uri.substring(7);
532+ log_message("Reditect to %s", uri);
533+ this.frame.open(uri);
534+ decision.ignore();
535+ return true;
536+ }
537+ else{
538+ this.app.ui_state[this.id + ".last_uri"] = (owned) uri;
539+ try{
540+ this.app.ui_state.save();
541+ }
542+ catch(Error e){
543+ log_warning("Unable to save UI state: %s", e.message);
544+ }
545+
546+ }
547+ }
548+ return false;
549+ }
550+ }
551+
552+}
553
554=== modified file 'src/application.vala'
555--- src/application.vala 2011-11-17 13:46:39 +0000
556+++ src/application.vala 2011-11-19 12:08:23 +0000
557@@ -105,7 +105,7 @@
558 */
559 public override void run(){
560 string? service = this.config["service"] ?? "";
561- if(service != "grooveshark" && service != "google-music"){
562+ if(service != "grooveshark" && service != "google-music" && service != "eighttracks"){
563 var dialog = new SelectServiceWindow(this, this.storage);
564 service = dialog.get_service();
565 if(service == null){
566@@ -229,10 +229,14 @@
567 }
568
569 private Service load_service(string id){
570+ if(id == "eighttracks")
571+ return new EightTracks(this, this.frame, this.storage);
572 if(id == "grooveshark")
573 return new Grooveshark(this, this.frame, this.storage);
574 if(id == "google-music")
575 return new GoogleMusic(this, this.frame, this.storage);
576+
577+
578 assert_not_reached();
579 }
580
581
582=== modified file 'src/window.vala'
583--- src/window.vala 2011-11-17 22:32:22 +0000
584+++ src/window.vala 2011-11-19 12:08:23 +0000
585@@ -315,7 +315,7 @@
586 table.attach(label, 0, 2, line, line + 1, opt, opt, 15, 15);
587
588 line++;
589-
590+ //Google music start
591 button = new Gtk.Button();
592 button.clicked.connect( () => {
593 this.service = "google-music";
594@@ -332,28 +332,8 @@
595 label.use_markup = true;
596 button.add(label);
597 }
598- table.attach(button, 1, 2, line, line + 1, opt, opt, 0, 10);
599-
600- button = new Gtk.Button();
601- button.clicked.connect( () => {
602- this.service = "grooveshark";
603- this.response(ResponseType.OK);
604- });
605-
606- logo_file = Fenryxo.Path.get_data_file(app.app_name + "/images/grooveshark.png");
607- if(logo_file != null){
608- logo = new Gtk.Image.from_file(logo_file.get_path());
609- button.add(logo);
610- }
611- else{
612- label = new Gtk.Label("<big><b>Grooveshark</b></big>");
613- label.use_markup = true;
614- button.add(label);
615- }
616 table.attach(button, 0, 1, line, line + 1, opt, opt, 0, 10);
617
618- line++;
619-
620 /// '<b>text</b>' makes 'text' bold
621 string about = _("""<b>Google Music</b> (formerly Music Beta by Google) is an online music streaming service that supports streaming music to desktop browsers and Android phones and tablets. The service is only available through invitation to US residents only. It supports uploading a user's own music and supports buying music at Android Market.
622
623@@ -363,7 +343,34 @@
624 label.wrap_mode = Pango.WrapMode.WORD;
625 label.wrap = true;
626 table.attach(label, 1, 2, line, line + 1, opt, opt, 15, 10);
627+
628+ button = new Gtk.Button.with_label("Use Music by Google");
629+ button.clicked.connect( () => {
630+ this.service = "google-music";
631+ this.response(ResponseType.OK);
632+ });
633+ table.attach(button, 2, 3, line, line + 1, opt, opt, 0, 0);
634+ line++;
635+ //Google music end
636+
637+ //Grooveshark start
638+ button = new Gtk.Button();
639+ button.clicked.connect( () => {
640+ this.service = "grooveshark";
641+ this.response(ResponseType.OK);
642+ });
643
644+ logo_file = Fenryxo.Path.get_data_file(app.app_name + "/images/grooveshark.png");
645+ if(logo_file != null){
646+ logo = new Gtk.Image.from_file(logo_file.get_path());
647+ button.add(logo);
648+ }
649+ else{
650+ label = new Gtk.Label("<big><b>Grooveshark</b></big>");
651+ label.use_markup = true;
652+ button.add(label);
653+ }
654+ table.attach(button, 0, 1, line, line + 1, opt, opt, 0, 10);
655 /// '<b>text</b>' makes 'text' bold
656 about = _("""<b>Grooveshark</b> is an international online music search engine, music streaming service and music recommendation web software application, allowing users to search for, stream, and upload music that can be played immediately or added to a playlist. An optional paid subscription adds additional functionality and removes advertisements.
657
658@@ -372,28 +379,60 @@
659 label.use_markup = true;
660 label.wrap_mode = Pango.WrapMode.WORD;
661 label.wrap = true;
662- table.attach(label, 0, 1, line, line + 1, opt, opt, 15, 10);
663-
664-
665-line++;
666-
667- button = new Gtk.Button.with_label("Use Music Beta by Google");
668- button.clicked.connect( () => {
669- this.service = "google-music";
670- this.response(ResponseType.OK);
671- });
672- table.attach(button, 1, 2, line, line + 1, opt, opt, 0, 0);
673+ table.attach(label, 1, 2, line, line + 1, opt, opt, 15, 10);
674+
675
676 button = new Gtk.Button.with_label("Use Grooveshark");
677 button.clicked.connect( () => {
678 this.service = "grooveshark";
679 this.response(ResponseType.OK);
680 });
681- table.attach(button, 0, 1, line, line + 1, opt, opt, 0, 0);
682-
683+ table.attach(button, 2, 3, line, line + 1, opt, opt, 0, 0);
684+ line++;
685+ //Grooveshark end
686+
687+ //8Tracks start
688+ button = new Gtk.Button();
689+ button.clicked.connect( () => {
690+ this.service = "eighttracks";
691+ this.response(ResponseType.OK);
692+ });
693+
694+ logo_file = Fenryxo.Path.get_data_file(app.app_name + "/images/8tracks.jpg");
695+ if(logo_file != null){
696+ logo = new Gtk.Image.from_file(logo_file.get_path());
697+ button.add(logo);
698+ }
699+ else{
700+ label = new Gtk.Label("<big><b>8tracks</b></big>");
701+ label.use_markup = true;
702+ button.add(label);
703+ }
704+ table.attach(button, 0, 1, line, line + 1, opt, opt, 0, 10);
705+ /// '<b>text</b>' makes 'text' bold
706+ about = _("""<b>8tracks</b> is a website that fuses elements of internet radio and social networking revolving around the concept of streaming user-curated playlists consisting of at least 8 tracks.
707+
708+<i>Source: <a href="http://en.wikipedia.org/wiki/8tracks.com">8tracks.com on Wikipedia</a>, <a href="http://8tracks.com">Official website</a></i>""");
709+ label = new Gtk.Label(about);
710+ label.use_markup = true;
711+ label.wrap_mode = Pango.WrapMode.WORD;
712+ label.wrap = true;
713+ table.attach(label, 1, 2, line, line + 1, opt, opt, 15, 10);
714+
715+
716+ button = new Gtk.Button.with_label("Use 8tracks");
717+ button.clicked.connect( () => {
718+ this.service = "eighttracks";
719+ this.response(ResponseType.OK);
720+ });
721+ table.attach(button, 2, 3, line, line + 1, opt, opt, 0, 0);
722+ //8Tracks end
723+
724 var content = this.get_content_area() as Gtk.Container;
725 content.add(table);
726 content.show_all();
727+
728+
729 }
730
731 public string? get_service(){
732
733=== modified file 'wscript'
734--- wscript 2011-11-17 13:12:43 +0000
735+++ wscript 2011-11-19 12:08:23 +0000
736@@ -180,6 +180,7 @@
737 'src/base-service.vala',
738 'src/google-music.vala',
739 'src/grooveshark.vala',
740+ 'src/8tracks.vala',
741 'src/application.vala',
742 'src/player.vala',
743 'src/mpris.vala',

Subscribers

People subscribed via source and target branches