commit c010b3433f78a2f9a61439eafbcda0c5e19bfa92 Author: Gustavo Sverzut Barbieri Date: Fri Sep 24 01:37:54 2010 +0000 add basic enjoy code to svn. WARNING: there is not much to see here yet! I'm just adding to svn as people seems interested in help, and it is impossible to help without the base code. IF YOU DON'T WANT TO HELP WITH CODE, DON'T EVEN BOTHER COMPILING IT YET! Enjoy is (will be) a music player fully based on Enlightenment Foundation Libraries (EFL), the goal is to have a simple yet useful music player that works on desktop and mobiles with the minimum set of dependencies and maximum efficiency. It is based on LightMediaScanner (my code[1]) to scan directories for music and create a database and does so in a very and safe efficient way by having a scanner process with lower nice priority. It is also smart enough to hint to your kernel you'll not need the scanned files anymore (posix_fadvise()) and thus it will not pollute your RAM with useless file system cache. So far it is not creating the database on its own, neither have a a library manager to add/remove directories. In order to run Enjoy you must first create your own database using "test" program from lightmediascanner sources: {{{ cd lightmediascanner ./configure && make && make install mkdir -p $HOME/.config/enjoy/media.db ./src/bin/test -i 5000 -p id3 -s $HOME/Music $HOME/.config/enjoy/media.db }}} The GUI is pretty much ugly and needs huge work. It is almost fully done in Edje, so C part is quite lean, however I did no Edje work yet, just the basics to show something (uses r | g | b rectangles for actions, you have to guess what's what ;-)) = HOW TO HELP = Read TODO file. If you're more like a coder: * src/bin/db.c follow the propsed stubs and db_songs_get() example; * src/bin/page.c add recursive paging with "folder" pages; * src/bin/win.c start/stop ecore_thread with lms_process() + lms_check(); * src/bin/win.c write library manager: rescan collection (thread as above item), add directories, remove directories. If you're more like an edje guy: * data/themes: follow eve's style, focus on the bottom toolbar, then the list items, then the page/songs. Use dummy icons (copy from eve or efenniht), we'll provide proper icons soon * add nowplaying screen * add volume using dragable (copy from efenniht's slider) [1] download code from http://git.profusion.mobi/cgit.cgi/lightmediascanner.git/ or http://lms.garage.maemo.org/ SVN revision: 52658 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..5ab1470 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Gustavo Sverzut Barbieri diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..9260eac --- /dev/null +++ b/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Enjoyryone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the enjoynt an Application does not supply the + function or data, the facility still operates, and performs + whatenjoyr part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and renjoyrse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License enjoyr published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d4c4e6f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,17 @@ +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src data po + +MAINTAINERCLEANFILES = Makefile.in ABOUT-NLS INSTALL aclocal.m4 config.guess compile \ + config.h.in config.rpath config.sub configure install-sh \ + ltconfig ltmain.sh missing mkinstalldirs \ + m4/lib-link.m4 m4/lib-prefix.m4 m4/lib-ld.m4 m4/lcmessage.m4 m4/libtool.m4 m4/ulonglong.m4 \ + m4/inttypes-pri.m4 m4/progtest.m4 m4/uintmax_t.m4 m4/lt~obsolete.m4 m4/stdint_h.m4 m4/intdiv0.m4 \ + m4/iconv.m4 m4/po.m4 m4/isc-posix.m4 m4/inttypes.m4 m4/ltsugar.m4 m4/glibc21.m4 m4/gettext.m4 m4/ltversion.m4 \ + m4/codeset.m4 m4/inttypes_h.m4 m4/ltoptions.m4 m4/nls.m4 po/Rules-quot po/en@quot.header po/insert-header.sin \ + po/quot.sed po/en@boldquot.header po/boldquot.sed po/Makevars.template po/remove-potcdate.sin po/Makefile.in.in \ + stamp-h.in acconfig.h depcomp + +EXTRA_DIST = README AUTHORS COPYING + +DISTCHECK_CONFIGURE_FLAGS = --disable-quicklaunch diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..421d227 --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +Enjoy - Music Player + +Enjoy is a music player written using Enlightenment Foundation +Libraries (EFL) with focus on speed and mobile usability, yet should +be usable on desktops as well. + +Enjoy is modeled around a media database constructed by +LightMediaScanner, it is not meant to play single files from disk, +instead it will recursively index directories for music files and then +list them by artist, album, genre and so on. Playlists are also +supported, as well as random or filter playlists can be dynamically +generated. + + +See INSTALL for help on how to install. +See COPYING for software usage, modification and redistribution license. diff --git a/TODO b/TODO new file mode 100644 index 0000000..f82f545 --- /dev/null +++ b/TODO @@ -0,0 +1,97 @@ +UI - base ui similar to eve (browser) +------------------------------------- + * top one of: + * list + header, allows going back or selecting new + filter + * cover + info + * bottom action bar (always visible): + * volume + * back + * forward + * play/pause (toggle) + * mini-mode (toggle) + * more: preferences, library manager, plugins? (cover fetch, etc) + + List Mode: + .-------------------------------------. + | | Play | Mode | Prefs | + | | | | | | + `-------------------------------------' + + ^ + | + toggles + | + V + + Play Mode: + .-------------------------------------. + | 01 - My Song | + | By Artist | + | Album Name | + | * * * * * [repeat] [random] | <- rating + toggles: repeat, random + | 02:03 ========V------------- -03:04 | <- progress/seek + }-------------------------------------{ + | | + | | + | | + | | <- cover area + | | + | | + | | + | | + | | + }-------------------------------------{ + | =====V----------------------------- | <- volume + | | | | | | + | | Play | Mode | More | + | | | | | | + `-------------------------------------' + + +Media List +---------- + * top shows " /dev/null | (grep -m1 git-svn-id || echo 0) | sed -e 's/.*@\([0-9]*\).*/\1/' | tr -d '\n']))]) +##-- When released, remove the dnl on the below line +m4_undefine([v_rev]) +##-- When doing snapshots - change soname. remove dnl on below line +dnl m4_define([relname], [ver-pre-svn-07]) +dnl m4_define([v_rel], [-release relname]) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], [m4_define([v_ver], [v_maj.v_min.v_mic.v_rev])], [m4_define([v_ver], [v_maj.v_min.v_mic])]) +m4_define([lt_rev], m4_eval(v_maj + v_min)) +m4_define([lt_cur], v_mic) +m4_define([lt_age], v_min) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## + +AC_INIT([enjoy], [v_ver-alpha], [enlightenment-devel@lists.sourceforge.net]) +AC_PREREQ([2.60]) +AC_CONFIG_SRCDIR([configure.ac]) +AC_CONFIG_MACRO_DIR([m4]) +AC_GNU_SOURCE +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_ISC_POSIX + +AM_INIT_AUTOMAKE(1.6 dist-bzip2) +AM_CONFIG_HEADER(config.h) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +define([AC_LIBTOOL_LANG_CXX_CONFIG], [:]) +define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:]) +define([AC_LIBTOOL_LANG_F77_CONFIG], [:]) +AC_PROG_LIBTOOL + +VMAJ=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $1);}'` +VMIN=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $2);}'` +VMIC=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $3);}'` +SNAP=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $4);}'` +version_info=`expr $VMAJ + $VMIN`":$VMIC:$VMIN" +m4_ifdef([v_rev], , [m4_define([v_rev], [0])]) +AC_DEFINE_UNQUOTED(VREV, [v_rev], [Revison]) +AC_SUBST(VMAJ) +AC_SUBST(version_info) + +AC_C_BIGENDIAN +AC_PROG_CC_C99 +AM_PROG_CC_C_O +AC_C_CONST +AC_FUNC_ALLOCA +AC_C___ATTRIBUTE__ + +ALL_LINGUAS=`cat po/LINGUAS | grep -v '^[ ]*#'` +AC_SUBST(ALL_LINGUAS) + +AM_GNU_GETTEXT_VERSION([0.12.1]) +AM_GNU_GETTEXT([external]) + +PKG_CHECK_MODULES([ELEMENTARY], [elementary ecore-file]) +PKG_CHECK_MODULES([EMOTION], [emotion]) +PKG_CHECK_MODULES([LMS], [lightmediascanner]) +PKG_CHECK_MODULES([SQLITE3], [sqlite3]) + +AC_ARG_WITH([edje-cc], + [AC_HELP_STRING([--with-edje-cc=PATH], + [specify a specific path to edje_cc])], + [edje_cc=$withval; + AC_MSG_NOTICE([edje_cc explicitly set to $edje_cc]) + ],[edje_cc=$(pkg-config --variable=prefix edje)/bin/edje_cc]) +AC_SUBST(edje_cc) + +want_quicklaunch="auto" +AC_ARG_ENABLE([quicklaunch], + [AC_HELP_STRING([--disable-quicklaunch], + [disable build of quicklaunch (default=auto)])], + [if test "x${enableval}" = "xno"; then + want_quicklaunch="no" + elif test "x${enableval}" = "xyes"; then + want_quicklaunch="yes" + else + want_quicklaunch="auto" + fi + ], + [want_quicklaunch="auto"]) + +if test "x${want_quicklaunch}" = "xauto"; then + AC_MSG_CHECKING([checking for elementary_quicklaunch binary...]) + if test -x $(pkg-config --variable=prefix elementary)/bin/elementary_quicklaunch; then + AC_MSG_RESULT([found, enable quicklaunch.]) + want_quicklaunch="yes" + else + AC_MSG_RESULT([not found, disable quicklaunch.]) + want_quicklaunch="no" + fi +fi + +if test "x${want_quicklaunch}" = "xyes"; then + AC_ARG_WITH([quicklauncher-libdir], + [AC_HELP_STRING([--with-quicklauncher-libdir=PATH], + [specify a specific path to install quicklauncher binaries])], + [quicklauncher_libdir=$withval; + AC_MSG_NOTICE([quicklauncher_libdir explicitly set to $quicklauncher_libdir]) + ], + [quicklauncher_libdir=$(pkg-config --variable=libdir elementary)]) + AC_SUBST(quicklauncher_libdir) +fi + +AM_CONDITIONAL(BUILD_QUICKLAUNCH, test "x${want_quicklaunch}" = "xyes") + +AC_OUTPUT([ +Makefile +src/Makefile +src/bin/Makefile +data/Makefile +data/desktop/Makefile +data/themes/Makefile +po/Makefile.in +]) + + +cat << EOF + +enjoy configured with: + +Flags: + CFLAGS.....(C): $CFLAGS + CXXFLAGS.(C++): $CXXFLAGS + CPPFLAGS.(CPP): $CPPFLAGS + LDFLAGS...(LD): $LDFLAGS + +Installation: + PREFIX..............: $prefix + +Quick Launcher: ${want_quicklaunch} +EOF + +if test "x${want_quicklaunch}" = "xyes"; then +cat << EOF_QL + quicklauncher_libdir: $quicklauncher_libdir + +EOF_QL +fi + +cat << EOF2 + +Now type 'make' ('gmake' on some systems) to compile enjoy, if it +builds successfully then you can 'make install', acquiring required +permissions with 'su' or 'sudo'. + +EOF2 diff --git a/data/Makefile.am b/data/Makefile.am new file mode 100644 index 0000000..2408bfe --- /dev/null +++ b/data/Makefile.am @@ -0,0 +1,2 @@ +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = desktop themes diff --git a/data/desktop/Makefile.am b/data/desktop/Makefile.am new file mode 100644 index 0000000..0bbc2ba --- /dev/null +++ b/data/desktop/Makefile.am @@ -0,0 +1,9 @@ +MAINTAINERCLEANFILES = Makefile.in + +desktopdir = $(datadir)/applications +desktop_DATA = enjoy.desktop + +icondir = $(datadir)/icons +icon_DATA = enjoy.png + +EXTRA_DIST = enjoy.desktop enjoy.png diff --git a/data/desktop/enjoy.desktop b/data/desktop/enjoy.desktop new file mode 100644 index 0000000..bae7191 --- /dev/null +++ b/data/desktop/enjoy.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Enjoy +Comment=Music Player +Exec=enjoy +Icon=enjoy +Terminal=false +Type=Application +Categories=Application;Audio;Player diff --git a/data/desktop/enjoy.png b/data/desktop/enjoy.png new file mode 100644 index 0000000..90b57af Binary files /dev/null and b/data/desktop/enjoy.png differ diff --git a/data/themes/Makefile.am b/data/themes/Makefile.am new file mode 100644 index 0000000..ebec5b7 --- /dev/null +++ b/data/themes/Makefile.am @@ -0,0 +1,20 @@ +MAINTAINERCLEANFILES = Makefile.in + +EDJE_CC = @edje_cc@ +EDJE_FLAGS = -v -id $(top_srcdir)/data/themes -fd $(top_srcdir)/data/themes + +filesdir = $(datadir)/$(PACKAGE) +files_DATA = default.edj + +images = +fonts = + +EXTRA_DIST = default.edc $(images) $(fonts) + +default.edj: Makefile $(images) $(fonts) default.edc + $(EDJE_CC) $(EDJE_FLAGS) \ + $(top_srcdir)/data/themes/default.edc \ + $(top_builddir)/data/themes/default.edj + +clean-local: + rm -f default.edj diff --git a/data/themes/default.edc b/data/themes/default.edc new file mode 100644 index 0000000..08fe11f --- /dev/null +++ b/data/themes/default.edc @@ -0,0 +1,277 @@ +collections { + + group { /* + * TODO: make a proper theme, see TODO and src/bin/win.c + * + * Theme should be based on efenniht, see THEMES/efenniht + * and eve/data/theme/default. + * + * - add volume bar + * - add nowplaying mode + * - add mode toggle + * - add "more" button + */ + name: "win"; + min: 480 320; + data.item: "initial_size" "480 800"; + + parts { + part { + name: "bg"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + } + } + + part { + name: "ejy.swallow.list"; + type: SWALLOW; + description { + state: "default" 0.0; + rel1 { + relative: 0.0 0.0; + offset: 0 0; + } + rel2 { + relative: 1.0 1.0; + offset: -1 -101; + } + } + } + +#define TB(_name, start, end, _color) \ + part { \ + name: _name".clipper"; \ + type: RECT; \ + description { \ + state: "default" 0.0; \ + } \ + description { \ + state: "hidden" 0.0; \ + inherit: "default" 0.0; \ + color: 255 255 255 0; \ + visible: 0; \ + } \ + } \ + part { \ + name: _name; \ + type: RECT; \ + clip_to: _name".clipper"; \ + description { \ + state: "default" 0.0; \ + color: _color; \ + rel1 { \ + relative: start 1.0; \ + offset: 0 -100; \ + } \ + rel2 { \ + relative: end 1.0; \ + offset: -1 -1; \ + } \ + } \ + description { \ + state: "disabled" 0.0; \ + inherit: "default" 0.0; \ + color: 128 128 128 255; \ + } \ + } \ + programs { \ + program { \ + name: "ejy,"_name",clicked"; \ + signal: "mouse,clicked,1"; \ + source: _name; \ + action: SIGNAL_EMIT "ejy,"_name",clicked" "ejy"; \ + } \ + program { \ + name: "ejy,"_name",disable"; \ + signal: "ejy,"_name",disable"; \ + source: "ejy"; \ + action: STATE_SET "disabled" 0.0; \ + target: _name; \ + } \ + program { \ + name: "ejy,"_name",enable"; \ + signal: "ejy,"_name",enable"; \ + source: "ejy"; \ + action: STATE_SET "default" 0.0; \ + target: _name; \ + } \ + program { \ + name: "ejy,"_name",hide"; \ + signal: "ejy,"_name",hide"; \ + source: "ejy"; \ + action: STATE_SET "hidden" 0.0; \ + target: _name".clipper"; \ + } \ + program { \ + name: "ejy,"_name",show"; \ + signal: "ejy,"_name",show"; \ + source: "ejy"; \ + action: STATE_SET "default" 0.0; \ + target: _name".clipper"; \ + } \ + } + + TB("prev", 0.0, 0.2, 255 0 0 255); + TB("next", 0.2, 0.4, 0 255 0 255); + TB("action,play", 0.4, 0.6, 0 0 255 255); + TB("action,pause", 0.4, 0.6, 255 0 255 255); + } + } + + group { + name: "page/songs"; + data.item: "homogeneous" "1"; + parts { + part { + name: "title.bg"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 128 128 128 255; + rel1.to: "ejy.text.title"; + rel2.to: "ejy.text.title"; + } + } + + part { + name: "ejy.text.title"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0.0; + rel1 { + relative: 0.0 0.0; + offset: 0 0; + } + rel2 { + relative: 1.0 0.0; + offset: -1 100; + } + text { + font: "Sans"; + size: 10; + text: "title"; + } + } + } + + part { + name: "ejy.swallow.list"; + type: SWALLOW; + description { + state: "default" 0.0; + rel1 { + relative: 0.0 0.0; + offset: 0 100; + } + rel2 { + relative: 1.0 1.0; + offset: -1 -1; + } + } + } + + part { + name: "ejy.swallow.index"; + type: SWALLOW; + description { + state: "default" 0.0; + rel1.to: "ejy.swallow.list"; + rel2.to: "ejy.swallow.list"; + } + } + } + } + + group { name: "elm/genlist/item_compress/song/default"; + alias: "elm/genlist/item_compress_odd/song/default"; + data.item: "labels" "text.title text.album-artist"; + //data.item: "icons" "swallow.cover"; + parts { + part { + name: "events"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 0; + } + } + + part { + name: "text.title"; + type: TEXT; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + color: 0 0 0 255; + rel1 { + relative: 0.0 0.0; + offset: 4 2; + } + rel2 { + relative: 1.0 0.0; + offset: -5 -2; + to_y: "text.album-artist"; + } + text { + font: "Sans:style=Bold"; + size: 12; + min: 0 1; + align: 0.0 0.5; + } + } + } + + part { + name: "text.album-artist"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 128 128 128 255; + align: 0.0 1.0; + rel1 { + relative: 0.0 1.0; + offset: 4 -3; + } + rel2 { + relative: 1.0 1.0; + offset: -5 -3; + } + text { + font: "Sans"; + size: 8; + min: 0 1; + align: 0.0 1.0; + text: "Album"; + } + } + } + + part { + name: "separator"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 128 128 128 255; + rel1 { + relative: 0.0 1.0; + offset: 0 -2; + } + rel2 { + relative: 1.0 1.0; + offset: -1 -1; + } + } + } + } + } +} diff --git a/m4/ac_attribute.m4 b/m4/ac_attribute.m4 new file mode 100644 index 0000000..23479a9 --- /dev/null +++ b/m4/ac_attribute.m4 @@ -0,0 +1,47 @@ +dnl Copyright (C) 2004-2008 Kim Woelders +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. +dnl Originally snatched from somewhere... + +dnl Macro for checking if the compiler supports __attribute__ + +dnl Usage: AC_C___ATTRIBUTE__ +dnl call AC_DEFINE for HAVE___ATTRIBUTE__ and __UNUSED__ +dnl if the compiler supports __attribute__, HAVE___ATTRIBUTE__ is +dnl defined to 1 and __UNUSED__ is defined to __attribute__((unused)) +dnl otherwise, HAVE___ATTRIBUTE__ is not defined and __UNUSED__ is +dnl defined to nothing. + +AC_DEFUN([AC_C___ATTRIBUTE__], +[ + +AC_MSG_CHECKING([for __attribute__]) + +AC_CACHE_VAL([ac_cv___attribute__], + [AC_TRY_COMPILE( + [ +#include + +int func(int x); +int foo(int x __attribute__ ((unused))) +{ + exit(1); +} + ], + [], + [ac_cv___attribute__="yes"], + [ac_cv___attribute__="no"] + )]) + +AC_MSG_RESULT($ac_cv___attribute__) + +if test "x${ac_cv___attribute__}" = "xyes" ; then + AC_DEFINE([HAVE___ATTRIBUTE__], [1], [Define to 1 if your compiler has __attribute__]) + AC_DEFINE([__UNUSED__], [__attribute__((unused))], [Macro declaring a function argument to be unused]) + else + AC_DEFINE([__UNUSED__], [], [Macro declaring a function argument to be unused]) +fi + +]) + +dnl End of ac_attribute.m4 diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..e69de29 diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..4eaaf23 --- /dev/null +++ b/po/Makevars @@ -0,0 +1,7 @@ +DOMAIN = enjoy +subdir = po +top_builddir = .. +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ +COPYRIGHT_HOLDER = Gustavo Sverzut Barbieri +MSGID_BUGS_ADDRESS = barbieri@profusion.mobi +EXTRA_LOCALE_CATEGORIES = diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..91df206 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1 @@ +src/bin/main.c diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..ccfbf1a --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,2 @@ +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = bin diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am new file mode 100644 index 0000000..8ee51fe --- /dev/null +++ b/src/bin/Makefile.am @@ -0,0 +1,42 @@ +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = \ +-I$(top_srcdir) \ +-I$(top_srcdir)/src/bin \ +-DPACKAGE_DATA_DIR=\"$(datadir)/$(PACKAGE)\" \ +-DGETTEXT_PACKAGE=\"$(PACKAGE)\" \ +-DLOCALEDIR=\"$(localedir)\" \ +@ELEMENTARY_CFLAGS@ \ +@EMOTION_CFLAGS@ \ +@LMS_CFLAGS@ \ +@SQLITE3_CFLAGS@ + +bin_PROGRAMS = enjoy +if BUILD_QUICKLAUNCH +bin_PROGRAMS += enjoy_ql +endif + +enjoy_LDADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ +enjoy_SOURCES = main.c win.c db.c list.c page.c + +if BUILD_QUICKLAUNCH +############################################################################ +## Build quick launch binary, needs elementary_quicklaunch to be enabled. ## +## ## +## It is composed of a library with actual code and a binary that talks ## +## to server that will then fork() + dlopen() such library. ## +############################################################################ +enjoy_qldir = $(quicklauncher_libdir) +enjoy_ql_LTLIBRARIES = enjoy_ql.la +enjoy_ql_la_SOURCES = main.c win.c db.c list.c page.c +enjoy_ql_la_LIBADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ +enjoy_ql_la_CFLAGS = +enjoy_ql_la_LDFLAGS = -module -avoid-version -no-undefined +enjoy_ql_SOURCES = main.c +enjoy_ql_LDADD = @ELEMENTARY_LIBS@ +enjoy_ql_CFLAGS = -DELM_LIB_QUICKLAUNCH=1 +enjoy_ql_LDFLAGS = +endif + +noinst_HEADERS = gettext.h private.h +EXTRA_DIST = gettext.h private.h diff --git a/src/bin/db.c b/src/bin/db.c new file mode 100644 index 0000000..3f51890 --- /dev/null +++ b/src/bin/db.c @@ -0,0 +1,357 @@ +#include "private.h" +#include + +struct _DB +{ + const char *path; + sqlite3 *handle; + struct { + /* just pre-compile most used and complex operations, no need to do so + * for simple update statements like "set song playcnt". + */ + sqlite3_stmt *songs_get; + } stmt; +}; + +static Eina_Bool +_db_stmt_reset(sqlite3_stmt *stmt) +{ + Eina_Bool r, c; + + r = sqlite3_reset(stmt) == SQLITE_OK; + if (!r) + ERR("could not reset SQL statement"); + + c = sqlite3_clear_bindings(stmt) == SQLITE_OK; + if (!c) + ERR("could not clear SQL"); + + return r && c; +} + +static Eina_Bool +_db_stmt_finalize(sqlite3_stmt *stmt, const char *name) +{ + int r = sqlite3_finalize(stmt); + if (r != SQLITE_OK) + ERR("could not finalize %s statement: #%d\n", name, r); + return r == SQLITE_OK; +} + +static sqlite3_stmt * +_db_stmt_compile(DB *db, const char *name, const char *sql) +{ + sqlite3_stmt *stmt = NULL; + + if (sqlite3_prepare_v2(db->handle, sql, -1, &stmt, NULL) != SQLITE_OK) + ERR("could not prepare %s sql=\"%s\": %s", + name, sql, sqlite3_errmsg(db->handle)); + + return stmt; +} + +static Eina_Bool +_db_stmts_compile(DB *db) +{ +#define C(m, sql) \ + do \ + { \ + db->stmt.m = _db_stmt_compile(db, stringify(m), sql); \ + if (!db->stmt.m) return EINA_FALSE; \ + } \ + while (0) + + C(songs_get, + "SELECT files.id, files.path, files.size, " + " audios.title, audios.album_id, audios.artist_id, audios.genre_id, " + " audios.trackno, audios.rating, audios.playcnt, audios.length, " + " audio_albums.name, audio_artists.name, audio_genres.name " + "FROM audios, files, audio_albums, audio_artists, audio_genres " + "WHERE " + " files.id = audios.id AND " + " audio_albums.id = audios.album_id AND " + " audio_artists.id = audios.artist_id AND " + " audio_genres.id = audios.genre_id " + "ORDER BY UPPER(audios.title)"); + +#undef C + return EINA_TRUE; +} + +static Eina_Bool +_db_stmts_finalize(DB *db) +{ + Eina_Bool ret = EINA_TRUE; + +#define F(m) \ + ret &= _db_stmt_finalize(db->stmt.m, stringify(m)); + + F(songs_get); + +#undef F + return ret; +} + +DB * +db_open(const char *path) +{ + DB *db; + + EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL); + db = calloc(1, sizeof(DB)); + EINA_SAFETY_ON_NULL_RETURN_VAL(db, NULL); + + db->path = eina_stringshare_add(path); + + if (sqlite3_open(path, &db->handle) != SQLITE_OK) + { + CRITICAL("Could not open database '%s'", db->path); + goto error; + } + + if (!_db_stmts_compile(db)) + { + CRITICAL("Could not compile statements."); + goto error; + } + + return db; + + error: + _db_stmts_finalize(db); + sqlite3_close(db->handle); + eina_stringshare_del(db->path); + free(db); + return NULL; +} + +Eina_Bool +db_close(DB *db) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(db, EINA_FALSE); + _db_stmts_finalize(db); + sqlite3_close(db->handle); + eina_stringshare_del(db->path); + free(db); + return EINA_TRUE; +} + +Song *db_song_copy(const Song *orig) +{ + // TODO: move to mempool to avoid fragmentation! + Song *copy; + EINA_SAFETY_ON_NULL_RETURN_VAL(orig, NULL); + copy = malloc(sizeof(Song)); + EINA_SAFETY_ON_NULL_RETURN_VAL(copy, NULL); + + /* Note: cannot use eina_stringshare_ref() as value may be from sqlite + * during iterator walks. + */ +#define STR(m) \ + copy->m = eina_stringshare_add(orig->m); \ + copy->len.m = orig->len.m + STR(path); + STR(title); + STR(album); + STR(artist); +#undef STR + +#define N(m) copy->m = orig->m + N(id); + N(album_id); + N(artist_id); + N(genre_id); + N(size); + N(trackno); + N(rating); + N(playcnt); + N(length); +#undef N + + return copy; +} + +void +db_song_free(Song *song) +{ + eina_stringshare_del(song->path); + eina_stringshare_del(song->title); + eina_stringshare_del(song->album); + eina_stringshare_del(song->artist); + free(song); +} + +struct DB_Iterator +{ + Eina_Iterator base; + DB *db; + sqlite3_stmt *stmt; +}; + +struct DB_Iterator_Songs +{ + struct DB_Iterator base; + Song song; +}; + +static void * +_db_iterator_container_get(Eina_Iterator *iterator) +{ + struct DB_Iterator *it = (struct DB_Iterator *)iterator; + if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR)) + { + EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR); + return NULL; + } + return it->db; +} + +static void +_db_iterator_free(Eina_Iterator *iterator) +{ + struct DB_Iterator *it = (struct DB_Iterator *)iterator; + if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR)) + { + EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR); + return; + } + _db_stmt_reset(it->stmt); + EINA_MAGIC_SET(&it->base, EINA_MAGIC_NONE); + free(it); +} + +static Eina_Bool +_db_iterator_songs_next(Eina_Iterator *iterator, void **data) +{ + struct DB_Iterator_Songs *it = (struct DB_Iterator_Songs *)iterator; + Song **song = (Song **)data; + int r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(song, EINA_FALSE); + *song = NULL; + if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR)) + { + EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR); + return EINA_FALSE; + } + + r = sqlite3_step(it->base.stmt); + if (r == SQLITE_DONE) + return EINA_FALSE; + if (r != SQLITE_ROW) + { + ERR("Error executing sql statement: %s", + sqlite3_errmsg(it->base.db->handle)); + return EINA_FALSE; + } + +#define ID(m, c) \ + it->song.m = sqlite3_column_int64(it->base.stmt, c); +#define INT(m, c) \ + it->song.m = sqlite3_column_int(it->base.stmt, c); +#define STR(m, c) \ + it->song.m = (const char *)sqlite3_column_text(it->base.stmt, c); \ + it->song.len.m = sqlite3_column_bytes(it->base.stmt, c) + + ID(id, 0); + STR(path, 1); + INT(size, 2); + STR(title, 3); + ID(album_id, 4); + ID(artist_id, 5); + ID(genre_id, 6); + INT(trackno, 7); + INT(rating, 8); + INT(playcnt, 9); + INT(length, 10); + STR(album, 11); + STR(artist, 12); + STR(genre, 13); + +#undef STR +#undef INT +#undef ID + + *song = &it->song; + + return EINA_TRUE; +} + +Eina_Iterator * +db_songs_get(DB *db) +{ + struct DB_Iterator_Songs *it; + EINA_SAFETY_ON_NULL_RETURN_VAL(db, NULL); + it = calloc(1, sizeof(*it)); + EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL); + + it->base.base.version = EINA_ITERATOR_VERSION; + it->base.base.next = _db_iterator_songs_next; + it->base.base.get_container = _db_iterator_container_get; + it->base.base.free = _db_iterator_free; + it->base.db = db; + it->base.stmt = db->stmt.songs_get; + + EINA_MAGIC_SET(&it->base.base, EINA_MAGIC_ITERATOR); + return &it->base.base; +} + +Eina_Bool +db_song_rating_set(DB *db, Song *song, int rating) +{ + DBG("TODO"); + return EINA_TRUE; +} + +Eina_Bool +db_song_length_set(DB *db, Song *song, int length) +{ + DBG("TODO"); + return EINA_TRUE; +} + + + +/* + +TODO: filters: + +// filter: free form string from user +// keep all searches with filter, not used now +// in future split on whitespace and search all terms in all fields (ie: even when listing artists, if term is Metallica, artist name would match and just "heavy metal" term will be shown +// possibly we'll take specifiers, like artist:metallica +// filters are case insensitive! + + +Eina_Bool db_path_del(DB *db, const char *path); // delete from ... where url like '$path/%', used to remove directories from media collection + +// iterators return temporary struct and values straight from sqlite (= zero copy), use functions to copy when required: +Song *song_copy(const Song *); +Album *album_copy(const Album *); +Artist *artist_copy(const Artist *); + +// use mempool + stringshare +void song_free(Song *); +void album_free(Album *); +void artist_free(Artist *); + +Song *db_song_get(DB *db, id); // ret song +Eina_Bool db_song_rating_set(DB *db, Song *song, int rating); + +Eina_Iterator *db_songs_get(DB *db); // ret song +Eina_Iterator *db_albums_get(DB *db); // ret albums (name, id) +Eina_Iterator *db_artists_get(DB *db); // ret artist (name, id) +Eina_Iterator *db_genres_get(DB *db); // ret genres (name, id) + +Eina_Iterator *db_album_songs_get(DB *db, id); // ret all songs from album id + +Eina_Iterator *db_artist_songs_get(DB *db, id); // ret all songs from artist id +Eina_Iterator *db_artist_albums_get(DB *db, id); // ret all albums (name, id) from artist id +Eina_Iterator *db_artist_album_songs_get(DB *db, id_artist, id_album); // ret all songs (name, id) from album from artist + +Eina_Iterator *db_genre_songs_get(DB *db, id); // ret all songs from genre id +Eina_Iterator *db_genre_artists_get(DB *db, id); // ret all artists (name, id) from genre id +Eina_Iterator *db_genre_artist_albums_get(DB *db, id_genre, id_artist); // ret all albums from artist from genre +Eina_Iterator *db_genre_artist_album_songs_get(DB *db, id_genre, id_artist, id_album); // ret all songs from albums from artist from genre + +*/ diff --git a/src/bin/gettext.h b/src/bin/gettext.h new file mode 100644 index 0000000..8a90d49 --- /dev/null +++ b/src/bin/gettext.h @@ -0,0 +1,272 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without enjoyn the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +#define _(x) gettext(x) +#define N_(x) gettext_noop(x) + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by + the gettext() and ngettext() macros. This is an alternative to calling + textdomain(), and is useful for libraries. */ +# ifdef DEFAULT_TEXT_DOMAIN +# undef gettext +# define gettext(Msgid)\ + dgettext (DEFAULT_TEXT_DOMAIN, Msgid) +# undef ngettext +# define ngettext(Msgid1, Msgid2, N)\ + dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) +# endif + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Many header files from the libstdc++ coming with g++ 3.3 or newer include + , which chokes if dcgettext is defined as a macro. So include + it now, to make later inclusions of a NOP. */ +#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *)(Msgid)) +# define dgettext(Domainname, Msgid) ((void)(Domainname), gettext (Msgid)) +# define dcgettext(Domainname, Msgid, Category)\ + ((void)(Category), dgettext (Domainname, Msgid)) +# define ngettext(Msgid1, Msgid2, N)\ + ((N) == 1\ + ? ((void)(Msgid2), (const char *)(Msgid1))\ + : ((void)(Msgid1), (const char *)(Msgid2))) +# define dngettext(Domainname, Msgid1, Msgid2, N)\ + ((void)(Domainname), ngettext (Msgid1, Msgid2, N)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category)\ + ((void)(Category), dngettext(Domainname, Msgid1, Msgid2, N)) +# define textdomain(Domainname) ((const char *)(Domainname)) +# define bindtextdomain(Domainname, Dirname)\ + ((void)(Domainname), (const char *)(Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset)\ + ((void)(Domainname), (const char *)(Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* The separator between msgctxt and msgid in a .mo file. */ +#define GETTEXT_CONTEXT_GLUE "\004" + +/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a + MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be + short and rarely need to change. + The letter 'p' stands for 'particular' or 'special'. */ +#ifdef DEFAULT_TEXT_DOMAIN +# define pgettext(Msgctxt, Msgid)\ + pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#else +# define pgettext(Msgctxt, Msgid)\ + pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#endif +#define dpgettext(Domainname, Msgctxt, Msgid)\ + pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#define dcpgettext(Domainname, Msgctxt, Msgid, Category)\ + pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category) +#ifdef DEFAULT_TEXT_DOMAIN +# define npgettext(Msgctxt, Msgid, MsgidPlural, N)\ + npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#else +# define npgettext(Msgctxt, Msgid, MsgidPlural, N)\ + npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#endif +#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N)\ + npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category)\ + npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +pgettext_aux (const char *domain, + const char *msg_ctxt_id, const char *msgid, + int category) +{ + const char *translation = dcgettext (domain, msg_ctxt_id, category); + if (translation == msg_ctxt_id) + return msgid; + else + return translation; +} + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +npgettext_aux (const char *domain, + const char *msg_ctxt_id, const char *msgid, + const char *msgid_plural, unsigned long int n, + int category) +{ + const char *translation = + dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); + if (translation == msg_ctxt_id || translation == msgid_plural) + return (n == 1 ? msgid : msgid_plural); + else + return translation; +} + +/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID + can be arbitrary expressions. But for string literals these macros are + less efficient than those above. */ + +#include + +#define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS\ + (((__GNUC__ >= 3 || __GNUG__ >= 2) && !__STRICT_ANSI__)\ + /* || __STDC_VERSION__ >= 199901L */) + +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS +#include +#endif + +#define pgettext_expr(Msgctxt, Msgid)\ + dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES) +#define dpgettext_expr(Domainname, Msgctxt, Msgid)\ + dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +dcpgettext_expr (const char *domain, + const char *msgctxt, const char *msgid, + int category) +{ + size_t msgctxt_len = strlen (msgctxt) + 1; + size_t msgid_len = strlen (msgid) + 1; + const char *translation; +#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + char msg_ctxt_id[msgctxt_len + msgid_len]; +#else + char buf[1024]; + char *msg_ctxt_id = + (msgctxt_len + msgid_len <= sizeof (buf) + ? buf + : (char *)malloc (msgctxt_len + msgid_len)); + if (msg_ctxt_id) +#endif + { + memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); + msg_ctxt_id[msgctxt_len - 1] = '\004'; + memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); + translation = dcgettext (domain, msg_ctxt_id, category); +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + if (msg_ctxt_id != buf) + free (msg_ctxt_id); + +#endif + if (translation != msg_ctxt_id) + return translation; + } + + return msgid; +} + +#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N)\ + dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) +#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N)\ + dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +dcnpgettext_expr (const char *domain, + const char *msgctxt, const char *msgid, + const char *msgid_plural, unsigned long int n, + int category) +{ + size_t msgctxt_len = strlen (msgctxt) + 1; + size_t msgid_len = strlen (msgid) + 1; + const char *translation; +#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + char msg_ctxt_id[msgctxt_len + msgid_len]; +#else + char buf[1024]; + char *msg_ctxt_id = + (msgctxt_len + msgid_len <= sizeof (buf) + ? buf + : (char *)malloc (msgctxt_len + msgid_len)); + if (msg_ctxt_id) +#endif + { + memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); + msg_ctxt_id[msgctxt_len - 1] = '\004'; + memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); + translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + if (msg_ctxt_id != buf) + free (msg_ctxt_id); + +#endif + if (!(translation == msg_ctxt_id || translation == msgid_plural)) + return translation; + } + + return (n == 1 ? msgid : msgid_plural); +} + +#endif /* _LIBGETTEXT_H */ diff --git a/src/bin/list.c b/src/bin/list.c new file mode 100644 index 0000000..6d7fb8f --- /dev/null +++ b/src/bin/list.c @@ -0,0 +1,114 @@ +#include "private.h" + +typedef struct _List +{ + DB *db; + Evas_Object *pager; + struct { + Eina_List *list; + Evas_Object *current; + Evas_Object *songs; + } page; +} List; + +static List _list; + +#define LIST_GET_OR_RETURN(list, obj, ...) \ + List *list = evas_object_data_get(obj, "_enjoy_list"); \ + do { if (!list) return __VA_ARGS__; } while (0) + + +static void +_list_page_song(void *data, Evas_Object *o __UNUSED__, void *event_info) +{ + List *list = data; + Song *song = event_info; + evas_object_smart_callback_call(list->pager, "selected", song); +} + +static void +_list_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__) +{ + List *list = data; + eina_list_free(list->page.list); +} + +Evas_Object * +list_add(Evas_Object *parent) +{ + List *list = &_list; + + memset(list, 0, sizeof(list)); + + list->pager = elm_pager_add(parent); + if (!list->pager) return NULL; + + evas_object_data_set(list->pager, "_enjoy_list", list); + evas_object_event_callback_add + (list->pager, EVAS_CALLBACK_DEL, _list_del, list); + + return list->pager; +} + +Eina_Bool +list_populate(Evas_Object *obj, DB *db) +{ + LIST_GET_OR_RETURN(list, obj, EINA_FALSE); + Evas_Object *page; + + EINA_SAFETY_ON_NULL_RETURN_VAL(list, EINA_FALSE); + EINA_LIST_FREE(list->page.list, page) evas_object_del(page); + list->page.current = list->page.songs = NULL; + list->db = db; + if (!db) return EINA_TRUE; + + // TODO: create fake root pages here + page = list->page.current = list->page.songs = page_songs_add + (obj, db_songs_get(db), "All Songs"); + if (!page) return EINA_FALSE; + evas_object_smart_callback_add(page, "song", _list_page_song, list); + list->page.list = eina_list_append(list->page.list, page); + elm_pager_content_push(list->pager, page); + + return EINA_TRUE; +} + +Song * +list_selected_get(const Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, NULL); + if (list->page.songs) return page_songs_selected_get(list->page.songs); + return NULL; +} + +Eina_Bool +list_next_exists(const Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, EINA_FALSE); + if (list->page.songs) return page_songs_next_exists(list->page.songs); + return EINA_FALSE; +} + +Song * +list_next_go(Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, NULL); + if (list->page.songs) return page_songs_next_go(list->page.songs); + return NULL; +} + +Eina_Bool +list_prev_exists(const Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, EINA_FALSE); + if (list->page.songs) return page_songs_prev_exists(list->page.songs); + return EINA_FALSE; +} + +Song * +list_prev_go(Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, NULL); + if (list->page.songs) return page_songs_prev_go(list->page.songs); + return NULL; +} diff --git a/src/bin/main.c b/src/bin/main.c new file mode 100644 index 0000000..08baca2 --- /dev/null +++ b/src/bin/main.c @@ -0,0 +1,122 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#ifndef ELM_LIB_QUICKLAUNCH + +#include "private.h" + +#include +#include +#include +#include "gettext.h" + +int _log_domain = -1; +static App app; + +static const Ecore_Getopt options = { + PACKAGE_NAME, + "%prog [options] [url]", + PACKAGE_VERSION "Revision:" stringify(VREV), + "(C) 2010 ProFUSION embedded systems", + "LGPL-3", + "Music player for mobiles and desktops.", + EINA_TRUE, + { + ECORE_GETOPT_APPEND_METAVAR + ('a', "add", "Add (recursively) directory to music library.", + "DIRECTORY", ECORE_GETOPT_TYPE_STR), + ECORE_GETOPT_APPEND_METAVAR + ('d', "del", "Delete (recursively) directory from music library.", + "DIRECTORY", ECORE_GETOPT_TYPE_STR), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_SENTINEL + } +}; + +EAPI int +elm_main(int argc, char **argv) +{ + int r = 0, args; + Eina_Bool quit_option = EINA_FALSE; + const char *home; + char *s; + + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_LIST(app.add_dirs), + ECORE_GETOPT_VALUE_LIST(app.del_dirs), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_NONE + }; + +#if ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); +#endif + + _log_domain = eina_log_domain_register("enjoy", NULL); + if (_log_domain < 0) + { + EINA_LOG_CRIT("could not create log domain 'enjoy'."); + return -1; + } + + args = ecore_getopt_parse(&options, values, argc, argv); + if (args < 0) + { + ERR("Could not parse command line options."); + return -1; + } + + if (quit_option) + { + DBG("Command lines option requires quit."); + return 0; + } + + elm_theme_extension_add(NULL, PACKAGE_DATA_DIR "/default.edj"); + + home = getenv("HOME"); + if (!home || !home[0]) + { + CRITICAL("Could not get $HOME"); + r = -1; + goto end; + } + + snprintf(app.configdir, sizeof(app.configdir), "%s/.config/enjoy", home); + if (!ecore_file_mkpath(app.configdir)) + { + ERR("Could not create %s", app.configdir); + r = -1; + goto end; + } + + app.win = win_new(&app); + if (!app.win) goto end; + + elm_run(); + + evas_object_del(app.win); + + end: + EINA_LIST_FREE(app.add_dirs, s) free(s); + EINA_LIST_FREE(app.del_dirs, s) free(s); + + eina_log_domain_unregister(_log_domain); + _log_domain = -1; + elm_shutdown(); + + return r; +} + +#endif +ELM_MAIN() diff --git a/src/bin/page.c b/src/bin/page.c new file mode 100644 index 0000000..9c1c2ff --- /dev/null +++ b/src/bin/page.c @@ -0,0 +1,270 @@ +#include "private.h" + +#include + +/* + * TODO: + * - create page_folder that contains something other than songs, click one + * should create a new page, stack it into the parent list and recurse. + * - add suffle action for page_songs + */ + +/* number of songs to populate at once before going back to mainloop */ +#define PAGE_SONGS_POPULATE_ITERATION_COUNT (128) + +typedef struct _Page +{ + Evas_Object *layout; + Evas_Object *edje; + Evas_Object *list; + Evas_Object *index; + const char *title; + Elm_Genlist_Item *selected; + Elm_Genlist_Item *first; + Eina_Iterator *iterator; + Ecore_Idler *populate; + char last_index_letter[2]; +} Page; + +#define PAGE_SONGS_GET_OR_RETURN(page, obj, ...) \ + Page *page = evas_object_data_get(obj, "_enjoy_page_songs"); \ + do { if (!page) return __VA_ARGS__; } while (0) + + +static char * +_song_item_label_get(void *data, Evas_Object *list __UNUSED__, const char *part) +{ + Song *song = data; + + if (!strcmp(part, "text.title")) + return strdup(song->title); + else if (!strcmp(part, "text.album-artist")) + { + char *str; + if ((!song->album) && (!song->artist)) return NULL; + else if (!song->album) return strdup(song->artist); + else if (!song->artist) return strdup(song->album); + + if (asprintf(&str, "%s - %s", song->album, song->artist) > 0) + return str; + return NULL; + } + else if (!strcmp(part, "text.album")) + return song->album ? strdup(song->album) : NULL; + else if (!strcmp(part, "text.artist")) + return song->artist ? strdup(song->artist) : NULL; + + return NULL; +} + +static void +_song_item_del(void *data, Evas_Object *list __UNUSED__) +{ + db_song_free(data); +} + +static const Elm_Genlist_Item_Class _song_item_class = { + "song", + { + _song_item_label_get, + NULL, + NULL, + _song_item_del + } +}; + +static Eina_Bool +_page_songs_populate(void *data) +{ + Page *page = data; + const Song *orig; + Song *copy; + unsigned int count; + + for (count = 0; count < PAGE_SONGS_POPULATE_ITERATION_COUNT; count++) + { + Elm_Genlist_Item *it; + char letter; + + if (!eina_iterator_next(page->iterator, (void **)&orig)) goto end; + // TODO: evaluate if we should keep a full copy or just store + // fields of interest such as id, title, artist and album + copy = db_song_copy(orig); + if (!copy) goto end; + + it = elm_genlist_item_append + (page->list, &_song_item_class, copy, + NULL, ELM_GENLIST_ITEM_NONE, NULL, NULL); + + letter = toupper(copy->title[0]); + if (isalpha(letter) && (page->last_index_letter[0] != letter)) + { + if ((page->first) && (!page->last_index_letter[0])) + elm_index_item_append(page->index, "Special", page->first); + + page->last_index_letter[0] = letter; + elm_index_item_append(page->index, page->last_index_letter, it); + } + if (!page->first) page->first = it; + } + + return EINA_TRUE; + + end: + page->populate = NULL; + return EINA_FALSE; +} + +static void +_page_songs_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__) +{ + Page *page = data; + eina_stringshare_del(page->title); + if (page->iterator) eina_iterator_free(page->iterator); + if (page->populate) ecore_idler_del(page->populate); + free(page); +} + +static void +_page_songs_selected(void *data, Evas_Object *o __UNUSED__, void *event_info) +{ + Page *page = data; + Elm_Genlist_Item *it = event_info; + Song *song = elm_genlist_item_data_get(it); + EINA_SAFETY_ON_NULL_RETURN(song); + if (page->selected == it) return; + page->selected = it; + evas_object_smart_callback_call(page->layout, "song", song); +} + +static void +_page_songs_index_changed(void *data __UNUSED__, Evas_Object *o __UNUSED__, void *event_info) +{ + Elm_Genlist_Item *it = event_info; + elm_genlist_item_top_bring_in(it); +} + +Evas_Object * +page_songs_add(Evas_Object *parent, Eina_Iterator *it, const char *title) +{ + Evas_Object *obj; + Page *page; + const char *s; + + EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL); + + obj = elm_layout_add(parent); + if (!obj) + { + eina_iterator_free(it); + return NULL; + } + + page = calloc(1, sizeof(*page)); + if (!page) + { + CRITICAL("could not allocate page memory!"); + eina_iterator_free(it); + goto error; + } + evas_object_data_set(obj, "_enjoy_page_songs", page); + evas_object_event_callback_add + (obj, EVAS_CALLBACK_DEL, _page_songs_del, page); + page->layout = obj; + page->iterator = it; + + if (!elm_layout_file_set(obj, PACKAGE_DATA_DIR "/default.edj", "page/songs")) + { + CRITICAL("no theme for 'page/songs' at %s", + PACKAGE_DATA_DIR "/default.edj"); + goto error; + } + + page->title = eina_stringshare_add(title); + page->edje = elm_layout_edje_get(obj); + edje_object_part_text_set(page->edje, "ejy.text.title", page->title); + + page->list = elm_genlist_add(obj); + elm_genlist_bounce_set(page->list, EINA_FALSE, EINA_TRUE); + elm_genlist_horizontal_mode_set(page->list, ELM_LIST_COMPRESS); + elm_genlist_compress_mode_set(page->list, EINA_TRUE); + elm_genlist_block_count_set(page->list, 1024); + + evas_object_smart_callback_add + (page->list, "selected", _page_songs_selected, page); + + s = edje_object_data_get(page->edje, "homogeneous"); + elm_genlist_homogeneous_set(page->list, s ? !!atoi(s) : EINA_FALSE); + printf("genlist homogeneous: %d\n", elm_genlist_homogeneous_get(page->list)); + + elm_layout_content_set(obj, "ejy.swallow.list", page->list); + + page->index = elm_index_add(obj); + evas_object_smart_callback_add + (page->index, "delay,changed", _page_songs_index_changed, page); + elm_layout_content_set(obj, "ejy.swallow.index", page->index); + + page->first = NULL; + page->populate = ecore_idler_add(_page_songs_populate, page); + + return obj; + + error: + evas_object_del(obj); /* should delete everything */ + return NULL; +} + +Song * +page_songs_selected_get(const Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, NULL); + return page->selected ? elm_genlist_item_data_get(page->selected) : NULL; +} + +Eina_Bool +page_songs_next_exists(const Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, EINA_FALSE); + Elm_Genlist_Item *it = page->selected; + if (!it) return EINA_FALSE; + it = elm_genlist_item_next_get(it); + return !!it; +} + +Song * +page_songs_next_go(Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, NULL); + Elm_Genlist_Item *it = page->selected; + Song *song; + if (!it) return NULL; + it = elm_genlist_item_next_get(it); + song = elm_genlist_item_data_get(it); + page->selected = it; + elm_genlist_item_selected_set(it, EINA_TRUE); + return song; +} + +Eina_Bool +page_songs_prev_exists(const Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, EINA_FALSE); + Elm_Genlist_Item *it = page->selected; + if (!it) return EINA_FALSE; + it = elm_genlist_item_prev_get(it); + return !!it; +} + +Song * +page_songs_prev_go(Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, NULL); + Elm_Genlist_Item *it = page->selected; + Song *song; + if (!it) return NULL; + it = elm_genlist_item_prev_get(it); + song = elm_genlist_item_data_get(it); + page->selected = it; + elm_genlist_item_selected_set(it, EINA_TRUE); + return song; +} diff --git a/src/bin/private.h b/src/bin/private.h new file mode 100644 index 0000000..54174d8 --- /dev/null +++ b/src/bin/private.h @@ -0,0 +1,86 @@ +#ifndef ENJOY_PRIVATE_H +#define ENJOY_PRIVATE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#define stringify(X) #X + +typedef struct _App App; +typedef struct _Song Song; +typedef struct _DB DB; + +extern int _log_domain; + +#define CRITICAL(...) EINA_LOG_DOM_CRIT(_log_domain, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__) + +struct _App +{ + Eina_List *add_dirs; + Eina_List *del_dirs; + char configdir[PATH_MAX]; + Evas_Object *win; +}; + +Evas_Object *win_new(App *app); + +Evas_Object *list_add(Evas_Object *parent); +Eina_Bool list_populate(Evas_Object *list, DB *db); +Song *list_selected_get(const Evas_Object *list); +Eina_Bool list_next_exists(const Evas_Object *list); +Song *list_next_go(Evas_Object *list); +Eina_Bool list_prev_exists(const Evas_Object *list); +Song *list_prev_go(Evas_Object *list); + + +Evas_Object *page_songs_add(Evas_Object *parent, Eina_Iterator *it, const char *title); +Song *page_songs_selected_get(const Evas_Object *obj); +Eina_Bool page_songs_next_exists(const Evas_Object *obj); +Song *page_songs_next_go(Evas_Object *obj); +Eina_Bool page_songs_prev_exists(const Evas_Object *obj); +Song *page_songs_prev_go(Evas_Object *obj); + + +DB *db_open(const char *path); +Eina_Bool db_close(DB *db); + +struct _Song +{ + const char *path; + const char *title; + const char *album; + const char *artist; + const char *genre; + int64_t id; + int64_t album_id; + int64_t artist_id; + int64_t genre_id; + int size; /* file size in bytes */ + int trackno; + int rating; + int playcnt; + int length; + struct { + unsigned int path; + unsigned int title; + unsigned int album; + unsigned int artist; + unsigned int genre; + } len; /* strlen of string fields */ +}; + +Eina_Iterator *db_songs_get(DB *db); /* walks over 'const Song*' */ +Song *db_song_copy(const Song *orig); +void db_song_free(Song *song); +Eina_Bool db_song_rating_set(DB *db, Song *song, int rating); +Eina_Bool db_song_length_set(DB *db, Song *song, int length); + +#endif diff --git a/src/bin/win.c b/src/bin/win.c new file mode 100644 index 0000000..dcbd502 --- /dev/null +++ b/src/bin/win.c @@ -0,0 +1,477 @@ +#include "private.h" +#include + +#ifndef ENGINE +/* todo: move to preferences */ +#define ENGINE "xine" +#endif + +#define MSG_VOLUME 1 +#define MSG_POSITION 2 +#define MSG_RATING 3 + +typedef struct Win +{ + Evas_Object *win; + Evas_Object *layout; + Evas_Object *edje; + Evas_Object *emotion; + Evas_Object *list; + const char *db_path; + DB *db; + Song *song; + struct { + double position, length; + double volume; + Eina_Bool playing:1; + } play; + struct { + Evas_Coord w, h; + } min; + struct { + Eina_List *add, *del; + } scan; + struct { + Ecore_Job *scan; + Ecore_Job *populate; + } job; + struct { + Ecore_Thread *scan; + } thread; +} Win; + +static Win _win; + +static void +_win_populate_job(void *data) +{ + Win *w = data; + w->job.populate = NULL; + + if (w->db) db_close(w->db); + w->db = db_open(w->db_path); + if (!w->db) + { + CRITICAL("no database at %s!", w->db_path); + // TODO: remove me! create library manager and start it from here + printf("SCAN and LIBRARY MANAGER are not implemeted yet!\n" + "Meanwhile please run " + "(./test = binary from lightmediascanner/src/bin):\n" + " ./test -p id3 -i 5000 -s %s/Music %s\n" + "sorry about the inconvenience!\n", + getenv("HOME"), w->db_path); + exit(-1); + } + list_populate(w->list, w->db); +} + +static void +_win_scan_job(void *data) +{ + Win *w = data; + w->job.scan = NULL; + DBG("TODO"); + printf("SCAN IS NOT IMPLEMENTED!\n" + "Meanwhile please run " + "(./test = binary from lightmediascanner/src/bin):\n"); + + Eina_List *l; + const char *path; + EINA_LIST_FOREACH(w->scan.add, l, path) + printf(" ./test -p id3 -i 5000 -s %s %s\n", + path, w->db_path); + EINA_LIST_FOREACH(w->scan.del, l, path) + printf(" sqlite3 %s \"delete from files where path like '%s%%'\"\n", + w->db_path, path); + + // notify win (should stop lists from updating) + // create lms + // w->thread.scan = ecore_thread_...: create thread + // emit delete as sqlite statements + // start lms process + check from thread + // finish thread -> unmark it from Win + // notify win (should reload lists) + + if (!w->job.populate) + w->job.populate = ecore_job_add(_win_populate_job, w); +} + +static void +_win_toolbar_eval(Win *w) +{ + printf("eval toolbar: prev=%hhu, next=%hhu\n", list_prev_exists(w->list), list_next_exists(w->list)); + if (list_prev_exists(w->list)) + edje_object_signal_emit(w->edje, "ejy,prev,enable", "ejy"); + else + edje_object_signal_emit(w->edje, "ejy,prev,disable", "ejy"); + + if (list_next_exists(w->list)) + edje_object_signal_emit(w->edje, "ejy,next,enable", "ejy"); + else + edje_object_signal_emit(w->edje, "ejy,next,disable", "ejy"); + + if (w->song) + { + edje_object_signal_emit(w->edje, "ejy,action,play,enable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,pause,enable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,list,enable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,enable", "ejy"); + } + else + { + edje_object_signal_emit(w->edje, "ejy,action,play,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,pause,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,list,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,disable", "ejy"); + } +} + +static void +_win_play_eval(Win *w) +{ + Edje_Message_Float_Set *mf; + + mf = alloca(sizeof(Edje_Message_Float_Set) + sizeof(double)); + mf->count = 2; + mf->val[0] = w->play.position; + mf->val[1] = w->play.length; + edje_object_message_send(w->edje, EDJE_MESSAGE_FLOAT_SET, MSG_POSITION, mf); + + if (w->play.playing) + { + edje_object_signal_emit(w->edje, "ejy,action,play,hide", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,pause,show", "ejy"); + } + else + { + edje_object_signal_emit(w->edje, "ejy,action,pause,hide", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,play,show", "ejy"); + } +} + +static void +_win_song_set(Win *w, Song *s) +{ + Edje_Message_Int mi; + char str[32]; + + w->play.position = 0.0; + w->play.length = 0.0; + w->song = s; + if (!s) goto end; + + if (s->trackno > 0) + snprintf(str, sizeof(str), "%d", s->trackno); + else + str[0] = '\0'; + + edje_object_part_text_set(w->edje, "ejy.text.trackno", str); + edje_object_part_text_set(w->edje, "ejy.text.title", s->title); + edje_object_part_text_set(w->edje, "ejy.text.album", s->album); + edje_object_part_text_set(w->edje, "ejy.text.artist", s->artist); + edje_object_part_text_set(w->edje, "ejy.text.genre", s->genre); + + mi.val = s->rating; + edje_object_message_send(w->edje, EDJE_MESSAGE_INT, MSG_RATING, &mi); + + emotion_object_file_set(w->emotion, s->path); + // TODO: emotion_object_audio_volume_set(w->emotion, w->play.volume); + w->play.playing = EINA_TRUE; + emotion_object_play_set(w->emotion, EINA_TRUE); + + end: + _win_play_eval(w); + _win_toolbar_eval(w); +} + +static void +_win_play_pos_update(void *data, Evas_Object *o __UNUSED__, void *event_info __UNUSED__) +{ + Win *w = data; + _win_play_eval(w); +} + +static void +_win_play_end(void *data, Evas_Object *o __UNUSED__, void *event_info __UNUSED__) +{ + Win *w = data; + Song *s = list_next_go(w->list); + _win_song_set(w, s); +} + +static void +_win_list_selected(void *data, Evas_Object *list __UNUSED__, void *event_info) +{ + Win *w = data; + Song *s = event_info; + _win_song_set(w, s); +} + +static void +_win_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__) +{ + Win *w = data; + if (w->emotion) evas_object_del(w->emotion); + if (w->job.scan) ecore_job_del(w->job.scan); + if (w->job.populate) ecore_job_del(w->job.populate); + if (w->thread.scan) ecore_thread_cancel(w->thread.scan); + if (w->db_path) eina_stringshare_del(w->db_path); +} + +static void +_win_prev(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + Song *s = list_prev_go(w->list); + INF("prev song=%p (%s)", s, s ? s->path : NULL); + if (s) _win_song_set(w, s); +} + +static void +_win_next(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + Song *s = list_next_go(w->list); + INF("next song=%p (%s)", s, s ? s->path : NULL); + if (s) _win_song_set(w, s); +} + +static void +_win_action_play(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + INF("play song=%p (%s)", w->song, w->song ? w->song->path : NULL); + w->play.playing = EINA_TRUE; + emotion_object_play_set(w->emotion, EINA_TRUE); + _win_play_eval(w); +} + +static void +_win_action_pause(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + INF("pause song=%p (%s)", w->song, w->song ? w->song->path : NULL); + w->play.playing = EINA_FALSE; + emotion_object_play_set(w->emotion, EINA_FALSE); + _win_play_eval(w); +} + +static void +_win_mode_list(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + edje_object_signal_emit(w->edje, "ejy,mode,list,hide", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,show", "ejy"); +} + +static void +_win_mode_nowplaying(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,hide", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,list,show", "ejy"); +} + +static void +_win_more(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__) +{ + Win *w = data; + DBG("todo"); +} + +//#define EDJE_SIGNAL_DEBUG 1 +#ifdef EDJE_SIGNAL_DEBUG +static void +_edje_signal_debug(void *data __UNUSED__, Evas_Object *o __UNUSED__, const char *emission, const char *source) +{ + DBG("emission=%s, source=%s", emission, source); +} +#endif + +static void +_win_edje_msg(void *data, Evas_Object *o __UNUSED__, Edje_Message_Type type, int id, void *msg) +{ + Win *w = data; + + switch (id) + { + case MSG_VOLUME: + if (type != EDJE_MESSAGE_FLOAT) + ERR("message for volume got type %d instead of %d", + type, EDJE_MESSAGE_FLOAT); + else + { + Edje_Message_Float *m = msg; + w->play.volume = m->val; + emotion_object_audio_volume_set(w->emotion, w->play.volume); + } + break; + + case MSG_POSITION: + if (type != EDJE_MESSAGE_FLOAT) + ERR("message for position/seek got type %d instead of %d", + type, EDJE_MESSAGE_FLOAT); + else + { + Edje_Message_Float *m = msg; + w->play.position = m->val; + emotion_object_position_set(w->emotion, w->play.position); + } + break; + + case MSG_RATING: + if (type != EDJE_MESSAGE_INT) + ERR("message for rating got type %d instead of %d", + type, EDJE_MESSAGE_INT); + else + { + Edje_Message_Int *m = msg; + if (!w->song) + ERR("setting rating without song?"); + else + db_song_rating_set(w->db, w->song, m->val); + } + break; + + default: + ERR("unknown edje message id: %d of type: %d", id, type); + } +} + + +Evas_Object * +win_new(App *app) +{ + Win *w = &_win; + const char *s; + Evas_Coord iw = 320, ih = 240; + char path[PATH_MAX]; + + memset(w, 0, sizeof(*w)); + + w->win = elm_win_add(NULL, PACKAGE_NAME, ELM_WIN_BASIC); + if (!w->win) return NULL; + evas_object_data_set(w->win, "_enjoy", &w); + evas_object_event_callback_add(w->win, EVAS_CALLBACK_DEL, _win_del, w); + + elm_win_autodel_set(w->win, 1); // TODO + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + + snprintf(path, sizeof(path), "%s/media.db", app->configdir); + w->db_path = eina_stringshare_add(path); + + w->emotion = emotion_object_add(evas_object_evas_get(w->win)); + if (!emotion_object_init(w->emotion, ENGINE)) + { + CRITICAL("cannot create emotion engine %s", ENGINE); + goto error; + } + emotion_object_video_mute_set(w->emotion, EINA_TRUE); + evas_object_show(w->emotion); // req? + evas_object_resize(w->emotion, 10, 10); // req? + + evas_object_smart_callback_add + (w->emotion, "position_update", _win_play_pos_update, w); + evas_object_smart_callback_add + (w->emotion, "length_change", _win_play_pos_update, w); + evas_object_smart_callback_add + (w->emotion, "decode_stop", _win_play_end, w); + + w->layout = elm_layout_add(w->win); + if (!w->layout) goto error; + evas_object_size_hint_weight_set + (w->layout, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set + (w->layout, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_win_resize_object_add(w->win, w->layout); + + if (!elm_layout_file_set(w->layout, PACKAGE_DATA_DIR "/default.edj", "win")) + { + CRITICAL("no theme for 'win' at %s", PACKAGE_DATA_DIR "/default.edj"); + goto error; + } + + w->list = list_add(w->layout); + if (!w->list) + { + CRITICAL("cannot create list"); + goto error; + } + elm_layout_content_set(w->layout, "ejy.swallow.list", w->list); + evas_object_smart_callback_add(w->list, "selected", _win_list_selected, w); + + w->edje = elm_layout_edje_get(w->layout); + edje_object_size_min_get(w->edje, &(w->min.w), &(w->min.h)); + edje_object_size_min_restricted_calc + (w->edje, &(w->min.w), &(w->min.h), w->min.w, w->min.h); + + s = edje_object_data_get(w->edje, "initial_size"); + if (!s) + WRN("no initial size specified."); + else + { + if (sscanf(s, "%d %d", &iw, &ih) != 2) + { + ERR("invalid initial_size format %s.", s); + iw = 320; + ih = 240; + } + } + s = edje_object_data_get(w->edje, "alpha"); + if (s) elm_win_alpha_set(w->win, !!atoi(s)); + s = edje_object_data_get(w->edje, "borderless"); + if (s) elm_win_borderless_set(w->win, !!atoi(s)); + +#ifdef EDJE_SIGNAL_DEBUG + edje_object_signal_callback_add(w->edje, "*", "*", _edje_signal_debug, w); +#endif + + edje_object_signal_callback_add + (w->edje, "ejy,prev,clicked", "ejy", _win_prev, w); + edje_object_signal_callback_add + (w->edje, "ejy,next,clicked", "ejy", _win_next, w); + edje_object_signal_callback_add + (w->edje, "ejy,action,play,clicked", "ejy", _win_action_play, w); + edje_object_signal_callback_add + (w->edje, "ejy,action,pause,clicked", "ejy", _win_action_pause, w); + edje_object_signal_callback_add + (w->edje, "ejy,mode,list,clicked", "ejy", _win_mode_list, w); + edje_object_signal_callback_add + (w->edje, "ejy,mode,nowplaying,clicked", "ejy", _win_mode_nowplaying, w); + edje_object_signal_callback_add + (w->edje, "ejy,more,clicked", "ejy", _win_more, w); + edje_object_message_handler_set(w->edje, _win_edje_msg, w); + + edje_object_signal_emit(w->edje, "ejy,prev,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,next,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,pause,hide", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,play,show", "ejy"); + edje_object_signal_emit(w->edje, "ejy,action,play,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,show", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,list,show", "ejy"); + edje_object_signal_emit(w->edje, "ejy,mode,list,disable", "ejy"); + edje_object_signal_emit(w->edje, "ejy,more,disable", "ejy"); + + evas_object_show(w->layout); + + printf("initial size: %dx%d\n", iw, ih); + evas_object_resize(w->win, iw, ih); + evas_object_size_hint_min_set(w->win, w->min.w, w->min.h); + elm_win_title_set(w->win, PACKAGE_STRING); + evas_object_show(w->win); + + if ((app->add_dirs) || (app->del_dirs)) + { + w->scan.add = app->add_dirs; + w->scan.del = app->del_dirs; + w->job.scan = ecore_job_add(_win_scan_job, w); + } + else + w->job.populate = ecore_job_add(_win_populate_job, w); + + return w->win; + + error: + evas_object_del(w->win); /* should delete everything */ + return NULL; +}