2 Pitchfork Music Player Daemon Client
3 Copyright (C) 2007 Roger Bystrøm
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 /* this is so broken that I can hardly describe it */
21 var BROWSER_ID = "browser_";
23 var BROWSE_ARTIST = 1;
25 var BROWSE_SEARCH = 3;
29 var BROWSE_SEARCH_OPTS;
31 function BrowserList(browser) {
35 this.type = BROWSE_FILE;
36 this.type_name = new Array();
37 this.type_name[BROWSE_FILE] = LANG.FILESYSTEM;
38 this.type_name[BROWSE_ARTIST] = LANG.ARTIST;
39 this.type_name[BROWSE_ALBUM] = LANG.ALBUM;
40 this.browser = browser;
41 this.search = new KeySearch(null); // this is for key-typing in the fields, not the search field
42 this.search_select = null; // jeje.. search field et al.
43 this.search_field = null;
44 this.search_opt = null;
45 this.act_buttons = null;
46 this.open_popup = null;
47 this.eventListener = null;
50 function BrowserWindow(value) {
55 BrowserList.prototype.get_window_by_value = function (elem) {
64 BrowserList.prototype.get_last_window = function() {
71 BrowserList.prototype.get_previous_window = function(node) {
73 while(w!=null&&w.next!=node)
77 BrowserList.prototype.get_next_window = function(node) {
87 function dirlist_init() {
88 BROWSE_SEARCH_OPTS = new Array(LANG.ANYTAG, LANG.ARTIST, LANG.TITLE,
89 LANG.ALBUM, LANG.GENRE, LANG.FILENAME, LANG.COMPOSER,
90 LANG.PERFORMER, LANG.DATE, LANG.LYRICS);
92 remove_children(pl_overlay_write);
93 send_command("dirlist", dirlist_init_handler);
96 function dirlist_init_handler(response) {
97 if(response=="failure") {
98 show_status_bar(LANG.E_FAILED_LOAD_DIR);
103 blist = new BrowserList(pl_overlay_write);
104 var stuff = create_node("div");
105 stuff.className = "browse_type_container";
107 for(var i=0; i<blist.type_name.length; i++) {
108 var n = create_node("p");
109 n.className = "browse_type";
110 n.appendChild(create_txt(blist.type_name[i]));
111 stuff.appendChild(n);
113 add_listener(n, "click", browse_file_init);
114 else if(i==BROWSE_ARTIST)
115 add_listener(n, "click", browse_artist_init);
116 else if(i==BROWSE_ALBUM)
117 add_listener(n, "click", browse_album_init);
118 add_listener(n, "mousedown", stop_event);
122 var search = blist.search_select = create_search_choices(BROWSE_SEARCH_OPTS, browse_search_change);
123 stuff.appendChild(search);
124 var opt = blist.search_opt = create_node("option", "bs_search");
126 opt.appendChild(create_txt("Search:"));
127 insert_first(opt, search);
128 search.selectedIndex = 0;
129 search.id ="browse_search_select";
131 blist.search_field = search = create_node("input", "browse_search_field");
132 search.className = "browse_type";
134 /* make sure it doesn't get captured by someone else */
135 add_listener(search, "keydown", stop_propagation);
136 add_listener(search, "keyup", browser_search_field_keyhandler);
137 search.disabled = "disabled";
138 stuff.appendChild(search);
140 var update = create_node("p");
141 update.className = "browse_type";
142 update.appendChild(create_txt(LANG.UPDATE_DB));
143 add_listener(update, "click", send_update_db_cmd);
144 stuff.appendChild(update);
146 blist.browser.appendChild(stuff);
148 var buttons = blist.act_buttons = create_node("ul", "browser_act_buttons");
151 var bl = new Array();
152 for(var i=0; i<BROWSER_NUM; i++) {
153 ul = create_node("ul", BROWSER_ID + i);
155 ul.className = "browser_field";
156 blist.browser.appendChild(ul);
157 setup_dirlist_listeners(ul);
159 btn = create_node("li", "browser_btn_add_" + i);
160 btn.className = "browser_button_add";
162 btn.style.left = 29*i + "%";
163 btn.setAttribute("field", i);
164 btn.appendChild(create_txt(LANG.ADD));
165 add_listener(btn, "click", browser_multi_add);
166 buttons.appendChild(btn);
168 add_listener(buttons, "mousedown", stop_event);
169 blist.browser.appendChild(buttons);
172 for(var i=0; i<BROWSER_NUM; i++) {
173 var bw = new BrowserWindow(bl[i]);
175 blist.first = last = bw;
177 last = last.next = bw;
180 browser_fill_entry(blist.first.value, response, 0, "");
184 /* opts: array with options
185 onchange_cb: what to call when something changes
188 function create_search_choices(opts, onchange_cb) {
189 var search = create_node("select");
190 search.className = "browse_type";
191 add_listener(search, "change", onchange_cb);
193 for(var i=0; i<opts.length; i++) {
194 opt = create_node("option");
196 opt.appendChild(create_txt(opts[i]));
197 search.appendChild(opt);
202 function browse_file_init() {
203 browse_style_changed(BROWSE_FILE);
205 function browse_album_init() {
206 browse_style_changed(BROWSE_ALBUM);
208 function browse_artist_init() {
209 browse_style_changed(BROWSE_ARTIST);
211 function browse_search_change(e) {
212 /* this is when opening the search browser */
213 if(blist.type!=BROWSE_SEARCH) {
216 browser_clear_window(w.value);
219 browse_style_changed(BROWSE_SEARCH);
220 browser_search_field_enable();
226 function browse_style_changed(type) {
230 /* this is when switching back to normal view from search */
231 if(blist.type==BROWSE_SEARCH) {
234 w.value.style.display = "";
235 w.value.style.width = "";
238 browser_search_field_clear();
239 insert_first(blist.search_opt, blist.search_select);
240 blist.search_opt.selected = "selected";
241 blist.search_select.blur(); // just in case it is selected..
242 browser_act_buttons_display(true);
244 else if(type==BROWSE_SEARCH) {
246 var w = blist.first.next;
248 w.value.style.display = "none";
252 blist.first.value.style.width = "95%";
253 browser_act_buttons_display(false);
254 remove_node(blist.search_opt);
257 if(type!=BROWSE_SEARCH)
258 browser_open_single_item(blist.first.value);
260 function setup_dirlist_listeners(ul) {
261 add_listener(ul, "click", browser_click);
262 /* no text selection support: */
263 add_listener(ul, "mousedown", prevent_dirlist_default);
266 function prevent_dirlist_default(e) {
267 if(e.target.hasAttribute("diritem")||e.target.parentNode.hasAttribute("diritem"))
271 function browser_fill_entry(entry, resp, strip, parent_dir) {
279 if(blist.type==BROWSE_FILE) {
280 var tmp = add_li(entry, "< ..", entry.id + "_parent");
281 tmp.setAttribute("dirtype", "parent");
286 for(var i in resp[t]) {
293 name = name.substring(0,strip-1);
294 tmp.setAttribute("diritem", name);
295 entry.setAttribute("dirlist", name);
297 entry.setAttribute("parent_dir", parent_dir);
300 entry.setAttribute("dirlist", blist.open_name);
301 entry.setAttribute("parent_dir", parent_dir);
305 entry.setAttribute("dirlist", "");
307 var strip_name = null;
309 for(var type in resp) {
310 for(var idx in resp[type]) {
311 name = resp[type][idx];
313 if(type=="filelist") {
314 if(typeof(name['Title'])!='undefined'&&name["Title"].length) {
315 strip_name = name["Title"];
320 strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1);
324 else if(!name.substring) {
327 else strip_name = name;
329 if(type=="directory") {
330 strip_name = name.substring(strip);
332 else if(type=="file"&&name.lastIndexOf(DIR_SEPARATOR)>=0) {
333 strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1);
336 var l = create_node("li", null, strip_name);
337 l.setAttribute("diritem", name);
338 l.setAttribute("dirtype", rtype);
339 entry.appendChild(l);
341 add_playlist_hover(l);
346 function add_playlist_hover(l) {
347 var h = create_node("div");
348 h.className = "playlist_hover";
349 var img = create_node("img");
350 img.src = IMAGE.BROWSER_PLAYLIST_REMOVE;
351 img.className = "fakelink";
353 add_listener(img, "click", show_remove_playlist);
357 function show_remove_playlist(e) {
358 var t = e.target.parentNode.parentNode;
359 var name = t.getAttribute("diritem");
360 var content = document.createDocumentFragment();
361 content.appendChild(create_txt(LANG.CONFIRM_REMOVE + " '" + name + "'?"));
362 var yes = create_node("span");
363 yes.className = "fakelink";
364 yes.appendChild(create_txt(" Yes,"));
365 content.appendChild(yes);
367 var no = create_node("span");
368 no.className = "fakelink";
369 no.appendChild(create_txt(" No"));
370 content.appendChild(no);
373 destroy_open_popup();
374 var pop = new Popup(document.body, content);
375 blist.open_popup = pop;
376 blist.eventListener = add_listener(document.body, "click", destroy_open_popup);
377 add_listener(yes, "click", function() { remove_playlist(t) ; destroy_open_popup(); });
378 add_listener(no, "click", destroy_open_popup);
379 pop.popup.style.padding = "5px 5px 5px 5px";
380 pop.popup.style.position = "absolute";
381 pop.popup.style.left = "80px";
382 pop.popup.style.top = "500px";
383 pop.popup.style.margin = "0 auto";
387 function destroy_open_popup() {
388 var blist = window.blist; // jic
389 if(blist.open_popup) {
390 blist.open_popup.destroy();
391 blist.open_popup = null;
393 if(blist.eventListener) {
394 blist.eventListener.unregister();
395 blist.eventListener = null;
399 function remove_playlist(elem) {
400 var item = elem.getAttribute("diritem");
401 send_command("playlist_rm=" + encodeURIComponent(item), remove_playlist_cb);
405 function remove_playlist_cb(e) {
407 show_status_bar(LANG.E_FAILED_ADD);
408 hide_status_bar(STATUS_DEFAULT_TIMEOUT);
412 function browser_click(e) {
416 if(!elem||!elem.hasAttribute) return;
418 /* test if we got one of the direct children as targets */
419 if(elem.parentNode&&elem.parentNode.hasAttribute("diritem"))
420 elem = elem.parentNode;
422 if(elem.hasAttribute&&elem.hasAttribute("diritem")) {
424 var container = elem.parentNode;
425 blist.search.focus = container;
426 if(e.ctrlKey||e.metaKey) {
427 if(is_node_selected(elem))
432 container.className = container.className;
434 else if (e.shiftKey) {
435 var sel_node = find_first_selected_node(container, elem);
436 unselect_all_nodes(container);
441 if(elem.hasAttribute("needle"))
442 select_range(elem, sel_node);
444 select_range(sel_node, elem);
446 if(elem.hasAttribute("needle"))
447 elem.removeAttribute("needle");
449 container.className = container.className;
452 browser_open_single_item(container, elem, e.detail==2);
457 function browser_open_single_item(container, elem, doubleclick) {
458 unselect_all_nodes(container);
461 type = elem.getAttribute("dirtype");
464 container.className = container.className;
469 var b_id = blist.get_window_by_value(container);
472 if(type=="directory"||type=="artist"||type=="album") {
473 var diritem = elem.getAttribute("diritem")
475 blist.open = b_id.next;
476 blist.open_name = diritem;
478 /* test if already open */
479 if(blist.open!=null) {
480 // this item is already opened
481 if(blist.open.value.getAttribute("dirlist")==diritem)
485 /* remove all entries in following rows */
488 if(tmp.value.hasAttribute("dirlist"))
489 tmp.value.removeAttribute("dirlist");
490 if(tmp.value.hasAttribute("parent_dir"))
491 tmp.value.removeAttribute("parent_dir");
492 remove_children(tmp.value);
495 if(blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM){
497 if(blist.open == blist.get_last_window()) {
498 if(blist.type==BROWSE_ARTIST) {
499 artist = b_id.value.getAttribute("dirlist");
503 album = b_id.value.getAttribute("dirlist");
506 send_command("searchfile&artist=" + encodeURIComponent(artist) + "&album=" + encodeURIComponent(album), browser_click_cb);
509 else if(blist.open==null) {
513 // else do as usual...
515 send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb);
517 else if(type=="parent"||type=="other") {
518 blist.open = blist.first;
520 blist.open_name = blist.first.value.getAttribute("parent_dir");
521 else blist.open_name = "";
524 if(type=="parent") // || switch type...
525 while(w!=null&&w!=b_id)
528 browser_clear_window(w.value);
532 if(blist.first.value.getAttribute("dirlist")!=""||type=="other") { // already at top
533 send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb);
537 else if(doubleclick&&type!="parent") {
538 var diritem = elem.getAttribute("diritem")
539 blink_node(elem, DEFAULT_BLINK_COLOR);
540 if(elem.getAttribute("dirtype")=="playlist") {
541 send_command("playlist_load=" + encodeURIComponent(diritem), null, LANG.WAIT_ADDING);
543 else if((blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM)&&elem.getAttribute("dirtype")!="file") {
545 if(b_id == blist.first.next) {
546 if(blist.type==BROWSE_ARTIST) {
547 artist = b_id.value.getAttribute("dirlist");
551 album = b_id.value.getAttribute("dirlist");
554 send_command("searchadd&artist=" + encodeURIComponent(artist) + "&album=" +
555 encodeURIComponent(album), browser_add_cb, LANG.WAIT_ADDING);
557 else if(b_id==blist.first) {
558 var cmd = "searchadd&";
559 if(blist.type==BROWSE_ARTIST) {
565 cmd += encodeURIComponent(diritem);
566 send_command(cmd, browser_add_cb, LANG.WAIT_ADDING);
570 // else do as usual...
571 send_command("add=" + encodeURIComponent(diritem), browser_add_cb, LANG.WAIT_ADDING);
576 function browser_click_cb(response) {
578 var parent_dir = null;
579 if(blist.open==null) {
581 var last = blist.get_last_window();
582 var first = blist.first;
583 var prev = blist.get_previous_window(last);
584 parent_dir = prev.value.getAttribute("dirlist");
585 var tmp = first.value;
587 while(tmp.hasChildNodes()) {
588 remove_node(tmp.lastChild);
591 insert_after(tmp, last.value);
593 blist.first = first.next;
595 blist.open = blist.get_last_window();
597 else if (blist.open==blist.first) {
598 parent_dir = blist.first.value.getAttribute("parent_dir");
600 parent_dir = parent_dir.substring(0,parent_dir.lastIndexOf(DIR_SEPARATOR));
602 var first = blist.first;
603 var last = blist.get_last_window();
604 var prev = blist.get_previous_window(last);
605 var tmp = last.value;
606 while(tmp.hasChildNodes())
607 remove_node(tmp.lastChild);
609 insert_before(tmp, first.value)
613 blist.open = blist.first;
616 var prev = blist.get_previous_window(b);
617 if(parent_dir==null&&prev!=null) {
618 parent_dir = prev.value.getAttribute("dirlist");
620 else if(parent_dir==null) {
624 if(blist.open_name.length>0)
625 strip = blist.open_name.length + 1;
627 browser_fill_entry(b.value, response, strip, parent_dir);
628 blist.open_name = "";
632 function browser_add_cb(response) {
634 show_status_bar(LANG.E_FAILED_ADD);
635 hide_status_bar(STATUS_DEFAULT_TIMEOUT);
639 function pl_overlay_open_cb(height) {
644 /* this makes room for the top (filesys, artist, search et al.) and bottom (add etc) */
645 w.value.style.height = (height - 40)+ "px";
648 blist.search.listener = keyboard_register_listener(browser_key_search, KEYBOARD_MATCH_ALL, KEYBOARD_NO_KEY, true);
649 // TODO, maybe set broswer_focus
652 function pl_overlay_close_cb() {
653 keyboard_remove_listener(blist.search.listener);
656 function browser_key_search(e) {
657 if(!blist.search.focus) {
660 var clear_time = 1000;
662 var c = blist.search.focus.childNodes;
664 if(e.keyCode==38||e.keyCode==40||e.keyCode==37||e.keyCode==39) {
666 // find first selected node
667 for(var i=0; i < c.length; i++) {
668 if(is_node_selected(c[i])) {
675 if(e.keyCode==40&&it.nextSibling) {
678 else if(e.keyCode==38&&it.previousSibling) {
679 it = it.previousSibling;
681 else if(e.keyCode==37) { // left
683 //win = blist.get_previous_window(blist.get_window_by_value(blist.search.focus));
686 else if(e.keyCode==39) { // right
688 //win = blist.get_next_window(blist.get_window_by_value(blist.search.focus));
693 unselect_all_nodes(blist.search.focus);
695 scrollIntoView(it, false);
696 blist.search.item = it;
700 blist.search.focus = win;
701 unselect_all_nodes(win);
702 select_node(win.firstChild)
704 blist.search.term = "";
708 var s = blist.search.term + String.fromCharCode(e.keyCode);
709 blist.search.term = s = s.toLowerCase();
711 for(var i=0; i<c.length; i++) {
712 var c_name = get_node_content(c[i]);
713 if(!c_name||!c_name.toLowerCase)
715 c_name = c_name.toLowerCase();
716 if(c_name.indexOf(s)==0) {
717 unselect_all_nodes(blist.search.focus);
719 scrollIntoView(c[i], false);
720 blist.search.item = c[i];
725 clearTimeout(blist.search.timer);
726 blist.search.timer = setTimeout(browser_clear_search_term, clear_time);
728 function browser_clear_window(win) {
729 remove_children(win);
730 if(win.hasAttribute("dirlist"))
731 win.removeAttribute("dirlist");
732 if(win.hasAttribute("parent_dir"))
733 win.removeAttribute("parent_dir");
736 /* clear key-search */
737 function browser_clear_search_term() {
738 blist.search.term = "";
739 if(blist.search.focus&&blist.search.item) {
740 browser_open_single_item(blist.search.focus, blist.search.item);
741 blist.search.item = null;
745 function KeySearch(listener) {
746 this.listener = listener;
752 /* clear search field */
753 function browser_search_field_clear() {
754 blist.search_field.value = "";
755 blist.search_field.disabled = "disabled";
756 blist.search_field.blur();
759 function browser_search_field_enable() {
760 blist.search_field.disabled = "";
761 blist.search_field.focus();
764 function browser_search_field_keyhandler(e) {
765 if(e.keyCode&&e.keyCode==RETURN_KEY_CODE) {
766 if(blist.search_field.value.trim().length>0) {
767 send_command("metasearch=" + blist.search_select.value + "&s=" + encodeURIComponent(blist.search_field.value),
768 browser_search_field_keyhandler_cb, LANG.WAIT_SEARCHING);
771 remove_children(blist.first.value);
776 function browser_search_field_keyhandler_cb(resp) {
777 var dst = blist.first.value;
778 remove_children(dst);
779 if(typeof(resp)!='undefined'&&resp!="failed") {
780 for(var i=0; i<resp.length; i++) {
781 var file = resp[i]["file"];
782 var artist = resp[i]["Artist"];
783 var title = resp[i]["Title"];
785 if(title==null||!title.length) {
786 name = file.substring(file.lastIndexOf(DIR_SEPARATOR)+1);
789 name = artist + " - " + title;
792 var l = add_li(dst, name, dst.id + "_" + i);
793 l.setAttribute("diritem", file);
794 l.setAttribute("dirtype", "file");
795 /* used to match css selector */
796 l.setAttribute("btype", "search");
797 l.appendChild(create_node("br"));
798 var fspan = create_node("span", null, file);
799 l.appendChild(fspan);
804 function browser_act_buttons_display(visible) {
805 var btn = blist.act_buttons.childNodes;
806 for(var i=1; i<btn.length; i++) {
807 btn[i].style.display = (visible?"":"none");
812 function browser_multi_add(e) {
813 var target = e.target;
814 if(!target||!target.hasAttribute("field"))
816 var field = parseInt(target.getAttribute("field"));
817 var container = blist.first;
818 /* find what field we belong to */
819 for(var i=0; i<field&&container; i++) {
820 container = container.next;
823 var add = get_attribute_from_selected_elems(container.value, "diritem");
824 var type = get_attribute_from_selected_elems(container.value, "dirtype");
825 if(add.length<=0||add.length!=type.length) { // add.length must always equal type.length
826 //debug("a: " + add.length + ", t: " + type.length);
830 if(blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM) {
831 if(container==blist.first.next) {
832 if(blist.type==BROWSE_ARTIST)
834 else cmd+="basealbum:";
835 var dirlist = container.value.getAttribute("dirlist");
839 for(var i=0; i<add.length; i++) {
840 if(type[i]=="parent")
847 blink_node(target, DEFAULT_BLINK_COLOR);
848 send_command("ma", browser_multi_add_cb, LANG.WAIT_ADDING, false, cmd);
852 function browser_multi_add_cb(res) {
854 show_status_bar(LANG.E_FAILED_ADD);
855 hide_status_bar(STATUS_DEFAULT_TIMEOUT);