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
This commit is contained in:
Gustavo Sverzut Barbieri 2010-09-24 01:37:54 +00:00
commit c010b3433f
28 changed files with 2575 additions and 0 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Gustavo Sverzut Barbieri <barbieri@profusion.mobi>

165
COPYING Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.

0
ChangeLog Normal file
View File

17
Makefile.am Normal file
View File

@ -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

0
NEWS Normal file
View File

16
README Normal file
View File

@ -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.

97
TODO Normal file
View File

@ -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:
.-------------------------------------.
| <BACK] Albums by 'Artist' |
}-------------------------------------{
| album name1 |
| album name2 |
| album name3 |
| album name4 |
| ... |
| ... |
| ... |
| ... |
| ... |
}-------------------------------------{
| =====V----------------------------- | <- volume
| | | | | |
| <Prev | Next> | 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
| | | | | |
| <Prev | Next> | Play | Mode | More |
| | | | | |
`-------------------------------------'
Media List
----------
* top shows "<Back", $CURRENT_SECTION [and filter field - later]
* all sections should start with "All songs"
* all sections should contain "Shuffle songs"
* root sections: Songs, Artist, Albums, Genres
* filter limits the current section and thus creates a dynamic playlist
Library (all from "more")
-------------------------
* manager: allows add/remove directories
* rescan: allows rescan media immediately
* stop-scan: while scanning, allows cancel the process
More
----
* Preferences:
* choose media backend (xine, gstreamer)
* Bookmarks? (show list of songs with bookmarked positions)
Misc
----
* single instance using dbus
* export mpris control interface (mini mode could be e17 control using it)
* support for e_notify (libnotify)
* read cover from id3? requires id3 image loader in evas
* fetch covers from webservices (amazon, last.fm)
* fetch lyrics from webservices (maybe use webkit)
* fetch track/artist/album info from webservices (wikipedia? uses webkit)
* audio bookmarks

12
autogen.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
DIRNAME=`basename $PWD`
find . -name Makefile -delete
find . -name Makefile.in -delete
autoreconf -f -i
if [ -z "$NOCONFIGURE" ]; then
./configure "$@"
fi

153
configure.ac Normal file
View File

@ -0,0 +1,153 @@
##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##
##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##
m4_define([v_maj], [0])
m4_define([v_min], [1])
m4_define([v_mic], [0])
m4_define([v_rev], m4_esyscmd([(svnversion "${SVN_REPO_PATH:-.}" | grep -v export || echo 0) | awk -F : '{printf("%s\n", $1);}' | tr -d ' :MSP\n']))
m4_if(v_rev, [0], [m4_define([v_rev], m4_esyscmd([git log 2> /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

2
data/Makefile.am Normal file
View File

@ -0,0 +1,2 @@
MAINTAINERCLEANFILES = Makefile.in
SUBDIRS = desktop themes

9
data/desktop/Makefile.am Normal file
View File

@ -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

View File

@ -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

BIN
data/desktop/enjoy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

20
data/themes/Makefile.am Normal file
View File

@ -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

277
data/themes/default.edc Normal file
View File

@ -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;
}
}
}
}
}
}

47
m4/ac_attribute.m4 Normal file
View File

@ -0,0 +1,47 @@
dnl Copyright (C) 2004-2008 Kim Woelders
dnl Copyright (C) 2008 Vincent Torri <vtorri at univ-evry dot fr>
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 <stdlib.h>
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

0
po/LINGUAS Normal file
View File

7
po/Makevars Normal file
View File

@ -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 =

1
po/POTFILES.in Normal file
View File

@ -0,0 +1 @@
src/bin/main.c

2
src/Makefile.am Normal file
View File

@ -0,0 +1,2 @@
MAINTAINERCLEANFILES = Makefile.in
SUBDIRS = bin

42
src/bin/Makefile.am Normal file
View File

@ -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

357
src/bin/db.c Normal file
View File

@ -0,0 +1,357 @@
#include "private.h"
#include <sqlite3.h>
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
*/

272
src/bin/gettext.h Normal file
View File

@ -0,0 +1,272 @@
/* Convenience header for conditional use of GNU <libintl.h>.
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 <locale.h> a NOP. We don't include <libintl.h>
as well because people using "gettext.h" will not include <libintl.h>,
and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
is OK. */
#if defined(__sun)
# include <locale.h>
#endif
/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
<libintl.h>, which chokes if dcgettext is defined as a macro. So include
it now, to make later inclusions of <libintl.h> a NOP. */
#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
# include <cstdlib>
#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 <string.h>
#define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS\
(((__GNUC__ >= 3 || __GNUG__ >= 2) && !__STRICT_ANSI__)\
/* || __STDC_VERSION__ >= 199901L */)
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
#include <stdlib.h>
#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 */

114
src/bin/list.c Normal file
View File

@ -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;
}

122
src/bin/main.c Normal file
View File

@ -0,0 +1,122 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Elementary.h>
#ifndef ELM_LIB_QUICKLAUNCH
#include "private.h"
#include <Ecore_Getopt.h>
#include <Ecore_File.h>
#include <stdlib.h>
#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()

270
src/bin/page.c Normal file
View File

@ -0,0 +1,270 @@
#include "private.h"
#include <ctype.h>
/*
* 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;
}

86
src/bin/private.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef ENJOY_PRIVATE_H
#define ENJOY_PRIVATE_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Elementary.h>
#include <limits.h>
#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

477
src/bin/win.c Normal file
View File

@ -0,0 +1,477 @@
#include "private.h"
#include <Emotion.h>
#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;
}