diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e507588 --- /dev/null +++ b/COPYING @@ -0,0 +1,25 @@ +Copyright notice for Enlightenment: + +Copyright (C) 2022-2023 Carsten Haitzler and various contributors (see AUTHORS) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3b772b --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# EFM + +## Remake of Enlightenment's file manager + +This redose EFM so it can be used in and out-of-process too in E. This +moves the FS layer entirely to a child process that EFM talks to over +stdio. The only direct file access is via local "cached" files in +$HOME that are intended to always be fast access (the location could +move somewhere - but this is the only sane way). diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b22d63e --- /dev/null +++ b/TODO.md @@ -0,0 +1,81 @@ +# Things To Do + +## Now + +* View + * List detailed mode + * Free x/y position + * Vertical icon view + * Free x/y cleanup/grid align + * Icon with no labels + * Icons with flush view (like rage video browser view) + * .dir.desktop support for changing view type and looks etc. etc. +* DND auto-open dir on hover-over +* File properties dialog +* File Ops + * mv + * cp + * rm + * "Job" tracking + management (cancel pending ones) +* Trashcan impl +* Progress feedback from file ops +* Display dir usage (# files, size) +* Display dir + subdir usage +* Each file/dir needs a busy status (eg while running du -s in a dir tree) +* In-fm + * Status - icon overlay label, icon, progress, busy spinner + * Status - progress/busy spinner + label + icon + * Dialog - label + icon + entry + buttons (ok/cancel etc.) +* Typebuf select/filter/cmd +* Choose fs abstraction dir setup etc. +* Live icons - desktop file runs binary with plug/socket efl app +* Filesystem info (df like with total available) +* List mode ".order" files +* Remember scroll pos, view size/pos +* Set window icon correctly for dir +* Save x,y per icon +* Set custom icon per file +* Special icons for special filenames/paths (~/Desktop, ~/Videos etc.) +* Favorites view move (manual .order changes, cb's for selecting single click) +* Single click/select mode +* Hide/show hidden files +* Immutable dirs (no moving files/changes - just browse/launch) +* Dir info pane + * Pane + * Table of label + icons + * List of items (label + 2 icons) +* Device monitoring/listing +* Device mount/unmount +* Custom target vfs dir (eg sshfs) controls +* Find filename in tree +* Show dir has no permission to view/go into (eg missing r/x on other/group) +* Right click context menu +* Open file with mime handler +* Open with ... +* File actions (separate to open with...) + +## Medium term + +* Edit desktop file support (dialog) +* Network fs monitoring/listing (smb/nfs/etc.) +* View background / overlay images/edj files +* Typebuf commands (ls, rm, cd, mv, ...) +* Special drop handling per dir (eg favorites adds links) +* Support running back-end cmds as another user +* Chmod support +* Thumbnail multiple pages from pdf, ps etc. +* Thumbnails multiple timepoints from video files +* Tooltip previews + * Multi-page show multiple pages + * Video files show multiple timepoints + +## Long term + +* Encrypted drives/volumes +* Partitioning and formatting tool (chnage volume labels etc) +* Encryption added to partitioning/formatitng tool +* Lvm support in partitioning/formatting tool +* Fstrim support +* Smart support for devices +* Badblocks support +* Lsattr/chattr support diff --git a/data/checkme b/data/checkme new file mode 100644 index 0000000..e69de29 diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..f0b240b --- /dev/null +++ b/data/meson.build @@ -0,0 +1,3 @@ +dir = join_paths(dir_data, 'efm') +install_data('checkme', + install_dir: dir) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..518db99 --- /dev/null +++ b/meson.build @@ -0,0 +1,37 @@ +project('efm', 'c', version: '0.0.0', license: 'GPL2', + default_options: [ 'buildtype=plain', 'c_std=gnu99' ], + meson_version: '>= 0.47.0') +proj = meson.project_name() +ver = meson.project_version() +base_url = 'https://www.enlightenment.org/about-' +cc = meson.get_compiler('c') +m_dep = cc.find_library('m', required : false) +deps = [ + dependency('elementary', version: '>= 1.26.0'), + dependency('emotion', version: '>= 1.26.0'), + m_dep +] +dir_prefix = get_option('prefix') +dir_bin = join_paths(dir_prefix, get_option('bindir')) +dir_lib = join_paths(dir_prefix, get_option('libdir')) +dir_data = join_paths(dir_prefix, get_option('datadir')) +dir_locale = join_paths(dir_prefix, get_option('localedir')) +cfg = configuration_data() +cfg.set_quoted('PACKAGE' , proj) +cfg.set_quoted('PACKAGE_NAME' , proj) +cfg.set_quoted('PACKAGE_VERSION' , ver) +cfg.set_quoted('PACKAGE_STRING' , proj + ' ' + ver) +cfg.set_quoted('PACKAGE_URL' , base_url + proj) +cfg.set_quoted('PACKAGE_BIN_DIR' , dir_bin) +cfg.set_quoted('PACKAGE_LIB_DIR' , dir_lib) +cfg.set_quoted('PACKAGE_DATA_DIR' , join_paths(dir_data, proj)) +cfg.set_quoted('LOCALEDIR' , dir_locale) +cfg.set ('_GNU_SOURCE' , 1) +cfg.set ('__EXTENSIONS__' , 1) +cfg.set ('_POSIX_PTHREAD_SEMANTICS', 1) +cfg.set ('_ALL_SOURCE' , 1) +cfg.set ('_POSIX_SOURCE' , 1) +cfg.set ('_POSIX_1_SOURCE' , 1) +configure_file(output: 'efm_config.h', configuration: cfg) +subdir('src') +subdir('data') diff --git a/sample-open-stdio.txt b/sample-open-stdio.txt new file mode 100644 index 0000000..b6799c3 --- /dev/null +++ b/sample-open-stdio.txt @@ -0,0 +1,65 @@ +CMD dir-set path=/home/raster/C/efm2 +CMD file-add path=/home/raster/C/efm2/DSC00943.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/53/f957ab4d5460f00628a587556e35629caa0e53.eet label=DSC00943.JPG user=raster group=raster mode=1ed inode=18749257 nlink=1 gid=1000 size=26574848 blksize=4096 blocks=51904 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/efm type=file label=efm user=raster group=raster mode=1ed inode=23724617 nlink=1 gid=1000 size=537192 blksize=4096 blocks=1056 atime=1648769404 mtime=1648769404 ctime=1648769404 +CMD file-add path=/home/raster/C/efm2/DSC00932.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/6e/cb788e0e6e03c5bcc98dcfbde5ea7449ced476.eet label=DSC00932.JPG user=raster group=raster mode=1ed inode=18749251 nlink=1 gid=1000 size=18448384 blksize=4096 blocks=36032 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/cmd.c\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=cmd.c\x7e user=raster group=raster mode=1a4 inode=23729908 nlink=1 gid=1000 size=6155 blksize=4096 blocks=16 atime=1648747518 mtime=1648747518 ctime=1648747544 +CMD file-add path=/home/raster/C/efm2/dir1 type=dir label=dir1 user=raster group=raster mode=1ed inode=23732294 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1644011527 mtime=1644011527 ctime=1644011527 +CMD file-add path=/home/raster/C/efm2/cmd.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=cmd.h user=raster group=raster mode=1a4 inode=23729901 nlink=1 gid=1000 size=980 blksize=4096 blocks=8 atime=1648747535 mtime=1648747535 ctime=1648747535 +CMD file-add path=/home/raster/C/efm2/build.sh type=file mime=application/x-shellscript mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-shellscript.svg label=build.sh user=raster group=raster mode=1ed inode=23724920 nlink=1 gid=1000 size=323 blksize=4096 blocks=8 atime=1646442369 mtime=1646442369 ctime=1646442369 +CMD file-add path=/home/raster/C/efm2/mplayer.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=MPlayer desktop-generic-name=true desktop-comment=Watch\x20movies\x20and\x20videos desktop-icon=video_player desktop-try-exec= desktop-exec=mplayer\x20\x25U desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup=/usr/local/share/enlightenment/data/icons/video_player.png user=raster group=raster mode=1a4 inode=23724916 nlink=1 gid=1000 size=2404 blksize=4096 blocks=8 atime=1645890199 mtime=1645890199 ctime=1645890199 +CMD file-add path=/home/raster/C/efm2/DSC00945.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/14/c9fbb4214ab4b8a622025509fdd38c8fa276b8.eet label=DSC00945.JPG user=raster group=raster mode=1ed inode=18749262 nlink=1 gid=1000 size=26116096 blksize=4096 blocks=51008 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/sha.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=sha.c user=raster group=raster mode=1a4 inode=23727830 nlink=1 gid=1000 size=3663 blksize=4096 blocks=8 atime=1645965283 mtime=1645965283 ctime=1645965283 +CMD file-add path=/home/raster/C/efm2/dir2 type=dir label=dir2 user=raster group=raster mode=1ed inode=23732493 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1644011530 mtime=1644011530 ctime=1644011530 +CMD file-add path=/home/raster/C/efm2/main.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=main.c user=raster group=raster mode=1a4 inode=23729913 nlink=1 gid=1000 size=3752 blksize=4096 blocks=8 atime=1648762748 mtime=1648762748 ctime=1648762748 +CMD file-add path=/home/raster/C/efm2/DSC00946.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/99/641dd8d01cb0f212f4295cea418fe4637835b5.eet label=DSC00946.JPG user=raster group=raster mode=1ed inode=18749264 nlink=1 gid=1000 size=23396352 blksize=4096 blocks=45696 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/blah type=link link=/home/raster/ link-type=dir link-label=blah link-user=raster link-group=raster link-mode=1ed link-inode=18219010 link-nlink=116 link-gid=1000 link-size=12288 link-blksize=4096 link-blocks=24 link-atime=1586903708 link-mtime=1648890971 link-ctime=1648890971 user=raster group=raster mode=1ff inode=23724046 nlink=1 gid=1000 size=13 blksize=4096 blocks=0 atime=1643917174 mtime=1643906454 ctime=1643917174 +CMD file-add path=/home/raster/C/efm2/yyb type=dir label=yyb user=raster group=raster mode=1ed inode=23732491 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1643917174 mtime=1643909696 ctime=1643917174 +CMD file-add path=/home/raster/C/efm2/efm_structs.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=efm_structs.h user=raster group=raster mode=1a4 inode=23726366 nlink=1 gid=1000 size=3546 blksize=4096 blocks=8 atime=1648764496 mtime=1648764496 ctime=1648764496 +CMD file-add path=/home/raster/C/efm2/xxa type=file label=xxa user=raster group=raster mode=1a4 inode=18770119 nlink=1 gid=1000 size=0 blksize=4096 blocks=0 atime=1643917175 mtime=1643908180 ctime=1645954000 +CMD file-add path=/home/raster/C/efm2/xxb type=file label=xxb user=raster group=raster mode=1a4 inode=18770120 nlink=1 gid=1000 size=0 blksize=4096 blocks=0 atime=1643917175 mtime=1643908180 ctime=1645954000 +CMD file-add path=/home/raster/C/efm2/yyc type=dir label=yyc user=raster group=raster mode=1ed inode=23732492 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1643917174 mtime=1643909696 ctime=1643917174 +CMD file-add path=/home/raster/C/efm2/Sam_Perry_-_Prince_-_When_doves_cry.mkv type=file mime=video/x-matroska mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/video-x-matroska.svg thumb=/home/raster/.e/e/thumbs/c5/bd5935e1f3f395d53a576e48aec7e39110a2ec.eet label=Sam_Perry_-_Prince_-_When_doves_cry.mkv user=raster group=raster mode=1a4 inode=23724895 nlink=1 gid=1000 size=46915391 blksize=4096 blocks=91640 atime=1646086494 mtime=1646086494 ctime=1647031857 +CMD file-add path=/home/raster/C/efm2/xxc type=file label=xxc user=raster group=raster mode=1a4 inode=18770121 nlink=1 gid=1000 size=0 blksize=4096 blocks=0 atime=1643917175 mtime=1643908180 ctime=1645954000 +CMD file-add path=/home/raster/C/efm2/efm_private.h\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=efm_private.h\x7e user=raster group=raster mode=1a4 inode=23729902 nlink=1 gid=1000 size=1193 blksize=4096 blocks=8 atime=1648752425 mtime=1648752425 ctime=1648762883 +CMD file-add path=/home/raster/C/efm2/efm_util.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=efm_util.c user=raster group=raster mode=1a4 inode=23729811 nlink=1 gid=1000 size=31886 blksize=4096 blocks=64 atime=1648769288 mtime=1648769288 ctime=1648769288 +CMD file-add path=/home/raster/C/efm2/efm_icon.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=efm_icon.c user=raster group=raster mode=1a4 inode=23730949 nlink=1 gid=1000 size=11078 blksize=4096 blocks=24 atime=1648209986 mtime=1648209986 ctime=1648209986 +CMD file-add path=/home/raster/C/efm2/DSC00929.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/3f/4a8b9b2c79314c676db1ea0d29136058d5aaab.eet label=DSC00929.JPG user=raster group=raster mode=1ed inode=18749248 nlink=1 gid=1000 size=14516224 blksize=4096 blocks=28352 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/efm_back_end.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=efm_back_end.c user=raster group=raster mode=1a4 inode=18746021 nlink=1 gid=1000 size=15235 blksize=4096 blocks=32 atime=1647210250 mtime=1647206422 ctime=1647210250 +CMD file-add path=/home/raster/C/efm2/sort.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=sort.h user=raster group=raster mode=1a4 inode=23730766 nlink=1 gid=1000 size=538 blksize=4096 blocks=8 atime=1643992534 mtime=1643992534 ctime=1643992534 +CMD file-add path=/home/raster/C/efm2/thumb.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=thumb.c user=raster group=raster mode=1a4 inode=23724123 nlink=1 gid=1000 size=7007 blksize=4096 blocks=16 atime=1646085700 mtime=1646085700 ctime=1646085700 +CMD file-add path=/home/raster/C/efm2/efm_private.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=efm_private.h user=raster group=raster mode=1a4 inode=23728355 nlink=1 gid=1000 size=1213 blksize=4096 blocks=8 atime=1648762883 mtime=1648762883 ctime=1648762883 +CMD file-add path=/home/raster/C/efm2/cmd.h\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=cmd.h\x7e user=raster group=raster mode=1a4 inode=23724625 nlink=1 gid=1000 size=974 blksize=4096 blocks=8 atime=1648747530 mtime=1648747530 ctime=1648747535 +CMD file-add path=/home/raster/C/efm2/fs_backend_core.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=fs_backend_core.c user=raster group=raster mode=1a4 inode=23724929 nlink=1 gid=1000 size=2629 blksize=4096 blocks=8 atime=1646248242 mtime=1646248242 ctime=1646248242 +CMD file-add path=/home/raster/C/efm2/.hidden1 type=dir label=.hidden1 user=raster group=raster mode=1ed inode=24512350 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1646596578 mtime=1646596578 ctime=1646596578 +CMD file-add path=/home/raster/C/efm2/efm.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=efm.h user=raster group=raster mode=1a4 inode=23730930 nlink=1 gid=1000 size=1079 blksize=4096 blocks=8 atime=1647632307 mtime=1647632307 ctime=1647632307 +CMD file-add path=/home/raster/C/efm2/ark.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=ARK:\x20Normal desktop-generic-name=false desktop-comment=Play\x20this\x20game\x20on\x20Steam desktop-icon=/home/raster/.icons/ark.png desktop-try-exec= desktop-exec=steam\x20steam://rungameid/346110 desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup= label-clicked=Ark:\x20Clicked label-selected=Ark:\x20Selected desktop-icon-clicked=/home/raster/.icons/wechat.png desktop-icon-selected=/home/raster/.icons/sweethome3d.png user=raster group=raster mode=1a4 inode=23724926 nlink=1 gid=1000 size=319 blksize=4096 blocks=8 atime=1647026562 mtime=1647026562 ctime=1647026562 +CMD file-add path=/home/raster/C/efm2/tig.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=Tiggy\x20SVG desktop-generic-name=false desktop-comment=Play\x20this\x20game\x20on\x20Steam desktop-icon=/home/raster/C/efm2/tiger.svg desktop-try-exec= desktop-exec=steam\x20steam://rungameid/346110 desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup= user=raster group=raster mode=1a4 inode=23727828 nlink=1 gid=1000 size=166 blksize=4096 blocks=8 atime=1646596358 mtime=1646596358 ctime=1646596358 +CMD file-add path=/home/raster/C/efm2/DSC00931.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/cd/fb485ec9e20bad55b5b6f76d3e8976eb438399.eet label=DSC00931.JPG user=raster group=raster mode=1ed inode=18749250 nlink=1 gid=1000 size=14024704 blksize=4096 blocks=27392 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/dir3 type=dir label=dir3 user=raster group=raster mode=1ed inode=23732494 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1644011532 mtime=1644011532 ctime=1644011532 +CMD file-add path=/home/raster/C/efm2/xxd type=file label=xxd user=raster group=raster mode=1a4 inode=18770149 nlink=1 gid=1000 size=0 blksize=4096 blocks=0 atime=1643917175 mtime=1643908180 ctime=1645954000 +CMD file-add path=/home/raster/C/efm2/efm.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=efm.c user=raster group=raster mode=1a4 inode=23729823 nlink=1 gid=1000 size=28335 blksize=4096 blocks=56 atime=1648769134 mtime=1648769134 ctime=1648769134 +CMD file-add path=/home/raster/C/efm2/DSC00928.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/aa/bfc32b248723656c48a31d446999fd3f8913f9.eet label=DSC00928.JPG user=raster group=raster mode=1ed inode=18745989 nlink=1 gid=1000 size=22773760 blksize=4096 blocks=44480 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/cmd.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=cmd.c user=raster group=raster mode=1a4 inode=23729897 nlink=1 gid=1000 size=6161 blksize=4096 blocks=16 atime=1648747544 mtime=1648747544 ctime=1648747544 +CMD file-add path=/home/raster/C/efm2/efm.c\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=efm.c\x7e user=raster group=raster mode=1a4 inode=23724064 nlink=1 gid=1000 size=28357 blksize=4096 blocks=56 atime=1648768644 mtime=1648768644 ctime=1648769134 +CMD file-add path=/home/raster/C/efm2/DSC00941.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/14/020bf44c8ddce48364d5ede7e9ee74e378f70a.eet label=DSC00941.JPG user=raster group=raster mode=1ed inode=18749252 nlink=1 gid=1000 size=14647296 blksize=4096 blocks=28608 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/beast.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=BEEAAST desktop-generic-name=false desktop-comment=Da\x20Biiieest desktop-icon=/home/raster/C/efm2/beast-art.jpg desktop-try-exec= desktop-exec=xterm desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup= user=raster group=raster mode=1a4 inode=23724921 nlink=1 gid=1000 size=122 blksize=4096 blocks=8 atime=1645890552 mtime=1645890552 ctime=1645890552 +CMD file-add path=/home/raster/C/efm2/sort.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=sort.c user=raster group=raster mode=1a4 inode=23729895 nlink=1 gid=1000 size=2789 blksize=4096 blocks=8 atime=1644017056 mtime=1644017056 ctime=1644017056 +CMD file-add path=/home/raster/C/efm2/home.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=Home desktop-generic-name=false desktop-comment=The\x20Directory\x20containing\x20all\x20your\x20personal\x20files desktop-icon=user-home desktop-try-exec= desktop-exec= desktop-url=file:\x24HOME desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/places/scalable/user-home.svg user=raster group=raster mode=1a4 inode=18770054 nlink=1 gid=1000 size=765 blksize=4096 blocks=8 atime=1643917175 mtime=1643906431 ctime=1643917175 +CMD file-add path=/home/raster/C/efm2/beast-art.jpg type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/9f/0fdd2433a4c6914e3685f6f203b0cab4342702.eet label=beast-art.jpg user=raster group=raster mode=1a4 inode=23724917 nlink=1 gid=1000 size=557108 blksize=4096 blocks=1096 atime=1645960418 mtime=1645960418 ctime=1645960418 +CMD file-add path=/home/raster/C/efm2/main.c\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=main.c\x7e user=raster group=raster mode=1a4 inode=18746023 nlink=1 gid=1000 size=3230 blksize=4096 blocks=8 atime=1647133165 mtime=1647120355 ctime=1648762748 +CMD file-add path=/home/raster/C/efm2/DSC00930.JPG type=file mime=image/jpeg mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-jpeg.svg thumb=/home/raster/.e/e/thumbs/2a/e2b0646970fa380c2641db83d0bb64e10973db.eet label=DSC00930.JPG user=raster group=raster mode=1ed inode=18749249 nlink=1 gid=1000 size=19038208 blksize=4096 blocks=37184 atime=1646005748 mtime=1645991014 ctime=1646005748 +CMD file-add path=/home/raster/C/efm2/cities.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=City4 desktop-generic-name=false desktop-comment=Play\x20this\x20game\x20on\x20Steam desktop-icon=steam_icon_255710 desktop-try-exec= desktop-exec=steam\x20steam://rungameid/255710 desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup=/home/raster/.local/share/icons/hicolor/128x128/apps/steam_icon_255710.png user=raster group=raster mode=1a4 inode=23727046 nlink=1 gid=1000 size=167 blksize=4096 blocks=8 atime=1645911516 mtime=1645911516 ctime=1646086457 +CMD file-add path=/home/raster/C/efm2/efm_icon.h type=file mime=text/x-chdr mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-chdr.svg label=efm_icon.h user=raster group=raster mode=1a4 inode=23724919 nlink=1 gid=1000 size=414 blksize=4096 blocks=8 atime=1646596453 mtime=1646596453 ctime=1646596453 +CMD file-add path=/home/raster/C/efm2/sh-open type=file label=sh-open user=raster group=raster mode=1ed inode=18770068 nlink=1 gid=1000 size=296 blksize=4096 blocks=8 atime=1643917175 mtime=1643906431 ctime=1643917175 +CMD file-add path=/home/raster/C/efm2/efm_util.c\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=efm_util.c\x7e user=raster group=raster mode=1a4 inode=23724141 nlink=1 gid=1000 size=31835 blksize=4096 blocks=64 atime=1648768645 mtime=1648768645 ctime=1648769288 +CMD file-add path=/home/raster/C/efm2/beast2.desktop type=file mime=application/x-desktop mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-desktop.svg label=BEEAAST2 desktop-generic-name=false desktop-comment=Da\x20Biiieest desktop-icon=/home/raster/C/efm2/beast-shot.gif desktop-try-exec= desktop-exec=xterm desktop-url= desktop-no-display=false desktop-hidden=false desktop-terminal=false desktop-startup-notify=false desktop-icon.lookup= user=raster group=raster mode=1a4 inode=23724922 nlink=1 gid=1000 size=117 blksize=4096 blocks=8 atime=1645890953 mtime=1645890953 ctime=1645890953 +CMD file-add path=/home/raster/C/efm2/open.c type=file mime=text/x-csrc mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/text-x-csrc.svg label=open.c user=raster group=raster mode=1a4 inode=23726403 nlink=1 gid=1000 size=27651 blksize=4096 blocks=56 atime=1647027930 mtime=1647027930 ctime=1647027930 +CMD file-add path=/home/raster/C/efm2/broken type=link broken-link=true user=raster group=raster mode=1ff inode=23724048 nlink=1 gid=1000 size=7 blksize=4096 blocks=0 atime=1643917174 mtime=1643906443 ctime=1643917174 +CMD file-add path=/home/raster/C/efm2/beast-shot.gif type=file mime=image/gif mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-gif.svg thumb=/home/raster/.e/e/thumbs/b2/9787354aa1c33816b2a53d5a85542d751f81d4.eet label=beast-shot.gif user=raster group=raster mode=1a4 inode=23724918 nlink=1 gid=1000 size=18854 blksize=4096 blocks=40 atime=1645890492 mtime=1645890492 ctime=1645954000 +CMD file-add path=/home/raster/C/efm2/order.pdf type=file mime=application/pdf mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/categories/scalable/application-pdf.svg thumb=/home/raster/.e/e/thumbs/37/bd6ec0ae6bfaa4035c40efaf73fcfbfcf57045.eet label=order.pdf user=raster group=raster mode=1a4 inode=23724126 nlink=1 gid=1000 size=184544 blksize=4096 blocks=368 atime=1646596700 mtime=1646596700 ctime=1647031857 +CMD file-add path=/home/raster/C/efm2/efm_structs.h\x7e type=file mime=application/x-trash mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/application-x-trash.svg label=efm_structs.h\x7e user=raster group=raster mode=1a4 inode=23730938 nlink=1 gid=1000 size=3510 blksize=4096 blocks=8 atime=1648489751 mtime=1648489751 ctime=1648764496 +CMD file-add path=/home/raster/C/efm2/open type=file label=open user=raster group=raster mode=1ed inode=23724049 nlink=1 gid=1000 size=283680 blksize=4096 blocks=560 atime=1648769403 mtime=1648769403 ctime=1648769403 +CMD file-add path=/home/raster/C/efm2/yya type=dir label=yya user=raster group=raster mode=1ed inode=23732295 nlink=2 gid=1000 size=4096 blksize=4096 blocks=8 atime=1643917174 mtime=1643909696 ctime=1643917174 +CMD file-add path=/home/raster/C/efm2/ark.png type=file mime=image/png mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-png.svg thumb=/home/raster/.e/e/thumbs/d9/0f5094c9a5019a065dbc42ca9db28f56441836.eet label=ark.png user=raster group=raster mode=1a4 inode=23724913 nlink=1 gid=1000 size=65829 blksize=4096 blocks=136 atime=1646596853 mtime=1646596895 ctime=1647031857 +CMD file-add path=/home/raster/C/efm2/thumb type=file label=thumb user=raster group=raster mode=1ed inode=23724056 nlink=1 gid=1000 size=399696 blksize=4096 blocks=784 atime=1648769403 mtime=1648769404 ctime=1648769404 +CMD file-add path=/home/raster/C/efm2/tiger.svg type=file mime=image/svg+xml mime-icon=/home/raster/.local/share/icons/Flat-Remix-Yellow-Dark/mimetypes/scalable/image-svg+xml.svg thumb=/home/raster/.e/e/thumbs/28/d53be4c4a7bc03dde866cc2a189ebc40b62648.eet label=tiger.svg user=raster group=raster mode=1a4 inode=18219382 nlink=1 gid=1000 size=86486 blksize=4096 blocks=176 atime=1646577034 mtime=1646577034 ctime=1648769263 diff --git a/src/backends/common/fs_backend_core.c b/src/backends/common/fs_backend_core.c new file mode 100644 index 0000000..a975d15 --- /dev/null +++ b/src/backends/common/fs_backend_core.c @@ -0,0 +1,130 @@ +// this is a template file to drive any fs backend that sets up stdin/out +// and the core parsers and init/shutdown funcs. the expectation is you build +// this along with an implementation file that implements that fs +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmd.h" + +void do_handle_cmd(Cmd *c); +int do_init(int argc, const char **argv); +void do_shutdown(void); + +static Ecore_Fd_Handler *fdh = NULL; +static Eina_Strbuf *strbuf = NULL; + +static Eina_Bool +_cb_stdio_in_read(void *data EINA_UNUSED, Ecore_Fd_Handler *fd_handler EINA_UNUSED) +{ + ssize_t ret; + char buf[4096 + 1]; + + errno = 0; + ret = read(0, buf, sizeof(buf) - 1); + + if (ret < 1) + { + int e = errno; + + if ((e == EIO) || (e == EBADF) || (e == EPIPE) || (e == EINVAL) || + (e == ENOSPC) || (!((e == EAGAIN) || (e == EINTR)))) + { + ecore_main_loop_quit(); + goto done; + } + goto done; + } + else + { + const char *nl, *str; + + buf[ret] = '\0'; + eina_strbuf_append(strbuf, buf); + + for (;;) + { + char *s; + Cmd *c; + + str = eina_strbuf_string_get(strbuf); + nl = strchr(str, '\n'); + if (!nl) break; + + s = strndup(str, nl - str); + if (!s) break; + c = cmd_parse(s); + if (c) + { + do_handle_cmd(c); + cmd_free(c); + } + free(s); + eina_strbuf_remove(strbuf, 0, nl - str + 1); + } + } +done: + return EINA_TRUE; +} + +static void +_init(void) +{ + strbuf = eina_strbuf_new(); + if (!strbuf) + { + fprintf(stderr, "ERR: Can't allocate strbuf\n"); + goto err; + } + if (fcntl(0, F_SETFL, O_NONBLOCK) != 0) + { + fprintf(stderr, "ERR: Can't set stdin to O_NONBLOCK\n"); + goto err; + } + fdh = ecore_main_fd_handler_add(0, ECORE_FD_READ, _cb_stdio_in_read, + NULL, NULL, NULL); + if (!fdh) goto err; + return; +err: + exit(-1); +} + +static void +_shutdown(void) +{ + ecore_main_fd_handler_del(fdh); + fdh = NULL; + eina_strbuf_free(strbuf); + strbuf = NULL; +} + +int +main(int argc, const char **argv) +{ + int ret; + + eina_init(); + ecore_init(); + + _init(); + + ret = do_init(argc, argv); + + if (ret == 0) + { + ecore_main_loop_begin(); + do_shutdown(); + } + + _shutdown(); + + ecore_shutdown(); + eina_shutdown(); + + return ret; +} diff --git a/src/backends/default/meson.build b/src/backends/default/meson.build new file mode 100644 index 0000000..b807fa0 --- /dev/null +++ b/src/backends/default/meson.build @@ -0,0 +1,36 @@ +dir = join_paths(dir_lib, 'efm', 'backends', 'default') +inc = include_directories( + '.', + '../../..', + '../../efm', + '../../shared/commands', + '../../shared/common', + '../../backends/common' +) +executable('open', [ + '../../shared/common/cmd.c', + '../../shared/common/sha.c', + '../../backends/common/fs_backend_core.c', + 'open.c' + ], + include_directories: inc, + dependencies: deps, + install: true, + install_dir: dir) +executable('thumb', [ + '../../shared/common/sha.c', + 'thumb.c', + 'thumb_util_img.c', + 'thumb_util_search.c', + 'thumb_util_url.c', + 'thumb_image.c', + 'thumb_font.c', + 'thumb_paged.c', + 'thumb_music.c', + 'thumb_video.c', + 'thumb_edje.c' + ], + include_directories: inc, + dependencies: deps, + install: true, + install_dir: dir) diff --git a/src/backends/default/open.c b/src/backends/default/open.c new file mode 100644 index 0000000..60ec446 --- /dev/null +++ b/src/backends/default/open.c @@ -0,0 +1,838 @@ +// fs backend for regular fs files - opens a dir, lists, monitors and +// sends all files it finds nd all metadata and any changes that happen +// to that dir. it only handles a single dir. in theory you can write complex +// fs handlers that could do this over a network protocol or merge multiple +// real fs locations into a single apparent dir to the user. the idea is +// the front end that consumes the output of this should do NO file access +// itself at all and do everything via fs handlers for open, delete, rename +// copy, import, export etc. +#include "cmd.h" +#include "sha.h" +#include +#include +#include +#include + +#include +#include +#include + +#include "thumb_check.h" + +static const char *icon_theme = NULL; +static const char *config_dir = NULL; +static const char *home_dir = NULL; + +static Ecore_File_Monitor *mon = NULL; + +typedef struct +{ + const char *path; + const char *mime; + const char *thumb; + Ecore_Exe *exe; + Ecore_Timer *busy_delay_timer; +} Thumb; + +static Ecore_Event_Handler *thumb_exe_del_handler = NULL; +static Eina_List *thumb_queue = NULL; +static Eina_List *thumb_busy_queue = NULL; +static unsigned int thumb_busy_max = 8; +static double thumb_update_delay = 0.25; + +static Eina_Bool _file_add_mod_info(Eina_Strbuf *strbuf, const char *path, Eina_Bool delay); + +static void +_strbuf_append_file_escaped(Eina_Strbuf *strbuf, const char *path) +{ // append a filename and escape special chars for cmdline args + const char *s; + + for (s = path; *s; s++) + { + // if it's a special char - escale it + if ((*s == ' ') || (*s == '\t') || (*s == '\n') || (*s == '\\') || + (*s == '\'') || (*s == '\"') || (*s == ';') || (*s == '!') || + (*s == '#') || (*s == '$') || (*s == '%') || (*s == '&') || + (*s == '*') || (*s == '(') || (*s == ')') || (*s == '[') || + (*s == ']') || (*s == '{') || (*s == '}') || (*s == '|') || + (*s == '<') || (*s == '>') || (*s == '?')) + eina_strbuf_append_char(strbuf, '\\'); + eina_strbuf_append_char(strbuf, *s); + } +} + +static void +_file_thumb_flush(void) +{ // while count of busy list < max, pick from queue and launch ecore exe + Thumb *th; + Eina_List *l; + Eina_Strbuf *strbuf; + const char *dir = getenv("EFM_BACKEND_DIR"); + + if (!dir) + { + fprintf(stderr, "EFM_BACKEND_DIR not set to path to backend dirs\n"); + abort(); + } + strbuf = eina_strbuf_new(); + if (!strbuf) return; + // run up to thumb_busy_max thumbnailers in the background. this is + // set in the init func. + while (eina_list_count(thumb_busy_queue) < thumb_busy_max) + { + if (!thumb_queue) break; + + // find first item in out queue that is noy delaying until it is busy + EINA_LIST_FOREACH(thumb_queue, l, th) + { + if (!th->busy_delay_timer) break; + th = NULL; + } + if (!th) break; // we didn't find anything that is not busy sleeping + // remove from queue and put on busys queue + thumb_queue = eina_list_remove_list(thumb_queue, l); + thumb_busy_queue = eina_list_append(thumb_busy_queue, th); + // build thumb command: + // thumb FILE_PATH MIME THUMBNAIL_PATH + // e.g. + // thumb /path/to/f.jpg image/jpeg /home/u/.e/e/thumbs/f8/2a18.eet + eina_strbuf_reset(strbuf); + eina_strbuf_append(strbuf, getenv("EFM_BACKEND_DIR")); + eina_strbuf_append(strbuf, "/thumb "); + _strbuf_append_file_escaped(strbuf, th->path); + eina_strbuf_append_char(strbuf, ' '); + _strbuf_append_file_escaped(strbuf, th->mime); + eina_strbuf_append_char(strbuf, ' '); + _strbuf_append_file_escaped(strbuf, th->thumb); + th->exe = ecore_exe_run(eina_strbuf_string_get(strbuf), th); + } + eina_strbuf_free(strbuf); +} + +static Eina_Bool +_file_thumb(const char *path EINA_UNUSED, const char *mime) +{ // return true if we should have/generate a thumb for this + return check_thumb_any(path, mime); +} + +static char * +_file_thumb_find(const char *path, const char *mime EINA_UNUSED) +{ // find the thumb file + unsigned char sha[20]; + char buf[PATH_MAX], shastr[41]; + + eina_sha1((const unsigned char *)path, strlen(path), sha); + sha1_str(sha, shastr); + snprintf(buf, sizeof(buf), "%s/thumbs/%c%c/%s.eet", config_dir, + shastr[0], shastr[1], shastr + 2); + return strdup(buf); +} + +static Eina_Bool +_cb_thumb_delay(void *data) +{ // when a delay timer expires - flush the queue to the busy queue + Thumb *th = data; + + th->busy_delay_timer = NULL; + _file_thumb_flush(); + return EINA_FALSE; +} + +static void +_file_thumb_queue(const char *path, const char *mime, Eina_Bool delay) +{ // append a thumb generation item to the queue with a delay timer + Thumb *th = calloc(1, sizeof(Thumb)); + char *thumb; + + if (!th) return; + thumb = _file_thumb_find(path, mime); + if (!thumb) + { + free(th); + return; + } + th->path = eina_stringshare_add(path); + th->mime = eina_stringshare_add(mime); + th->thumb = eina_stringshare_add(thumb); + if (delay) + th->busy_delay_timer = ecore_timer_add(thumb_update_delay, + _cb_thumb_delay, th); + thumb_queue = eina_list_append(thumb_queue, th); + free(thumb); +} + +static void +_file_thumb_gen(const char *path, const char *mime, Eina_Bool delay) +{ // queue path + mime and kick off queue porcessor if idle + Eina_List *l; + Thumb *th; + Eina_Bool found = EINA_FALSE; + + // if already on queue - just moved to back + EINA_LIST_FOREACH(thumb_queue, l, th) + { + if (!strcmp(th->path, path)) + { + // move to end of queue + thumb_queue = eina_list_demote_list(thumb_queue, l); + // reset timer to start again + ecore_timer_reset(th->busy_delay_timer); + found = EINA_TRUE; + break; + } + } + if (!found) _file_thumb_queue(path, mime, delay); + _file_thumb_flush(); +} + +static Eina_Bool +_cb_thumb_exe_del(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event) +{ // thumb slave exited - exit code 0 == all ok + Ecore_Exe_Event_Del *ev = event; + Thumb *th; + Eina_List *l; + + // find exe in our busy queue - if it is there + EINA_LIST_FOREACH(thumb_busy_queue, l, th) + { + if (th->exe != ev->exe) continue; // it's not our desired exe - next + // remove this thumb from the busy list + thumb_busy_queue = eina_list_remove_list(thumb_busy_queue, l); + if (ev->exit_code == 0) + { // thumb generation was all ok so send a file update + Eina_Strbuf *strbuf = cmd_strbuf_new("file-mod"); + cmd_strbuf_append(strbuf, "path", th->path); + if (!_file_add_mod_info(strbuf, th->path, EINA_FALSE)) + eina_strbuf_free(strbuf); + else + cmd_strbuf_print_consume(strbuf); + } + // free up this thumb in our busy queue as we're done with it + eina_stringshare_del(th->path); + eina_stringshare_del(th->mime); + eina_stringshare_del(th->thumb); + free(th); + // we may have spare spots on our busy queue so flush to it + _file_thumb_flush(); + break; + } + return ECORE_CALLBACK_PASS_ON; // let others handle this exe if they want +} + +static void +_file_thumb_handle(Eina_Strbuf *strbuf, const char *path, const char *mime, struct stat *st, Eina_Bool delay) +{ // handle finding the thumb of file and checking it's valid etc. + char *thumb; + + // does this file type do/need thumbnails + if (!_file_thumb(path, mime)) return; + + // get what the path to the target thumb should be + thumb = _file_thumb_find(path, mime); + if (thumb) + { // open the thumb and let's see if the stat info is up to date + Eet_File *ef = eet_open(thumb, EET_FILE_MODE_READ); + + if (ef) + { // thumb file exists - check meta data + int size = 0; + unsigned char statsha1[20]; + unsigned char *origstatsha1; + Eina_Bool ok = EINA_FALSE; + + // sha1 the relevant stat data at this point + sha1_stat(st, statsha1); + // load the stored sha1 of stat data from the thumb file + origstatsha1 = eet_read(ef, "orig/stat/sha1", &size); + if ((origstatsha1) && (size == 20)) + { + if (!memcmp(statsha1, origstatsha1, 20)) + ok = EINA_TRUE; // sha1 of stat data matches - ok + } + eet_close(ef); + if (!ok) + { // thumb stat data doesn'yt match file state data + free(thumb); + thumb = NULL; + } + } + else + { // doesn't exist so no valid thumb + free(thumb); + thumb = NULL; + } + } + if (!thumb) + { // no valid thumb - generate one + _file_thumb_gen(path, mime, delay); + } + else + { // add the thumb property with full path to thumb + cmd_strbuf_append(strbuf, "thumb", thumb); + // XXX: add if tumb has alpha or not... + free(thumb); + } +} + +static char * +_icon_resolve(const char *path, const char *icon, struct stat *st) +{ + struct passwd *pw; + char buf[PATH_MAX], *dir, *p, *user; + + if (!icon) return NULL; + // /path/to/file.png + if (icon[0] == '/') return strdup(icon); + if ((icon[0] == '.') && (icon[1] == '/')) + { // ./path/file.png + if ((st->st_mode & S_IFMT) == S_IFDIR) + dir = strdup(path); + else + dir = ecore_file_dir_get(path); + if (dir) + { + snprintf(buf, sizeof(buf), "%s/%s", dir, icon + 2); + return strdup(buf); + } + else return NULL; + } + if ((icon[0] == '~') && (icon[1] == '/')) + { // ~/path/file.png + pw = getpwuid(st->st_uid); + if ((pw) && (pw->pw_dir)) + { + snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, icon + 2); + return strdup(buf); + } + else return NULL; + } + if (icon[0] == '~') + { // ~user/path/file.png + p = strchr(icon, '/'); + if (p) + { + user = malloc(p - icon); + if (user) + { + strncpy(user, icon + 1, p - icon - 1); + user[p - icon - 1] = 0; + pw = getpwnam(user); + if ((pw) && (pw->pw_dir)) + { + snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, p + 1); + return strdup(buf); + } + else return NULL; + } + else return NULL; + } + else return NULL; + } + return NULL; +} + +static const char * +_desktop_x_field(Efreet_Desktop *desktop, const char *key) +{ + if (!desktop->x) return NULL; + return eina_hash_find(desktop->x, key); +} + +static void +_cmd_desktop_x_field_append(Eina_Strbuf *strbuf, Efreet_Desktop *desktop, const char *cmd_key, const char *key) +{ + const char *s = _desktop_x_field(desktop, key); + if (s) cmd_strbuf_append(strbuf, cmd_key, s); +} + +static void +_cmd_desktop_x_field_icon_resolve_append(Eina_Strbuf *strbuf, Efreet_Desktop *desktop, const char *cmd_key, const char *key, const char *path, struct stat *st) +{ + const char *s = _desktop_x_field(desktop, key); + char *icf; + + if (!s) return; + icf = _icon_resolve(path, s, st); + if (!icf) return; + cmd_strbuf_append(strbuf, cmd_key, icf); + free(icf); +} + +const char * +_mime_get(const char *file) +{ + if (eina_fnmatch("*.edj", file, EINA_FNMATCH_CASEFOLD)) + return "application/x-edje"; + return efreet_mime_type_get(file); +} + +static Eina_Bool +_file_add_mod_info(Eina_Strbuf *strbuf, const char *path, Eina_Bool delay) +{ // add file metadata info on file add or modfiy + char dst[PATH_MAX], buf[256], buf2[PATH_MAX], *icf; + struct stat st; + int mode; + struct passwd *pw; + struct group *gr; + const char *mime, *ext, *icon; + Efreet_Desktop *desktop; + Eina_Bool have_label = EINA_FALSE; +// XXX: x & y pos @ wxh view + + if (lstat(path, &st) != 0) return EINA_FALSE; + if ((st.st_mode & S_IFMT) == S_IFLNK) + { + struct stat stdst; + + cmd_strbuf_append(strbuf, "type", "link"); + if (stat(path, &stdst) == 0) + { + ssize_t sz; + + sz = readlink(path, dst, sizeof(dst) - 1); + if (sz > 0) + { + dst[sz] = 0; + cmd_strbuf_append(strbuf, "link", dst); + if ((stdst.st_mode & S_IFMT) == S_IFLNK) + cmd_strbuf_append(strbuf, "link-type", "link"); + else if ((stdst.st_mode & S_IFMT) == S_IFBLK) + cmd_strbuf_append(strbuf, "link-type", "block"); + else if ((stdst.st_mode & S_IFMT) == S_IFCHR) + cmd_strbuf_append(strbuf, "link-type", "char"); + else if ((stdst.st_mode & S_IFMT) == S_IFIFO) + cmd_strbuf_append(strbuf, "link-type", "fifo"); + else if ((stdst.st_mode & S_IFMT) == S_IFSOCK) + cmd_strbuf_append(strbuf, "link-type", "socket"); + else if ((stdst.st_mode & S_IFMT) == S_IFDIR) + { + cmd_strbuf_append(strbuf, "link-type", "dir"); + snprintf(buf2, sizeof(buf2), "%s/.dir.desktop", path); + desktop = efreet_desktop_get(buf2); + if (desktop) + { + cmd_strbuf_append(strbuf, "link-label", desktop->name ? desktop->name : ecore_file_file_get(path)); + cmd_strbuf_append(strbuf, "link-desktop-generic-name", desktop->generic_name ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-comment", desktop->comment ? desktop->comment : ""); + icf = _icon_resolve(path, desktop->icon, &stdst); + if (icf) + { + cmd_strbuf_append(strbuf, "link-desktop-icon", icf); + free(icf); + } + else + cmd_strbuf_append(strbuf, "link-desktop-icon", desktop->icon ? desktop->icon : ""); + cmd_strbuf_append(strbuf, "link-desktop-try-exec", desktop->try_exec ? desktop->try_exec : ""); + cmd_strbuf_append(strbuf, "link-desktop-exec", desktop->exec ? desktop->exec : ""); + cmd_strbuf_append(strbuf, "link-desktop-url", desktop->url ? desktop->url : ""); + cmd_strbuf_append(strbuf, "link-desktop-no-display", desktop->no_display ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-hidden", desktop->hidden ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-terminal", desktop->terminal ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-startup-notify", desktop->startup_notify ? "true" : "false"); + if ((desktop->icon) && (desktop->icon[0] != '/')) + { + icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128); + cmd_strbuf_append(strbuf, "link-desktop-icon.lookup", icon ? icon : ""); + } + _cmd_desktop_x_field_append(strbuf, desktop, "link-label-clicked", "X-NameClicked"); + _cmd_desktop_x_field_append(strbuf, desktop, "link-label-selected", "X-NameSelected"); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-clicked", "X-IconClicked", path, &stdst); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-selected", "X-IconSelected", path, &stdst); + efreet_desktop_free(desktop); + have_label = EINA_TRUE; + } + } + else + cmd_strbuf_append(strbuf, "link-type", "file"); + mime = _mime_get(dst); + if (mime) + { + cmd_strbuf_append(strbuf, "mime", mime); + icon = efreet_mime_type_icon_get(mime, icon_theme, 128); + if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon); + _file_thumb_handle(strbuf, dst, mime, &stdst, delay); + } + ext = strrchr(dst, '.'); + if ((ext) && (!strcasecmp(ext, ".desktop")) && (S_ISREG(stdst.st_mode))) + { + desktop = efreet_desktop_get(path); + if (desktop) + { + cmd_strbuf_append(strbuf, "link-label", desktop->name ? desktop->name : ecore_file_file_get(path)); + cmd_strbuf_append(strbuf, "link-desktop-generic-name", desktop->generic_name ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-comment", desktop->comment ? desktop->comment : ""); + icf = _icon_resolve(path, desktop->icon, &stdst); + if (icf) + { + cmd_strbuf_append(strbuf, "link-desktop-icon", icf); + free(icf); + } + else + cmd_strbuf_append(strbuf, "link-desktop-icon", desktop->icon ? desktop->icon : ""); + cmd_strbuf_append(strbuf, "link-desktop-try-exec", desktop->try_exec ? desktop->try_exec : ""); + cmd_strbuf_append(strbuf, "link-desktop-exec", desktop->exec ? desktop->exec : ""); + cmd_strbuf_append(strbuf, "link-desktop-url", desktop->url ? desktop->url : ""); + cmd_strbuf_append(strbuf, "link-desktop-no-display", desktop->no_display ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-hidden", desktop->hidden ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-terminal", desktop->terminal ? "true" : "false"); + cmd_strbuf_append(strbuf, "link-desktop-startup-notify", desktop->startup_notify ? "true" : "false"); + if ((desktop->icon) && (desktop->icon[0] != '/')) + { + icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128); + cmd_strbuf_append(strbuf, "link-desktop-icon.lookup", icon ? icon : ""); + } + _cmd_desktop_x_field_append(strbuf, desktop, "link-label-clicked", "X-NameClicked"); + _cmd_desktop_x_field_append(strbuf, desktop, "link-label-selected", "X-NameSelected"); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-clicked", "X-IconClicked", path, &stdst); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-selected", "X-IconSelected", path, &stdst); + efreet_desktop_free(desktop); + } + else + cmd_strbuf_append(strbuf, "link-label", ecore_file_file_get(path)); + } + else + cmd_strbuf_append(strbuf, "link-label", ecore_file_file_get(path)); + pw = getpwuid(stdst.st_uid); + if (pw) cmd_strbuf_append(strbuf, "link-user", pw->pw_name); + gr = getgrgid(stdst.st_gid); + if (gr) cmd_strbuf_append(strbuf, "link-group", gr->gr_name); + mode = stdst.st_mode & + (S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH); + snprintf(buf, sizeof(buf), "%x", mode); + cmd_strbuf_append(strbuf, "link-mode", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_ino); + cmd_strbuf_append(strbuf, "link-inode", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_nlink); + cmd_strbuf_append(strbuf, "link-nlink", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_gid); + cmd_strbuf_append(strbuf, "link-gid", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_size); + cmd_strbuf_append(strbuf, "link-size", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_blksize); + cmd_strbuf_append(strbuf, "link-blksize", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_blocks); + cmd_strbuf_append(strbuf, "link-blocks", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_atime); + cmd_strbuf_append(strbuf, "link-atime", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_mtime); + cmd_strbuf_append(strbuf, "link-mtime", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_ctime); + cmd_strbuf_append(strbuf, "link-ctime", buf); + } + } + else // stat of original failed - what do we do? + { + cmd_strbuf_append(strbuf, "broken-link", "true"); + mime = _mime_get(path); + if (mime) + { + cmd_strbuf_append(strbuf, "mime", mime); + icon = efreet_mime_type_icon_get(mime, icon_theme, 128); + if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon); + } + } + } + else + { + if ((st.st_mode & S_IFMT) == S_IFBLK) + { + cmd_strbuf_append(strbuf, "type", "block"); + snprintf(buf, sizeof(buf), "%i", (int)st.st_dev); + cmd_strbuf_append(strbuf, "dev", buf); + snprintf(buf, sizeof(buf), "%i", (int)st.st_rdev); + cmd_strbuf_append(strbuf, "rdev", buf); + } + else if ((st.st_mode & S_IFMT) == S_IFCHR) + { + cmd_strbuf_append(strbuf, "type", "char"); + snprintf(buf, sizeof(buf), "%i", (int)st.st_dev); + cmd_strbuf_append(strbuf, "dev", buf); + snprintf(buf, sizeof(buf), "%i", (int)st.st_rdev); + cmd_strbuf_append(strbuf, "rdev", buf); + } + else if ((st.st_mode & S_IFMT) == S_IFIFO) + { + cmd_strbuf_append(strbuf, "type", "fifo"); + } + else if ((st.st_mode & S_IFMT) == S_IFSOCK) + { + cmd_strbuf_append(strbuf, "type", "socket"); + } + else if ((st.st_mode & S_IFMT) == S_IFDIR) + { + cmd_strbuf_append(strbuf, "type", "dir"); + snprintf(buf2, sizeof(buf2), "%s/.dir.desktop", path); + desktop = efreet_desktop_get(buf2); + if (desktop) + { + cmd_strbuf_append(strbuf, "label", desktop->name ? desktop->name : ecore_file_file_get(path)); + cmd_strbuf_append(strbuf, "desktop-generic-name", desktop->generic_name ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-comment", desktop->comment ? desktop->comment : ""); + icf = _icon_resolve(path, desktop->icon, &st); + if (icf) + { + cmd_strbuf_append(strbuf, "desktop-icon", icf); + free(icf); + } + else + cmd_strbuf_append(strbuf, "desktop-icon", desktop->icon ? desktop->icon : ""); + cmd_strbuf_append(strbuf, "desktop-try-exec", desktop->try_exec ? desktop->try_exec : ""); + cmd_strbuf_append(strbuf, "desktop-exec", desktop->exec ? desktop->exec : ""); + cmd_strbuf_append(strbuf, "desktop-url", desktop->url ? desktop->url : ""); + cmd_strbuf_append(strbuf, "desktop-no-display", desktop->no_display ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-hidden", desktop->hidden ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-terminal", desktop->terminal ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-startup-notify", desktop->startup_notify ? "true" : "false"); + if ((desktop->icon) && (desktop->icon[0] != '/')) + { + icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128); + cmd_strbuf_append(strbuf, "desktop-icon.lookup", icon ? icon : ""); + } + _cmd_desktop_x_field_append(strbuf, desktop, "label-clicked", "X-NameClicked"); + _cmd_desktop_x_field_append(strbuf, desktop, "label-selected", "X-NameSelected"); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-clicked", "X-IconClicked", path, &st); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-selected", "X-IconSelected", path, &st); + efreet_desktop_free(desktop); + have_label = EINA_TRUE; + } + } + else + cmd_strbuf_append(strbuf, "type", "file"); + mime = _mime_get(path); + if (mime) + { + cmd_strbuf_append(strbuf, "mime", mime); + icon = efreet_mime_type_icon_get(mime, icon_theme, 128); + if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon); + _file_thumb_handle(strbuf, path, mime, &st, delay); + } + ext = strrchr(path, '.'); + if ((ext) && (!strcasecmp(ext, ".desktop")) && (S_ISREG(st.st_mode))) + { + desktop = efreet_desktop_get(path); + if (desktop) + { + cmd_strbuf_append(strbuf, "label", desktop->name ? desktop->name : ecore_file_file_get(path)); + cmd_strbuf_append(strbuf, "desktop-generic-name", desktop->generic_name ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-comment", desktop->comment ? desktop->comment : ""); + icf = _icon_resolve(path, desktop->icon, &st); + if (icf) + { + cmd_strbuf_append(strbuf, "desktop-icon", icf); + free(icf); + } + else + cmd_strbuf_append(strbuf, "desktop-icon", desktop->icon ? desktop->icon : ""); + cmd_strbuf_append(strbuf, "desktop-try-exec", desktop->try_exec ? desktop->try_exec : ""); + cmd_strbuf_append(strbuf, "desktop-exec", desktop->exec ? desktop->exec : ""); + cmd_strbuf_append(strbuf, "desktop-url", desktop->url ? desktop->url : ""); + cmd_strbuf_append(strbuf, "desktop-no-display", desktop->no_display ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-hidden", desktop->hidden ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-terminal", desktop->terminal ? "true" : "false"); + cmd_strbuf_append(strbuf, "desktop-startup-notify", desktop->startup_notify ? "true" : "false"); + if ((desktop->icon) && (desktop->icon[0] != '/')) + { + icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128); + cmd_strbuf_append(strbuf, "desktop-icon.lookup", icon ? icon : ""); + } + _cmd_desktop_x_field_append(strbuf, desktop, "label-clicked", "X-NameClicked"); + _cmd_desktop_x_field_append(strbuf, desktop, "label-selected", "X-NameSelected"); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-clicked", "X-IconClicked", path, &st); + _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-selected", "X-IconSelected", path, &st); + efreet_desktop_free(desktop); + } + else if (!have_label) + cmd_strbuf_append(strbuf, "label", ecore_file_file_get(path)); + } + else if (!have_label) + cmd_strbuf_append(strbuf, "label", ecore_file_file_get(path)); + } + pw = getpwuid(st.st_uid); + if (pw) cmd_strbuf_append(strbuf, "user", pw->pw_name); + gr = getgrgid(st.st_gid); + if (gr) cmd_strbuf_append(strbuf, "group", gr->gr_name); + mode = st.st_mode /*& + (S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)*/; + snprintf(buf, sizeof(buf), "%x", mode); + cmd_strbuf_append(strbuf, "mode", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_ino); + cmd_strbuf_append(strbuf, "inode", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_nlink); + cmd_strbuf_append(strbuf, "nlink", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_gid); + cmd_strbuf_append(strbuf, "gid", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_size); + cmd_strbuf_append(strbuf, "size", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_blksize); + cmd_strbuf_append(strbuf, "blksize", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_blocks); + cmd_strbuf_append(strbuf, "blocks", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_atime); + cmd_strbuf_append(strbuf, "atime", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_mtime); + cmd_strbuf_append(strbuf, "mtime", buf); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_ctime); + cmd_strbuf_append(strbuf, "ctime", buf); + + return EINA_TRUE; +} + +static void +_file_add(const char *path) +{ + Eina_Strbuf *strbuf; + + strbuf = cmd_strbuf_new("file-add"); + cmd_strbuf_append(strbuf, "path", path); + if (!_file_add_mod_info(strbuf, path, EINA_FALSE)) + eina_strbuf_free(strbuf); + else + cmd_strbuf_print_consume(strbuf); +} + +static void +_file_del(const char *path) +{ + Eina_Strbuf *strbuf; + + strbuf = cmd_strbuf_new("file-del"); + cmd_strbuf_append(strbuf, "path", path); + cmd_strbuf_print_consume(strbuf); +} + +static void +_file_mod(const char *path) +{ + Eina_Strbuf *strbuf; + + strbuf = cmd_strbuf_new("file-mod"); + cmd_strbuf_append(strbuf, "path", path); + if (!_file_add_mod_info(strbuf, path, EINA_TRUE)) + eina_strbuf_free(strbuf); + else + cmd_strbuf_print_consume(strbuf); +} + +static void +_dir_del(const char *path) +{ + Eina_Strbuf *strbuf; + + strbuf = cmd_strbuf_new("dir-del"); + cmd_strbuf_append(strbuf, "path", path); + cmd_strbuf_print_consume(strbuf); +} + +static void +_cb_mon(void *data EINA_UNUSED, Ecore_File_Monitor *em EINA_UNUSED, Ecore_File_Event event, const char *path) +{ + if ((event == ECORE_FILE_EVENT_CREATED_FILE) || + (event == ECORE_FILE_EVENT_CREATED_DIRECTORY)) + _file_add(path); + else if ((event == ECORE_FILE_EVENT_DELETED_FILE) || + (event == ECORE_FILE_EVENT_DELETED_DIRECTORY)) + _file_del(path); + else if (event == ECORE_FILE_EVENT_MODIFIED) + _file_mod(path); + else if (event == ECORE_FILE_EVENT_DELETED_SELF) + _dir_del(path); +} + +static void +_monitor(const char *path) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *info; + Eina_Strbuf *strbuf; + + if (mon) return; + + // tell the front end out listing is beginning + strbuf = cmd_strbuf_new("list-begin"); + cmd_strbuf_print_consume(strbuf); + + mon = ecore_file_monitor_add(path, _cb_mon, NULL); + it = eina_file_direct_ls(path); + if (!it) + { + // XXX: error output + goto err; + } + EINA_ITERATOR_FOREACH(it, info) _file_add(info->path); + eina_iterator_free(it); +err: + // tell the front end out listing is done + strbuf = cmd_strbuf_new("list-end"); + cmd_strbuf_print_consume(strbuf); +} + +void +do_handle_cmd(Cmd *c) +{ + if (!strcmp(c->command, "dir-set")) + { + int i = 0; + + for (i = 0; c->dict[i]; i += 2) + { + if (!strcmp(c->dict[i], "path")) _monitor(c->dict[i + 1]); + } + } +// cmd_dump_sterr(c); +} + +int +do_init(int argc EINA_UNUSED, const char **argv EINA_UNUSED) +{ + const char *s; + + ecore_file_init(); + efreet_init(); + efreet_mime_init(); + + icon_theme = getenv("E_ICON_THEME"); + config_dir = getenv("E_HOME_DIR"); + home_dir = getenv("HOME"); + if (!home_dir) return 0; + if (!config_dir) + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/.e/e", home_dir); + home_dir = eina_stringshare_add(buf); + } + + // maximum number of back-end thumbnailer slaves is num cores - 2 + // with a minimum of 1 (so dual core systems will be limited to 1 + // thumbnailer at once. quad core will run 2. 8 core will run 6, 16 + // core will run 14, 32 core will run 30 etc. - this can get overidden + // by env var of course + s = getenv("E_THUMB_MAX"); + if (s) + { + thumb_busy_max = atoi(s); + } + else thumb_busy_max = eina_cpu_count() - 2; + if (thumb_busy_max < 1) thumb_busy_max = 1; + + // we want to listen for when thumbnails slaves finish + thumb_exe_del_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, + _cb_thumb_exe_del, NULL); + return 0; +} + +void +do_shutdown(void) +{ + ecore_event_handler_del(thumb_exe_del_handler); + thumb_exe_del_handler = NULL; + + if (mon) ecore_file_monitor_del(mon); + mon = NULL; + + efreet_mime_shutdown(); + efreet_shutdown(); + ecore_file_shutdown(); +} diff --git a/src/backends/default/sh-open b/src/backends/default/sh-open new file mode 100755 index 0000000..4330a82 --- /dev/null +++ b/src/backends/default/sh-open @@ -0,0 +1,10 @@ +#!/bin/bash + +read -r CMD + +sleep 1; +echo -e "CMD add type=file file=hello.txt icon=plain path=/xx/yy/hello.txt" +sleep 1 +echo -e "CMD add type=file file=blah.txt icon=folder path=/xx/yy/blah.txt" +echo -e "CMD add type=file file=boo.txt icon=blank path=/xx/yy/boo.txt" +while [ 1 ]; do sleep 1; done diff --git a/src/backends/default/thumb.c b/src/backends/default/thumb.c new file mode 100644 index 0000000..89f2d6f --- /dev/null +++ b/src/backends/default/thumb.c @@ -0,0 +1,108 @@ +#include "thumb.h" + +Evas_Object *win = NULL; +Evas_Object *subwin = NULL; +Evas_Object *image = NULL; + +static void +_thumb_dir_make(const char *thumb) +{ // create dir for the thumb to live in + char *dir = ecore_file_dir_get(thumb); + if (!dir) return; + ecore_file_mkpath(dir); + free(dir); +} + +static Eet_File * +_thumb_output_open(const char *thumb) +{ // open target thumb tmp file we will later atomically replace target with + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s.tmp", thumb); + return eet_open(buf, EET_FILE_MODE_WRITE); +} + +static void +_thumb_output_close(Eet_File *ef, const char *thumb) +{ // close thumnb file and atomically rename tmp file on top of target + char buf[PATH_MAX]; + + eet_close(ef); + snprintf(buf, sizeof(buf), "%s.tmp", thumb); + ecore_file_mv(buf, thumb); +} + +EAPI_MAIN int +elm_main(int argc, char **argv) +{ + Eet_File *ef; + const char *path, *mime, *thumb; + int ret; + struct stat st; + unsigned char statsha1[20]; + + if (argc < 4) return 100; + + path = argv[1]; + mime = argv[2]; + thumb = argv[3]; + + // set up buffer win/canvas with sub win rendered inline as image + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + elm_config_preferred_engine_set("buffer"); + win = elm_win_add(NULL, "Efm-Thumb", ELM_WIN_BASIC); + subwin = elm_win_add(win, "inlined", ELM_WIN_INLINED_IMAGE); + elm_win_alpha_set(subwin, EINA_TRUE); + image = elm_win_inlined_image_object_get(subwin); + evas_object_show(subwin); + evas_object_show(win); + // manual rendering as we won't run the loop - only render then get results + elm_win_norender_push(subwin); + elm_win_norender_push(win); + + // stat orig file and store the state info we care about as a sha1 hash + if (stat(path, &st) != 0) exit(1); + sha1_stat(&st, statsha1); + + // ensure dest dir for thumb exists + _thumb_dir_make(thumb); + // open our thubm file ti write thnigs into it + ef = _thumb_output_open(thumb); + if (!ef) exit(3); + + // an edj file (theme, background, icon .... groups inside matter + if (check_thumb_edje(path, mime)) + ret = thumb_edje(ef, path, mime, thumb); + // if it's a font - load it and render some text as thumb/preview + else if (check_thumb_font(path, mime)) + ret = thumb_font(ef, path, mime, thumb); + // a paged document - load multiple pages into thumb + else if (check_thumb_paged(path, mime)) + ret = thumb_paged(ef, path, mime, thumb); + // a music track/file + else if (check_thumb_music(path, mime)) + ret = thumb_music(ef, path, mime, thumb); + // a video file of a moive, series episode or something else + else if (check_thumb_video(path, mime)) + ret = thumb_video(ef, path, mime, thumb); + // otherwise handle as an image + else + ret = thumb_image(ef, path, mime, thumb); + + // write out the original file path so we could walk through all thumbs + // and find which thumbs no longer have an original file left + eet_write(ef, "orig/path", path, strlen(path), + EET_COMPRESSION_LOW); + // write out our sha1 of the file stat info - quick and mostly right way + // to heck if the thumb is up to date with file + eet_write(ef, "orig/stat/sha1", statsha1, 20, + EET_COMPRESSION_NONE); + // done - finish file write and atomic rename + _thumb_output_close(ef, thumb); + + // if we failed to generate the thumb - delete what we were building + if (ret != 0) unlink(thumb); + + return ret; +} +ELM_MAIN() diff --git a/src/backends/default/thumb.h b/src/backends/default/thumb.h new file mode 100644 index 0000000..8d28610 --- /dev/null +++ b/src/backends/default/thumb.h @@ -0,0 +1,71 @@ +#include +#include "efm_config.h" +#include "sha.h" +#include "thumb_check.h" + +extern Evas_Object *win; +extern Evas_Object *subwin; +extern Evas_Object *image; + +void thumb_image_write(Eet_File *ef, const char *key, Evas_Object *img, Eina_Bool a, Eina_Bool lossy); + +void thumb_url_str_get(const char *url, size_t max, void (*cb) (void *data, const char *result), const void *data); +void thumb_url_bin_get(const char *url, size_t max, void (*cb) (void *data, const void *result, size_t size), const void *data); + +typedef struct +{ + char *url; + int w, h; +} Search_Result; + +void thumb_search_image(const char *str, void (*cb) (void *data, Eina_List *results_orig, Eina_List *results_cached), void *data); + +int thumb_image (Eet_File *ef, const char *path, const char *mime, const char *thumb); +int thumb_font (Eet_File *ef, const char *path, const char *mime, const char *thumb); +int thumb_paged (Eet_File *ef, const char *path, const char *mime, const char *thumb); +int thumb_music (Eet_File *ef, const char *path, const char *mime, const char *thumb); +int thumb_video (Eet_File *ef, const char *path, const char *mime, const char *thumb); +int thumb_edje (Eet_File *ef, const char *path, const char *mime, const char *thumb); + +// utility +static inline void +scale(int *w, int *h, int maxw, int maxh, Eina_Bool no_scale_up) +{ // write a big 1024x1024 preview (but no larger than the original image) + int ww, hh; + + ww = maxw; + hh = (*h * maxw) / *w; + if (hh > maxh) + { // too tall = so limit height and scale down keeping aspect + hh = maxh; + ww = (*w * maxh) / *h; + } + if ((no_scale_up) && ((ww > *h) || (hh > *h))) + { + ww = *w; + hh = *h; + } + *w = ww; + *h = hh; +} + +static inline void +scale_out(int *w, int *h, int maxw, int maxh, Eina_Bool no_scale_up) +{ // write a big 1024x1024 preview (but no larger than the original image) + int ww, hh; + + ww = maxw; + hh = (*h * maxw) / *w; + if (hh < maxh) + { // not tall enough = so limit height and scale down keeping aspect + hh = maxh; + ww = (*w * maxh) / *h; + } + if ((no_scale_up) && ((ww > *h) || (hh > *h))) + { + ww = *w; + hh = *h; + } + *w = ww; + *h = hh; +} diff --git a/src/backends/default/thumb_edje.c b/src/backends/default/thumb_edje.c new file mode 100644 index 0000000..7e529ce --- /dev/null +++ b/src/backends/default/thumb_edje.c @@ -0,0 +1,378 @@ +// generate thumbnail for images +#include "thumb.h" + +// XXX: can do progressive resize down ie scale to 512 then take 512 and +// halve to 256 then halve it to 128 etc. rather than render from orig to +// target size.... + +static Evas_Object *edj = NULL; +static Evas_Object *subsubwin = NULL; +static Evas_Object *subimage = NULL; +static int iw = 0, ih = 0; +static char *grp_first = NULL; + +typedef enum +{ + EDJ_NONE, // does not appear to have groups + EDJ_THEME, // what looks like a full theme + EDJ_BACKGROUND, // a wallpaper background file + EDJ_ICON, // an icon edje file + EDJ_FIRST // just use the first group found +} Edj_Type; + +static void +_thumb_image_setup(void) +{ // create and show image + subsubwin = elm_win_add(subwin, "inlined2", ELM_WIN_INLINED_IMAGE); + subimage = elm_win_inlined_image_object_get(subsubwin); + elm_win_alpha_set(subsubwin, EINA_TRUE); + evas_object_show(subsubwin); + + edj = edje_object_add(evas_object_evas_get(subsubwin)); + evas_object_show(edj); +} + +static void +_thumb_image_file_background_set(const char *file) +{ + edje_object_file_set(edj, file, "e/desktop/background"); + iw = 1920; + ih = 1080; +} + +static void +_thumb_image_file_icon_set(const char *file) +{ + edje_object_file_set(edj, file, "icon"); + edje_object_size_min_get(edj, &iw, &ih); + if ((iw <= 0) || (ih < 0)) + { + iw = 256; + ih = 256; + } +} + +static void +_thumb_image_file_first_group_set(const char *file, const char *grp) +{ + edje_object_file_set(edj, file, grp); + edje_object_size_min_get(edj, &iw, &ih); + if (iw <= 0) + { + edje_object_size_min_restricted_calc(edj, &iw, &ih, 256, 256); + } +} + +static Eina_Bool +_cb_quit(void *data EINA_UNUSED) +{ + elm_exit(); + return EINA_FALSE; +} + +static void +_thumb_image_file_theme_set(const char *file) +{ + const char *s; + Evas_Object *o, *o_comp, *o_border, *o_win_table, *o_win_bg; + Evas_Object *o_toolbar, *o_list, *o_frame, *o_box; + Evas_Object *o_check1, *o_check2, *o_check3, *o_sep; + Evas_Object *o_radio1, *o_radio2, *o_radio3; + Evas_Object *o_slider, *o_progress; + Evas_Object *o_button1, *o_button2; + Elm_Object_Item *it; + + elm_theme_overlay_add(NULL, file); + + s = elm_theme_group_path_find(NULL, "e/desktop/background"); + edje_object_file_set(edj, s, "e/desktop/background"); + iw = 512; + ih = 512; + + o = o_comp = edje_object_add(evas_object_evas_get(subsubwin)); + s = elm_theme_group_path_find(NULL, "e/comp/frame/default"); + edje_object_file_set(o, s, "e/comp/frame/default"); + edje_object_signal_emit(o, "e,state,visible", "e"); + + o = o_border = edje_object_add(evas_object_evas_get(subsubwin)); + s = elm_theme_group_path_find(NULL, "e/widgets/border/default/border"); + edje_object_file_set(o, s, "e/widgets/border/default/border"); + edje_object_part_text_set(o, "e.text.title", "Title"); + edje_object_signal_emit(o, "e,state,focused", "e"); + evas_object_show(o); + edje_object_part_swallow(o_comp, "e.swallow.content", o); + + o = o_win_table = elm_table_add(subsubwin); + edje_object_part_swallow(o_border, "e.swallow.client", o); + evas_object_show(o); + + o = o_win_bg = elm_bg_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_table_pack(o_win_table, o, 0, 0, 10, 10); + evas_object_show(o); + + o = o_toolbar = elm_toolbar_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.0); + elm_toolbar_homogeneous_set(o, EINA_TRUE); + elm_toolbar_align_set(o, 0.5); + elm_table_pack(o_win_table, o, 0, 0, 10, 1); + evas_object_show(o); + + it = elm_toolbar_item_append(o_toolbar, "folder", "Folder", NULL, NULL); + elm_toolbar_item_selected_set(it, EINA_TRUE); + it = elm_toolbar_item_append(o_toolbar, "computer", "Computer", NULL, NULL); + it = elm_toolbar_item_append(o_toolbar, "audio-volume", "Volume", NULL, NULL); + it = elm_toolbar_item_append(o_toolbar, "user-available", "User", NULL, NULL); + it = elm_toolbar_item_append(o_toolbar, "mail-send", "Mail", NULL, NULL); + + o = o_list = elm_list_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_table_pack(o_win_table, o, 0, 1, 5, 6); + evas_object_show(o); + + it = elm_list_item_append(o_list, "This", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "List", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Contains", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Items", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Long list item", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Short item", NULL, NULL, NULL, NULL); + elm_list_item_selected_set(it, EINA_TRUE); + it = elm_list_item_append(o_list, "More text here", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Less text", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Text", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Yet more items", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "And even longer items", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Shorter items", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Short items", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Shortest item", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Second last item", NULL, NULL, NULL, NULL); + it = elm_list_item_append(o_list, "Last item", NULL, NULL, NULL, NULL); + + elm_list_go(o_list); + + o = o_frame = elm_frame_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_object_text_set(o, "Frame"); + elm_table_pack(o_win_table, o, 5, 1, 5, 6); + evas_object_show(o); + + o = o_box = elm_box_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_horizontal_set(o, EINA_FALSE); + elm_box_align_set(o, 0.5, 0.0); + elm_object_content_set(o_frame, o); + evas_object_show(o); + + o = o_check1 = elm_check_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_object_text_set(o, "This option"); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_check2 = elm_check_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_object_text_set(o, "Something"); + elm_check_state_set(o, EINA_TRUE); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_check3 = elm_check_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_object_text_set(o, "And another thing"); + elm_check_state_set(o, EINA_TRUE); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_sep = elm_separator_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_separator_horizontal_set(o, EINA_TRUE); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_radio1 = elm_radio_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_radio_state_value_set(o, 1); + elm_object_text_set(o, "First"); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_radio2 = elm_radio_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_radio_state_value_set(o, 2); + elm_radio_group_add(o, o_radio1); + elm_object_text_set(o, "Second"); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + o = o_radio3 = elm_radio_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 0.5); + elm_radio_state_value_set(o, 3); + elm_radio_group_add(o, o_radio1); + elm_object_text_set(o, "Third"); + elm_box_pack_end(o_box, o); + evas_object_show(o); + + elm_radio_value_set(o_radio1, 2); + + o = o_slider = elm_slider_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 1.0); + elm_object_text_set(o, "Slider"); + elm_slider_unit_format_set(o, "%1.1f"); + elm_slider_min_max_set(o, 0, 10); + elm_slider_value_set(o, 3.7); + elm_table_pack(o_win_table, o, 0, 7, 10, 1); + evas_object_show(o); + + o = o_progress = elm_progressbar_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 1.0); + elm_object_text_set(o, "Progress"); + elm_progressbar_value_set(o, 0.7); + elm_progressbar_unit_format_set(o, "%1.1f"); + elm_table_pack(o_win_table, o, 0, 8, 10, 1); + evas_object_show(o); + + o = o_button1 = elm_button_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 1.0); + elm_object_text_set(o, "Select"); + elm_table_pack(o_win_table, o, 0, 9, 5, 1); + evas_object_show(o); + + o = o_button2 = elm_button_add(o_win_table); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, 1.0); + elm_object_text_set(o, "Cancel"); + elm_table_pack(o_win_table, o, 5, 9, 5, 1); + evas_object_show(o); + + o = o_comp; + evas_object_move(o, 64, 64); + evas_object_resize(o, 384, 384); + evas_object_show(o); + + ecore_timer_add(1.0, _cb_quit, NULL); + elm_run(); +} + +static Edj_Type +_thumb_edje_type_guess(const char *file) +{ + Eina_List *l; + const char *s; + Eina_List *groups = edje_file_collection_list(file); + if (!groups) return EDJ_NONE; + Eina_Bool have_icon = EINA_FALSE; + Eina_Bool have_e_background = EINA_FALSE; + Eina_Bool have_e_border = EINA_FALSE; + Eina_Bool have_elm_button = EINA_FALSE; + Eina_Bool have_elm_check = EINA_FALSE; + Eina_Bool have_elm_genlist = EINA_FALSE; + Eina_Bool have_elm_frame = EINA_FALSE; + + EINA_LIST_FOREACH(groups, l, s) + { + if (!grp_first) grp_first = strdup(s); +#define MATCH(__str, __val) (!strcmp(s, __str)) have_ ## __val = EINA_TRUE + if MATCH("icon", icon); + else if MATCH("e/desktop/background", e_background); + else if MATCH("e/widgets/border/default/border", e_border); + else if MATCH("elm/button/base/default", elm_button); + else if MATCH("elm/check/base/default", elm_check); + else if MATCH("elm/genlist/item/default/default", elm_genlist); + else if MATCH("elm/frame/base/default", elm_frame); + } + edje_file_collection_list_free(groups); + + if (have_e_background && have_e_border && have_elm_button && + have_elm_check && have_elm_genlist && have_elm_frame) + return EDJ_THEME; + else if (have_e_background) + return EDJ_BACKGROUND; + else if (have_icon) + return EDJ_ICON; + return EDJ_FIRST; +} + +int +thumb_edje(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + int w, h, i; + char buf[128]; + Edj_Type etype = _thumb_edje_type_guess(path); + + if (etype == EDJ_NONE) return 2; + + elm_config_scale_set(1.0); + _thumb_image_setup(); + + if (etype == EDJ_BACKGROUND) + { // add filled background + _thumb_image_file_background_set(path); + } + else if (etype == EDJ_ICON) + { // an edj icon file + _thumb_image_file_icon_set(path); + } + else if (etype == EDJ_THEME) + { // it's a theme - let's fake up some gui setup + _thumb_image_file_theme_set(path); + } + else if (etype == EDJ_FIRST) + { // just load first group we find + _thumb_image_file_first_group_set(path, grp_first); + } + // if size is bunk - we can't load it... + if ((iw <= 0) || (ih < 0)) return 2; + + // write a big 1024x1024 preview (but no larger than the original image) + w = iw; h = ih; scale(&w, &h, 1024, 1024, EINA_TRUE); + + // resize to target size + evas_object_resize(edj, iw, ih); + evas_object_resize(subsubwin, iw, ih); + + // preview + evas_object_resize(subimage, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subsubwin); + elm_win_render(subwin); + // save out preview size + snprintf(buf, sizeof(buf), "image/preview"); + thumb_image_write(ef, buf, image, EINA_TRUE, EINA_TRUE); + snprintf(buf, sizeof(buf), "%i %i", w, h); + eet_write(ef, "image/preview/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // multiple thumb sizes so can load/pick the best one at runtime + for (i = 0; sizes[i] != 0; i++) + { + // scale down and keep aspect + w = iw; h = ih; scale(&w, &h, sizes[i], sizes[i], EINA_FALSE); + // resize to target size + evas_object_resize(subimage, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + thumb_image_write(ef, buf, image, EINA_TRUE, EINA_TRUE); + } + + return 0; +} diff --git a/src/backends/default/thumb_font.c b/src/backends/default/thumb_font.c new file mode 100644 index 0000000..f413f11 --- /dev/null +++ b/src/backends/default/thumb_font.c @@ -0,0 +1,102 @@ +// generate thumbnail files for fonts +#include "thumb.h" + +static Evas_Object * +_thumb_text_setup(const char *file, const char *str, int maxw, int maxh, int size, int *size_chosen) +{ + Evas_Object *o; + Evas_Coord w, h; + int sizeup, sizedn, psize; + + o = evas_object_text_add(evas_object_evas_get(subwin)); + evas_object_color_set(o, 255, 255, 255, 255); + evas_object_text_text_set(o, str); + if (size == 0) + evas_object_text_font_set(o, file, maxh); + else + evas_object_text_font_set(o, file, size); + evas_object_show(o); + if (size == 0) + { // auto find a size that fits in maxw/maxh + size = maxh; + sizeup = size * 2; + sizedn = 1; + psize = size + 100; // dummy psize for first loop + for (;;) + { + evas_object_geometry_get(o, NULL, NULL, &w, &h); + if ((w <= maxw) && (h <= maxh)) + { // it fits + if (abs(size - psize) <= 1) break; // we found the best size + psize = size; + size = (size + sizeup) / 2; + sizedn = psize; + } + else + { + if (abs(size - psize) <= 1) break; // we found the best size + psize = size; + size = (size + sizedn) / 2; + sizeup = psize; + } + evas_object_text_font_set(o, file, size); + } + } + *size_chosen = size; + return o; +} + +static void +_thumb_font_2_line(const char *str1, const char *str2, Eet_File *ef, const char *path, int szw, int szh, const char *key) +{ + Evas_Object *tx1, *tx2; + int sz1, sz2, w, h; + + tx1 = _thumb_text_setup(path, str1, szw, szh / 2, 0, &sz1); + tx2 = _thumb_text_setup(path, str2, szw, szh / 2, 0, &sz2); + if (sz1 < sz2) + { + evas_object_del(tx2); + sz2 = sz1; + tx2 = _thumb_text_setup(path, str2, szw, szh / 2, sz2, &sz2); + } + else if (sz2 < sz1) + { + evas_object_del(tx1); + sz1 = sz2; + tx1 = _thumb_text_setup(path, str1, szw, szh / 2, sz1, &sz1); + } + evas_object_geometry_get(tx1, NULL, NULL, &w, &h); + evas_object_move(tx1, (szw - w) / 2, ((szh / 2) - h) / 2); + evas_object_geometry_get(tx2, NULL, NULL, &w, &h); + evas_object_move(tx2, (szw - w) / 2, (szh / 2) + (((szh / 2) - h) / 2)); + evas_object_resize(subwin, szw, szh); + elm_win_render(subwin); + thumb_image_write(ef, key, image, EINA_TRUE, EINA_FALSE); + evas_object_del(tx1); + evas_object_del(tx2); +} + +int +thumb_font(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + char buf[128]; + int i; + char one = 1; + + // XXX: we don't handle any font load errors - eg 0 sized + for (i = 0; sizes[i] != 0; i++) + { + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + _thumb_font_2_line("ABC", + "def", + ef, path, sizes[i], sizes[i], buf); + } + _thumb_font_2_line("Lorem ipsum dolor sit amet 012/789 #!?", + "日本語 にほんご ソフト 中文 華語 한국", + ef, path, 2048, 256, "image/preview"); + eet_write(ef, "image/thumb/mono", &one, 1, + EET_COMPRESSION_NONE); + return 0; +} diff --git a/src/backends/default/thumb_image.c b/src/backends/default/thumb_image.c new file mode 100644 index 0000000..152b9cc --- /dev/null +++ b/src/backends/default/thumb_image.c @@ -0,0 +1,165 @@ +// generate thumbnail for images +#include "thumb.h" + +// XXX: can do progressive resize down ie scale to 512 then take 512 and +// halve to 256 then halve it to 128 etc. rather than render from orig to +// target size.... + +static Evas_Object *im = NULL; +static Eina_Bool alpha = EINA_FALSE; +static int iw = 0, ih = 0; + +static void +_thumb_image_setup(void) +{ // create and show image + im = evas_object_image_filled_add(evas_object_evas_get(subwin)); + evas_object_show(im); +} + +static void +_thumb_image_file_set(const char *file) +{ // set file to image, get size & alpha + evas_object_image_file_set(im, file, NULL); + evas_object_image_size_get(im, &iw, &ih); + alpha = evas_object_image_alpha_get(im); +} + +static void +_thumb_image_hsv_sort_render(unsigned int *dst, int w, int h) +{ // render image down to a specific size (small) and copy to dest + void *pixels; + + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + elm_win_render(subwin); + pixels = evas_object_image_data_get(image, EINA_FALSE); + if (!pixels) return; + memcpy(dst, pixels, w * h * sizeof(int)); + evas_object_image_data_set(image, pixels); +} + +static void +_thumb_image_hsv_sort_key_write(Eet_File *ef, const char *key) +{ // get rendered pixels from image representing subwin and write out AHVS key + unsigned int *data4x4, *data2x2, *data1x1; + unsigned char id2[256]; + int n, i, hi, si, vi; + float h, s, v; + const int pat2x2[4] = { 0, 3, 1, 2 }; + const int pat4x4[16] = { 5, 10, 6, 9, + 0, 15, 3, 12, + 1, 14, 7, 8, + 4, 11, 2, 13 }; + + data4x4 = malloc(4 * 4 * sizeof(int)); + data2x2 = malloc(2 * 2 * sizeof(int)); + data1x1 = malloc(1 * 1 * sizeof(int)); + + if ((!data4x4) || (!data2x2) || (!data1x1)) goto err; + + _thumb_image_hsv_sort_render(data4x4, 4, 4); + _thumb_image_hsv_sort_render(data2x2, 2, 2); + _thumb_image_hsv_sort_render(data1x1, 1, 1); + + n = 0; + +#define A(v) (((v) >> 24) & 0xff) +#define R(v) (((v) >> 16) & 0xff) +#define G(v) (((v) >> 8 ) & 0xff) +#define B(v) (((v) ) & 0xff) +#define HSV(p) do { \ + evas_color_rgb_to_hsv(R(p), G(p), B(p), &h, &s, &v); \ + hi = 20 * (h / 360.0); \ + si = 20 * s; \ + vi = 20 * v; \ + if (si < 2) hi = 25; \ +} while (0) +#define SAVEX(x) id2[n++] = 'a' + (x) + + SAVEX(A(data1x1[0])); + for (i = 0; i < 4 ; i++) SAVEX(A(data2x2[pat2x2[i]])); + for (i = 0; i < 16; i++) SAVEX(A(data4x4[pat4x4[i]])); + +#define STORE(val) \ + HSV(data1x1[0]); \ + SAVEX(val); \ + for (i = 0; i < 4; i++) { \ + HSV(data2x2[pat2x2[i]]); \ + SAVEX(val); \ + } \ + for (i = 0; i < 16; i++) { \ + HSV(data4x4[pat4x4[i]]); \ + SAVEX(val); \ + } + + STORE(hi); + STORE(vi); + STORE(si); + + id2[n++] = 0; + + eet_write(ef, key, id2, n, + EET_COMPRESSION_NONE); +err: + free(data4x4); + free(data2x2); + free(data1x1); +} + +int +thumb_image(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + int w, h, i; + char buf[128]; + unsigned char a; + + _thumb_image_setup(); + // add filled image to then size accordingly + _thumb_image_file_set(path); + // if size is bunk - we can't load it... + if ((iw <= 0) || (ih < 0)) return 2; + + // write a big 1024x1024 preview (but no larger than the original image) + w = iw; h = ih; scale(&w, &h, 1024, 1024, EINA_TRUE); + + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out preview size + snprintf(buf, sizeof(buf), "image/preview"); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + snprintf(buf, sizeof(buf), "%i %i", w, h); + eet_write(ef, "image/preview/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // multiple thumb sizes so can load/pick the best one at runtime + for (i = 0; sizes[i] != 0; i++) + { + // scale down and keep aspect + w = iw; h = ih; scale(&w, &h, sizes[i], sizes[i], EINA_FALSE); + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + } + + _thumb_image_hsv_sort_key_write(ef, "image/sort_key"); + + // write original alpha flag + a = alpha; + if (alpha) + eet_write(ef, "orig/image/alpha", &a, 1, + EET_COMPRESSION_NONE); + // write original size + snprintf(buf, sizeof(buf), "%i %i", iw, ih); + eet_write(ef, "orig/image/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + return 0; +} diff --git a/src/backends/default/thumb_music.c b/src/backends/default/thumb_music.c new file mode 100644 index 0000000..cc67bd6 --- /dev/null +++ b/src/backends/default/thumb_music.c @@ -0,0 +1,400 @@ +// generate thumbnail for music files - look for album art on google +#include "thumb.h" +#include + +// XXX: can do progressive resize down ie scale to 512 then take 512 and +// halve to 256 then halve it to 128 etc. rather than render from orig to +// target size.... + +static Evas_Object *im = NULL; +static Eina_Bool alpha = EINA_FALSE; +static int iw = 0, ih = 0; + +static Eina_List *results = NULL; + +static void *mem_data = NULL; +static int mem_size = 0; +static int query_pass = 0; + +static char *title = NULL; +static char *artist = NULL; +static char *album = NULL; + +typedef struct +{ + char *url; + int w, h; + unsigned long long fitness; +} Result; + +static void +_thumb_image_setup(void) +{ // create and show image + im = evas_object_image_filled_add(evas_object_evas_get(subwin)); + evas_object_show(im); +} + +static void +_thumb_image_mem_set(void *data, int size) +{ // set file to image, get size & alpha + evas_object_image_memfile_set(im, data, size, "jpg", NULL); + evas_object_image_size_get(im, &iw, &ih); + alpha = evas_object_image_alpha_get(im); +} + +static void +_thumb_image_file_set(const char *file) +{ // set file to image, get size & alpha + evas_object_image_file_set(im, file, NULL); + evas_object_image_size_get(im, &iw, &ih); + alpha = evas_object_image_alpha_get(im); +} + +static Eina_Bool +_cb_vid_open_done_timeout(void *data EINA_UNUSED) +{ + elm_exit(); + return EINA_FALSE; +} + +static void +_cb_vid_open_done(void *data EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED) +{ // we finished opening - get netadata + const char *s; + + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_TITLE); + if (s) title = strdup(s); + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ARTIST); + if (s) artist = strdup(s); + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ALBUM); + if (s) album = strdup(s); + // finish loop + elm_exit(); +} + +static void +_video_metadata_get(const char *path) +{ + Ecore_Timer *t; + Evas_Object *o = emotion_object_add(evas_object_evas_get(subwin)); + + evas_object_smart_callback_add(o, "open_done", _cb_vid_open_done, NULL); + emotion_object_file_set(o, path); + emotion_object_audio_mute_set(o, EINA_TRUE); + emotion_object_audio_volume_set(o, 0.0); + // a timeout for the loop + t = ecore_timer_add(10.0, _cb_vid_open_done_timeout, NULL); + elm_run(); + ecore_timer_del(t); + evas_object_del(o); +} + +static void +_cb_results(void *data EINA_UNUSED, Eina_List *results_orig, Eina_List *results_cached EINA_UNUSED) +{ + Eina_List *l; + Search_Result *res; + Result *r; + unsigned long long fit_size, fit_square, fit_jpg, fit_listpos, fit_pass; + + // we need to re-score results first square better than not suqare + // next - higher est better than lower rest + // ends in .jpg, .jpeg better than not + fit_listpos = 100; + EINA_LIST_FOREACH(results_orig, l, res) + { + // skip results that are 0 sized or with no url + if ((res->w <= 0) || (res->h <= 0) || (!res->url)) continue; + // new result + r = calloc(1, sizeof(Result)); + if (!r) continue; + r->w = res->w; + r->h = res->h; + r->url = strdup(res->url); + // jpegs preferred + if ((eina_fnmatch("*.jpg", res->url, EINA_FNMATCH_CASEFOLD)) || + (eina_fnmatch("*.jpeg", res->url, EINA_FNMATCH_CASEFOLD)) || + (eina_fnmatch("*.jpe", res->url, EINA_FNMATCH_CASEFOLD))) + fit_jpg = 100; + else + fit_jpg = 50; + // bigger is better + fit_size = (r->w / 10) * (r->h / 10); + // if it's bigger than 1000x1000 it's not really better + if (fit_size > 10000) fit_size = 10000; + // more square is better + fit_square = (100 * r->w) / r->h; + if (fit_square > 100) fit_square = (100 * r->h) / r->w; + // first pass gets a higher multiplier than latter passes + fit_pass = ((10 - query_pass) * 100) / 10; + // store fitness and result + r->fitness = fit_listpos * fit_size * fit_square * fit_jpg * fit_pass; + results = eina_list_append(results, r); + // list position fitness goes down by .9 of previous list pos fitness + fit_listpos = (90 * fit_listpos) / 100; + if (fit_listpos < 1) fit_listpos = 1; + } +} + +static int +_cb_fitness_sort(const void *data1, const void *data2) +{ + const Result *r1 = data1, *r2 = data2; + + if (r1->fitness < r2->fitness) return 1; + else if (r1->fitness > r2->fitness) return -1; + return 0; +} + +static void +_cb_url_bin(void *data EINA_UNUSED, const void *result, size_t size) +{ // handle in memory fetch of image + // too big - 64M + if (size > (64 * 1024 * 1024)) return; + if (mem_data) free(mem_data); + mem_data = malloc(size); + mem_size = size; + memcpy(mem_data, result, size); + elm_exit(); +} + +static void +_thumb_online_search(const char *path) +{ + Eina_Strbuf *query_buf; + const char *file, *ext; + Eina_List *l; + Result *r; + int i; + + // get file and where extension starts to be removed + file = ecore_file_file_get(path); + ext = strchr(file, '.'); + + _video_metadata_get(path); + + if ((title) || (album) || (artist)) + { + query_buf = eina_strbuf_new(); + if (artist) + { + eina_strbuf_append(query_buf, artist); + eina_strbuf_append(query_buf, " "); + } + if (artist) + { + eina_strbuf_append(query_buf, artist); + eina_strbuf_append(query_buf, " "); + } + if (title) + { + eina_strbuf_append(query_buf, title); + eina_strbuf_append(query_buf, " "); + } + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + // we have real metasata - make filename searches lhave lower + // fitness by bumping query pass + query_pass += 4; + free(title); + free(album); + free(artist); + } + + // search using filename as our search + // search for munged filename + "album art" + query_buf = eina_strbuf_new(); + if (ext) // append all but extension and dot + eina_strbuf_append_n(query_buf, file, ext - file); + else // append the whole filename + eina_strbuf_append(query_buf, file); + eina_strbuf_append(query_buf, " album art"); + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + query_pass++; + + // now search for just the munged filename without any extra string + query_buf = eina_strbuf_new(); + if (ext) // append all but extension and dot + eina_strbuf_append_n(query_buf, file, ext - file); + else // append the whole filename + eina_strbuf_append(query_buf, file); + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + + // sort results by fitness + results = eina_list_sort(results, eina_list_count(results), + _cb_fitness_sort); + + i = 0; + EINA_LIST_FOREACH(results, l, r) + { + // get image max 64M + thumb_url_bin_get(r->url, 64 * 1024 * 1024, _cb_url_bin, NULL); + elm_run(); + if (mem_data) + { + // add filled image to then size accordingly + _thumb_image_mem_set(mem_data, mem_size); + free(mem_data); + mem_data = NULL; + if ((iw > 0) && (ih > 0)) break; + i++; + } + // tried 10 - give up + if (i >= 10) break; + } + if (mem_data) free(mem_data); +} + +static char * +_thumb_explicit_find(const char *path) +{ + char *tmp = alloca(strlen(path) + 1 + 100); + char *dir, *fraw, *s; + const char *fname, *e, *c; + const char *ext[] = { + "png", "PNG", + "jpg", "JPG", + "jpeg", "JPEG", + "jpe", "JPE", + NULL }; + const char *cover[] = { + "cover", "Cover", "COVER", + "front", "Front", "FRONT", + "folder", "Folder", "FOLDER", + ".cover", ".Cover", ".COVER", + ".front", ".Front", ".FRONT", + ".folder", ".Folder", ".FOLDER", + NULL }; + int i, j; + + // from here example comments assume /dir/file.mp3 as the path + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.mp3.png etc. + sprintf(tmp, "%s.%s", path, e); + if (ecore_file_exists(tmp)) return strdup(tmp); + } + + dir = ecore_file_dir_get(path); + if (!dir) + { // if no dir we are /file.mp3 thus "" works find for following code + dir = strdup(""); + if (!dir) return NULL; + } + fname = ecore_file_file_get(path); + if (!fname) + { // this shouldn't happen - but handle it anyway + free(dir); + return NULL; + } + fraw = strdup(fname); // fraw will be filename for e.g. filename.mp3 + if (!fraw) + { + free(dir); + return NULL; + } + s = strrchr(fraw, '.'); + if (s) *s = 0; + + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.mp3.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fname, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.file.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.file.mp3.png etc. + sprintf(tmp, "%s/.thumb/%s.%s", dir, fname, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.thumb/file.png etc. + sprintf(tmp, "%s/.thumb/%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + + // XXX: should we do this for every file in that dir? really? + for (j = 0; (c = cover[j]) && c; j++) + { // /dir/cover.png etc. - single img for everything in the dir... + for (i = 0; (e = ext[i]) && e; i++) + { + sprintf(tmp, "%s/%s.%s", dir, c, e); + if (ecore_file_exists(tmp)) goto found; + } + } + + free(dir); + free(fraw); + return NULL; +found: + free(dir); + free(fraw); + return strdup(tmp); +} + +int +thumb_music(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + int w, h, i; + char buf[128]; + char *thumb_file; + + + _thumb_image_setup(); + + // look for an explicitly "requested" file for the thumb path + thumb_file = _thumb_explicit_find(path); + + // if we didn't find an explicit matching thumb path in dir or nearby... + if (thumb_file) _thumb_image_file_set(thumb_file); + // explicit thumb not found or the load failed as image size is not sane + if ((iw <= 0) || (ih < 0)) _thumb_online_search(path); + + // if size is bunk - we can't load it... + if ((iw <= 0) || (ih < 0)) return 2; + + // write a big 1024x1024 preview (but no larger than the original image) + w = iw; h = ih; scale_out(&w, &h, 1024, 1024, EINA_FALSE); + + // resize to target size + evas_object_resize(im, w, h); + evas_object_move(im, (1024 - w) / 2, 0); + evas_object_resize(subwin, 1024, 1024); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out preview size + snprintf(buf, sizeof(buf), "image/preview"); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + snprintf(buf, sizeof(buf), "%i %i", w, h); + eet_write(ef, "image/preview/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // multiple thumb sizes so can load/pick the best one at runtime + for (i = 0; sizes[i] != 0; i++) + { + // scale down and keep aspect + w = iw; h = ih; scale_out(&w, &h, sizes[i], sizes[i], EINA_FALSE); + // resize to target size + evas_object_resize(im, w, h); + evas_object_move(im, (sizes[i] - w) / 2, 0); + evas_object_resize(subwin, sizes[i], sizes[i]); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + } + + return 0; +} diff --git a/src/backends/default/thumb_paged.c b/src/backends/default/thumb_paged.c new file mode 100644 index 0000000..0994089 --- /dev/null +++ b/src/backends/default/thumb_paged.c @@ -0,0 +1,107 @@ +// generate thumbnail for images +#include "thumb.h" + +// XXX: can do progressive resize down ie scale to 512 then take 512 and +// halve to 256 then halve it to 128 etc. rather than render from orig to +// target size.... + +static Evas_Object *im = NULL; +static Eina_Bool alpha = EINA_FALSE; +static int iw = 0, ih = 0; + +static void +_thumb_page_setup(void) +{ // create and show image + im = evas_object_image_filled_add(evas_object_evas_get(subwin)); + evas_object_show(im); +} + +static Eina_Bool +_thumb_page_file_set(const char *file, int page) +{ // set file to image, get size & alpha + char buf[32]; + + snprintf(buf, sizeof(buf), "%i", page); + evas_object_image_file_set(im, file, buf); + evas_object_image_size_get(im, &iw, &ih); + if ((iw > 0) && (ih > 0)) return EINA_TRUE; + alpha = evas_object_image_alpha_get(im); + return EINA_FALSE; +} + +int +thumb_paged(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + int w, h, i; + char buf[128]; + unsigned char a; + + _thumb_page_setup(); + // add filled image to then size accordingly + _thumb_page_file_set(path, 0); + // if size is bunk - we can't load it... + if ((iw <= 0) || (ih < 0)) return 2; + + // write a big 1024x1024 preview (but no larger than the original doc) + w = iw; h = ih; scale(&w, &h, 1024, 1024, EINA_TRUE); + + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out preview size + snprintf(buf, sizeof(buf), "image/preview"); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + snprintf(buf, sizeof(buf), "%i %i", w, h); + eet_write(ef, "image/preview/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // multiple thumb sizes so can load/pick the best one at runtime + for (i = 0; sizes[i] != 0; i++) + { + // scale down and keep aspect + w = sizes[i]; + h = (ih * sizes[i]) / iw; + if (h > sizes[i]) + { // too tall = so limit height and scale down keeping aspect + h = sizes[i]; + w = (iw * sizes[i]) / ih; + } + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + } + + // write original alpha flag + a = alpha; + if (alpha) + eet_write(ef, "orig/image/alpha", &a, 1, + EET_COMPRESSION_NONE); + // write original size + snprintf(buf, sizeof(buf), "%i %i", iw, ih); + eet_write(ef, "orig/image/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // write page previews of the first 32 pages + for (i = 0; i < 32; i++) + { + if (!_thumb_page_file_set(path, i)) break; + w = iw; h = ih; scale(&w, &h, 512, 512, EINA_TRUE); + snprintf(buf, sizeof(buf), "image/page/%i", i); + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + } + + return 0; +} diff --git a/src/backends/default/thumb_util_img.c b/src/backends/default/thumb_util_img.c new file mode 100644 index 0000000..bbcaad1 --- /dev/null +++ b/src/backends/default/thumb_util_img.c @@ -0,0 +1,22 @@ +#include "thumb.h" + +void +thumb_image_write(Eet_File *ef, const char *key, Evas_Object *img, Eina_Bool a, Eina_Bool lossy) +{ // get rendered pixels from image representing subwin + void *pixels = evas_object_image_data_get(img, EINA_FALSE); + int w, h; + + if (!pixels) return; + evas_object_image_size_get(img, &w, &h); + if (lossy) + eet_data_image_write(ef, key, pixels, w, h, a, + 0, /* compr */ + 80, /* qual */ + EET_IMAGE_JPEG); + else + eet_data_image_write(ef, key, pixels, w, h, a, + EET_COMPRESSION_HI, /* compr */ + 0, /* qual */ + EET_IMAGE_LOSSLESS); + evas_object_image_data_set(img, pixels); // put pixels back we borrowed +} diff --git a/src/backends/default/thumb_util_search.c b/src/backends/default/thumb_util_search.c new file mode 100644 index 0000000..b2a164c --- /dev/null +++ b/src/backends/default/thumb_util_search.c @@ -0,0 +1,212 @@ +// search for +#include "thumb.h" + +// look for these divs: +// +//
+// +// data-xxx could be in any order so parse tag until end of tag and store +// each X=Y +// +// other option - low res but google served imaage in img tags +// +// + +typedef struct +{ + Eina_Bool in_div; + Eina_Bool in_img; + Eina_Hash *attr; + void (*cb) (void *data, Eina_List *results_orig, Eina_List *results_cached); + void *data; + Eina_List *results_orig; + Eina_List *results_cached; + int magic; +} State; + +static Eina_Bool +_cb_attr(void *data, const char *key, const char *val) +{ + State *state = data; + + // only if in div or img - add tag + if (state->in_div || state->in_img) + eina_hash_add(state->attr, key, strdup(val)); + return EINA_TRUE; +} + +static Eina_Bool +_cb_tag(void *data, Eina_Simple_XML_Type type, + const char *content, unsigned offset EINA_UNUSED, unsigned int len) +{ + State *state = data; + const char *tags, *ow, *oh, *img; + int w, h; + Search_Result *res; + + if (type == EINA_SIMPLE_XML_OPEN) + { // start sokme tag + state->in_div = EINA_FALSE; + state->in_img = EINA_FALSE; + if (state->attr) + { // clean up previous hash from previous tag if there + eina_hash_free(state->attr); + state->attr = NULL; + } + if (!strncmp(content, "div ", 4)) + { // we have a div - we're interested in those + state->in_div = EINA_TRUE; + state->attr = eina_hash_string_superfast_new(free); + } + else if (!strncmp(content, "img ", 4)) + { // we have an img - we're also interested in those + state->in_img = EINA_TRUE; + state->attr = eina_hash_string_superfast_new(free); + } + + if ((state->in_div) || (state->in_img)) + { // pars the tag attributes + tags = eina_simple_xml_tag_attributes_find(content, len); + if (tags) + { // pars a tag - div or img + eina_simple_xml_attributes_parse(tags, + len - (tags - content), + _cb_attr, state); + img = eina_hash_find(state->attr, "data-ou"); + if (img) + { // this is an origial url entry with size + ow = eina_hash_find(state->attr, "data-ow"); + oh = eina_hash_find(state->attr, "data-oh"); + if ((ow) && (oh)) + { // we need a width and height string and have them + w = atoi(ow); + h = atoi(oh); + res = calloc(1, sizeof(Search_Result)); + if (res) + { + res->w = w; + res->h = h; + res->url = strdup(img); + state->results_orig = + eina_list_append(state->results_orig, res); + } + } + } + else + { + img = eina_hash_find(state->attr, "data-src"); + if (img) + { // this is a cached/scaled down version withotu size + res = calloc(1, sizeof(Search_Result)); + if (res) + { + res->url = strdup(img); + state->results_cached = + eina_list_append(state->results_cached, res); + } + } + } + } + } + } + return EINA_TRUE; +} + +static void +_cb_query(void *data, const char *reply) +{ // parse the query result from searching + State *state = data; + Search_Result *res; + + if (!reply) goto err; + eina_simple_xml_parse(reply, strlen(reply), EINA_TRUE, _cb_tag, state); + if (state->attr) + { // clean up any left over attributes hash + eina_hash_free(state->attr); + state->attr = NULL; + } + state->cb(state->data, state->results_orig, state->results_cached); + // clean up lists of results we no longer need + EINA_LIST_FREE(state->results_orig, res) + { + free(res->url); + free(res); + } + EINA_LIST_FREE(state->results_cached, res) + { + free(res->url); + free(res); + } +err: + elm_exit(); +} + +static Eina_Bool +_search_append(Eina_Strbuf *sb, const char *str, Eina_Bool hadword) +{ // take input string "as typed" and convert to part of query with +'s + // like "this is a set of words" -> "this+is+a+set+of+words" + // hadwords indicates if we had words before this call so to add a + + // at the start to appeand to previous words + const char *s; + Eina_Bool word = EINA_FALSE; + + for (s = str; *s; s++) + { + // only use a-z, A-Z and 0-9 chars as words + if (((*s >= 'a') && (*s <= 'z')) || + ((*s >= 'A') && (*s <= 'Z')) || + ((*s >= '0') && (*s <= '9'))) + { + if (!word) + { // we're not inside a word now + if (hadword) + { // we had a word before so add + between words + eina_strbuf_append_char(sb, '+'); + word = EINA_FALSE; + } + } + eina_strbuf_append_char(sb, *s); + word = EINA_TRUE; + hadword = EINA_TRUE; + } + else word = EINA_FALSE; + // stop when we hit a . + if (*s == '.') break; + } + return hadword; +} + +void +thumb_search_image(const char *str, void (*cb) (void *data, Eina_List *results_orig, Eina_List *results_cached), void *data) +{ // search for images given the str string + Eina_Strbuf *query_buf; + const char *query; + State state = { 0 }; + + state.cb = cb; + state.data = data; + state.magic = 1234567; + + query_buf = eina_strbuf_new(); + // add beginning of query + eina_strbuf_append + (query_buf, + "http://www.google.com/search?as_st=y&tbm=isch&hl=en&as_q="); + // add a search string like typed in like "this is a search" + _search_append(query_buf, str, EINA_FALSE); + // add rest of query + eina_strbuf_append + (query_buf, + "&as_epq=&as_oq=&as_eq=&cr=&as_sitesearch=&safe=images&tbs=ift:jpg"); + query = eina_strbuf_string_get(query_buf); + // send that query off and handle it in _cb_query - max 8M + thumb_url_str_get(query, 8 * 1024 * 1024, _cb_query, &state); + elm_run(); + eina_strbuf_free(query_buf); +} diff --git a/src/backends/default/thumb_util_url.c b/src/backends/default/thumb_util_url.c new file mode 100644 index 0000000..ee1bd65 --- /dev/null +++ b/src/backends/default/thumb_util_url.c @@ -0,0 +1,126 @@ +#include "thumb.h" + +static Ecore_Con_Url *fetch = NULL; +static Ecore_Event_Handler *handle_data = NULL; +static Ecore_Event_Handler *handle_complete = NULL; + +static Eina_Strbuf *buf_str = NULL; +static size_t buf_str_max = 0; +static void *cb_str_data = NULL; +static void (*cb_str) (void *data, const char *result) = NULL; + +static Eina_Strbuf *buf_bin = NULL; +static size_t buf_bin_max = 0; +static void *cb_bin_data = NULL; +static void (*cb_bin) (void *data, const void *result, size_t size) = NULL; + +static Eina_Bool +_cb_http_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Con_Event_Url_Data *ev = event; + + if (ev->url_con != fetch) return EINA_TRUE; + if (buf_str) + { + eina_strbuf_append_length(buf_str, (char *)ev->data, (size_t)ev->size); + if (eina_strbuf_length_get(buf_str) > buf_str_max) + { // too big - abort whole fetch entirely + ecore_con_url_free(fetch); + fetch = NULL; + cb_str(cb_str_data, NULL); + cb_str = NULL; + cb_str_data = NULL; + eina_strbuf_free(buf_str); + buf_str = NULL; + } + } + else if (buf_bin) + { + eina_binbuf_append_length(buf_bin, (unsigned char *)ev->data, (size_t)ev->size); + if (eina_binbuf_length_get(buf_bin) > buf_bin_max) + { // too big - abort whole fetch entirely + ecore_con_url_free(fetch); + fetch = NULL; + cb_bin(cb_bin_data, NULL, 0); + cb_bin = NULL; + cb_bin_data = NULL; + eina_strbuf_free(buf_bin); + buf_bin = NULL; + } + } + return EINA_FALSE; +} + +static Eina_Bool +_cb_http_complete(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Con_Event_Url_Complete *ev = event; + Eina_Strbuf *buf; + + if (ev->url_con != fetch) return EINA_TRUE; + if (buf_str) + { + buf = buf_str; + buf_str = NULL; + ecore_con_url_free(fetch); + fetch = NULL; + cb_str(cb_str_data, + eina_strbuf_string_get(buf)); + cb_str = NULL; + cb_str_data = NULL; + eina_strbuf_free(buf); + } + else if (buf_bin) + { + buf = buf_bin; + buf_bin = NULL; + ecore_con_url_free(fetch); + fetch = NULL; + cb_bin(cb_bin_data, + eina_binbuf_string_get(buf), + eina_binbuf_length_get(buf)); + cb_bin = NULL; + cb_bin_data = NULL; + eina_binbuf_free(buf); + } + return EINA_FALSE; +} + +static Ecore_Con_Url * +_url_init(const char *url) +{ + Ecore_Con_Url *f; + + if (!handle_data) + handle_data = ecore_event_handler_add(ECORE_CON_EVENT_URL_DATA, + _cb_http_data, NULL); + if (!handle_complete) + handle_complete = ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE, + _cb_http_complete, NULL); + f = ecore_con_url_new(url); + ecore_con_url_additional_header_add + (f, "user-agent", + "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); + ecore_con_url_get(f); + return f; +} + +void +thumb_url_str_get(const char *url, size_t max, void (*cb) (void *data, const char *result), const void *data) +{ + cb_str_data = (void *)data; + cb_str = cb; + buf_str = eina_strbuf_new(); + buf_str_max = max; + fetch = _url_init(url); +} + +void +thumb_url_bin_get(const char *url, size_t max, void (*cb) (void *data, const void *result, size_t size), const void *data) +{ + cb_bin_data = (void *)data; + cb_bin = cb; + buf_bin = eina_binbuf_new(); + buf_bin_max = max; + fetch = _url_init(url); +} diff --git a/src/backends/default/thumb_video.c b/src/backends/default/thumb_video.c new file mode 100644 index 0000000..efca7db --- /dev/null +++ b/src/backends/default/thumb_video.c @@ -0,0 +1,444 @@ +// generate thumbnail for video files - look for posters on google or frames +#include "thumb.h" +#include + +// XXX: can do progressive resize down ie scale to 512 then take 512 and +// halve to 256 then halve it to 128 etc. rather than render from orig to +// target size.... + +static Evas_Object *im = NULL; +static Eina_Bool alpha = EINA_FALSE; +static int iw = 0, ih = 0; + +static Eina_List *results = NULL; + +static void *mem_data = NULL; +static int mem_size = 0; +static int query_pass = 0; + +static char *title = NULL; +static char *artist = NULL; +static char *album = NULL; +static double len = 0.0; +static double aspect = 0.0; + +typedef struct +{ + char *url; + int w, h; + unsigned long long fitness; +} Result; + +static void +_thumb_image_setup(void) +{ // create and show image + im = evas_object_image_filled_add(evas_object_evas_get(subwin)); + evas_object_show(im); +} + +static void +_thumb_image_mem_set(void *data, int size) +{ // set file to image, get size & alpha + evas_object_image_memfile_set(im, data, size, "jpg", NULL); + evas_object_image_size_get(im, &iw, &ih); + alpha = evas_object_image_alpha_get(im); +} + +static void +_thumb_image_file_set(const char *file, double pos) +{ // set file to image, get size & alpha + char buf[32]; + + if (pos >= 0.0) + { + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)(pos * 1000.0)); + evas_object_image_file_set(im, file, buf); + } + else evas_object_image_file_set(im, file, NULL); + evas_object_image_size_get(im, &iw, &ih); + alpha = evas_object_image_alpha_get(im); +} + +static Eina_Bool +_cb_vid_open_done_timeout(void *data EINA_UNUSED) +{ + elm_exit(); + return EINA_FALSE; +} + +static void +_cb_vid_open_done(void *data EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED) +{ // we finished opening - get netadata also size/aspect + const char *s; + int w = 0, h = 0; + + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_TITLE); + if (s) title = strdup(s); + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ARTIST); + if (s) artist = strdup(s); + s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ALBUM); + if (s) album = strdup(s); + len = emotion_object_play_length_get(obj); + aspect = emotion_object_ratio_get(obj); + if (aspect <= 0.0) + { + emotion_object_size_get(obj, &w, &h); + if (h > 0) aspect = (double)w / (double)h; + } + // finish loop + elm_exit(); +} + +static void +_video_metadata_get(const char *path) +{ + Ecore_Timer *t; + Evas_Object *o = emotion_object_add(evas_object_evas_get(subwin)); + + evas_object_smart_callback_add(o, "open_done", _cb_vid_open_done, NULL); + emotion_object_file_set(o, path); + emotion_object_audio_mute_set(o, EINA_TRUE); + emotion_object_audio_volume_set(o, 0.0); + // a timeout for the loop + t = ecore_timer_add(10.0, _cb_vid_open_done_timeout, NULL); + elm_run(); + ecore_timer_del(t); + evas_object_del(o); +} + +static void +_cb_results(void *data EINA_UNUSED, Eina_List *results_orig, Eina_List *results_cached EINA_UNUSED) +{ + Eina_List *l; + Search_Result *res; + Result *r; + unsigned long long fit_size, fit_ratio2to3, fit_jpg, fit_listpos, fit_pass; + + // we need to re-score results first ratio 2:3 better than not suqare + // next - higher est better than lower rest + // ends in .jpg, .jpeg better than not + fit_listpos = 100; + EINA_LIST_FOREACH(results_orig, l, res) + { + // skip results that are 0 sized or with no url + if ((res->w <= 0) || (res->h <= 0) || (!res->url)) continue; + // new result + r = calloc(1, sizeof(Result)); + if (!r) continue; + r->w = res->w; + r->h = res->h; + r->url = strdup(res->url); + // jpegs preferred + if ((eina_fnmatch("*.jpg", res->url, EINA_FNMATCH_CASEFOLD)) || + (eina_fnmatch("*.jpeg", res->url, EINA_FNMATCH_CASEFOLD)) || + (eina_fnmatch("*.jpe", res->url, EINA_FNMATCH_CASEFOLD))) + fit_jpg = 5000; + else + fit_jpg = 100; + // bigger is better + fit_size = (r->w / 10) * (r->h / 10); + // if it's bigger than 1000x1000 it's not really better + if (fit_size > 10000) fit_size = 10000; + // 2:3 poster better + fit_ratio2to3 = (100 * r->w * 3) / (r->h * 2); + if (fit_ratio2to3 > 100) fit_ratio2to3 = (100 * r->h * 2) / (r->w * 3); + fit_ratio2to3 *= 10; // ratio is VERY important + // first pass gets a higher multiplier than latter passes + fit_pass = (10 - query_pass) * 1000; + // store fitness and result + r->fitness = fit_listpos * fit_size * fit_ratio2to3 * fit_jpg * fit_pass; + results = eina_list_append(results, r); + // list position fitness goes down by .9 of previous list pos fitness + fit_listpos = (90 * fit_listpos) / 100; + if (fit_listpos < 1) fit_listpos = 1; + } +} + +static int +_cb_fitness_sort(const void *data1, const void *data2) +{ + const Result *r1 = data1, *r2 = data2; + + if (r1->fitness < r2->fitness) return 1; + else if (r1->fitness > r2->fitness) return -1; + return 0; +} + +static void +_cb_url_bin(void *data EINA_UNUSED, const void *result, size_t size) +{ // handle in memory fetch of image + // too big - 64M + if (size > (64 * 1024 * 1024)) return; + if (mem_data) free(mem_data); + mem_data = malloc(size); + mem_size = size; + memcpy(mem_data, result, size); + elm_exit(); +} + +static void +_thumb_online_search(const char *path) +{ + Eina_Strbuf *query_buf; + const char *file, *ext; + Eina_List *l; + Result *r; + int i; + + // get file and where extension starts to be removed + file = ecore_file_file_get(path); + ext = strchr(file, '.'); + + if ((title) || (album) || (artist)) + { + query_buf = eina_strbuf_new(); + if (artist) + { + eina_strbuf_append(query_buf, artist); + eina_strbuf_append(query_buf, " "); + } + if (artist) + { + eina_strbuf_append(query_buf, artist); + eina_strbuf_append(query_buf, " "); + } + if (title) + { + eina_strbuf_append(query_buf, title); + eina_strbuf_append(query_buf, " "); + } + eina_strbuf_append(query_buf, " movie poster"); + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + // we have real metasata - make filename searches lhave lower + // fitness by bumping query pass + query_pass += 4; + free(title); + free(album); + free(artist); + } + + // search using filename as our search + // search for munged filename + "album art" + query_buf = eina_strbuf_new(); + if (ext) // append all but extension and dot + eina_strbuf_append_n(query_buf, file, ext - file); + else // append the whole filename + eina_strbuf_append(query_buf, file); + eina_strbuf_append(query_buf, " movie poster"); + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + query_pass++; + + // now search for just the munged filename without any extra string + query_buf = eina_strbuf_new(); + if (ext) // append all but extension and dot + eina_strbuf_append_n(query_buf, file, ext - file); + else // append the whole filename + eina_strbuf_append(query_buf, file); + thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL); + eina_strbuf_free(query_buf); + + // sort results by fitness + results = eina_list_sort(results, eina_list_count(results), + _cb_fitness_sort); + + i = 0; + EINA_LIST_FOREACH(results, l, r) + { + // get image max 64M + thumb_url_bin_get(r->url, 64 * 1024 * 1024, _cb_url_bin, NULL); + elm_run(); + if (mem_data) + { + // add filled image to then size accordingly + _thumb_image_mem_set(mem_data, mem_size); + free(mem_data); + mem_data = NULL; + if ((iw > 0) && (ih > 0)) break; + i++; + } + // tried 10 - give up + if (i >= 10) break; + } + if (mem_data) free(mem_data); +} + +static char * +_thumb_explicit_find(const char *path) +{ + char *tmp = alloca(strlen(path) + 1 + 100); + char *dir, *fraw, *s; + const char *fname, *e; + const char *ext[] = { + "png", "PNG", + "jpg", "JPG", + "jpeg", "JPEG", + "jpe", "JPE", + NULL }; + int i; + + // from here example comments assume /dir/file.mp3 as the path + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.mp3.png etc. + sprintf(tmp, "%s.%s", path, e); + if (ecore_file_exists(tmp)) return strdup(tmp); + } + + dir = ecore_file_dir_get(path); + if (!dir) + { // if no dir we are /file.mp3 thus "" works find for following code + dir = strdup(""); + if (!dir) return NULL; + } + fname = ecore_file_file_get(path); + if (!fname) + { // this shouldn't happen - but handle it anyway + free(dir); + return NULL; + } + fraw = strdup(fname); // fraw will be filename for e.g. filename.mp3 + if (!fraw) + { + free(dir); + return NULL; + } + s = strrchr(fraw, '.'); + if (s) *s = 0; + + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/file.mp3.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fname, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.file.png etc. + sprintf(tmp, "%s/.%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.file.mp3.png etc. + sprintf(tmp, "%s/.thumb/%s.%s", dir, fname, e); + if (ecore_file_exists(tmp)) goto found; + } + for (i = 0; (e = ext[i]) && e; i++) + { // /dir/.thumb/file.png etc. + sprintf(tmp, "%s/.thumb/%s.%s", dir, fraw, e); + if (ecore_file_exists(tmp)) goto found; + } + + free(dir); + free(fraw); + return NULL; +found: + free(dir); + free(fraw); + return strdup(tmp); +} + +static void +_thumb_file_snap_pos_set(Eet_File *ef, const char *path, int snap, double pos) +{ + char buf[128]; + int w, h; + + _thumb_image_file_set(path, pos); + snprintf(buf, sizeof(buf), "image/snap/%i", snap); + w = iw; h = ih; scale(&w, &h, 512, 512, EINA_TRUE); + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); +} + +int +thumb_video(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED) +{ + const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; + int w, h, i; + char buf[128]; + char *thumb_file; + double p; + + _thumb_image_setup(); + + // look for an explicitly "requested" file for the thumb path + thumb_file = _thumb_explicit_find(path); + + // if we didn't find an explicit matching thumb path in dir or nearby... + if (thumb_file) _thumb_image_file_set(thumb_file, -1); + + // explicit thumb not found or the load failed as image size is not sane + if ((iw <= 0) || (ih < 0)) + { + _video_metadata_get(path); + + if ((len > (65.0 * 60.0)) && // more than 65 mins - like movies + (aspect > 1.6)) // is 16:9 or wider like a movie + _thumb_online_search(path); + } + // if size is bunk - we hven;'t found something yet + if ((iw <= 0) || (ih < 0)) _thumb_image_file_set(path, len / 2.0); + + // if size is bunk - we can't load it... + if ((iw <= 0) || (ih < 0)) return 2; + + // write a big 1024x1024 preview (but no larger than the original image) + w = iw; h = ih; scale(&w, &h, 1024, 1024, EINA_FALSE); + + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out preview size + snprintf(buf, sizeof(buf), "image/preview"); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + snprintf(buf, sizeof(buf), "%i %i", w, h); + eet_write(ef, "image/preview/size", buf, strlen(buf) + 1, + EET_COMPRESSION_NONE); + + // multiple thumb sizes so can load/pick the best one at runtime + for (i = 0; sizes[i] != 0; i++) + { + // scale down and keep aspect + w = iw; h = ih; scale(&w, &h, sizes[i], sizes[i], EINA_FALSE); + // resize to target size + evas_object_resize(im, w, h); + evas_object_resize(subwin, w, h); + // render our current state and pick up pixel results + elm_win_render(subwin); + // save out thumb size + snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]); + thumb_image_write(ef, buf, image, alpha, EINA_TRUE); + } + + evas_object_del(im); + im = NULL; + + _thumb_image_setup(); + if (len < (1 * 60.0)) // less than 1 min + { + for (i = 0, p = 0.25; i < 3; i++, p += 0.25) + _thumb_file_snap_pos_set(ef, path, i, p * len); + } + else if (len < (10 * 60.0)) // less than 10 min + { + for (i = 0, p = 0.10; i < 9; i++, p += 0.10) + _thumb_file_snap_pos_set(ef, path, i, p * len); + } + else + { + for (i = 0, p = 0.05; i < 19; i++, p += 0.05) + _thumb_file_snap_pos_set(ef, path, i, p * len); + } + + return 0; +} diff --git a/src/backends/meson.build b/src/backends/meson.build new file mode 100644 index 0000000..7cd9a71 --- /dev/null +++ b/src/backends/meson.build @@ -0,0 +1 @@ +subdir('default') diff --git a/src/efm/efm.c b/src/efm/efm.c new file mode 100644 index 0000000..ed1be02 --- /dev/null +++ b/src/efm/efm.c @@ -0,0 +1,1604 @@ +// the efm view - intended to be put in an elm scroller - though could +// work without and display a set of file icons in some view mode and +// allow them to update, sort, drag and drop, rename, delete, copy/paste, +// etc. etc. +// +// maximum # of files in a dir that is sane: 10,000 +// maximum number of files in a dir on a real system to be fast with: 3,000 +#include "efm.h" +#include "efm_icon.h" +#include "cmd.h" +#include "sort.h" + +// maximum number of icons in a block +#define BLOCK_MAX 64 +#define SCROLL_SEL_TIMER 0.2 +#define SCROLL_DND_TIMER 0.2 +#define FOCUS_ANIM_TIME 0.2 +#define DND_OVER_OPEN_TIMER 1.0 +#define ICON_LONGPRESS_TIMER 1.0 + +#include "efm_structs.h" + +int _log_dom = -1; + +static Eina_List *_pending_exe_dels = NULL; +static Evas_Smart *_smart = NULL; +static Evas_Smart_Class _sc = EVAS_SMART_CLASS_INIT_NULL; +static Evas_Smart_Class _sc_parent = EVAS_SMART_CLASS_INIT_NULL; + +#define ENTRY Smart_Data *sd = evas_object_smart_data_get(obj); if (!sd) return + +#include "efm_private.h" +#include "efm_back_end.c" +#include "efm_util.c" +#include "efm_dnd.c" + +static void _cb_canvas_resize(void *data, Evas *e, void *event_info EINA_UNUSED); +static void _reposition_detail_header_items(Smart_Data *sd); + +static void +_cb_lost_selection(void *data, Elm_Sel_Type selection EINA_UNUSED) +{ + Smart_Data *sd = evas_object_smart_data_get(data); + + if (sd->cnp_have) + { + sd->cnp_have = EINA_FALSE; + sd->cnp_cut = EINA_FALSE; + printf("XXX: lost select\n"); + } +} + +static void +_cnp_copy_files(Smart_Data *sd) +{ + Eina_Strbuf *strbuf; + const char *str = NULL; + + strbuf = eina_strbuf_new(); + if (!strbuf) return; + if (_selected_icons_uri_strbuf_append(sd, strbuf)) + { + str = eina_strbuf_string_get(strbuf); + if (str) + { + elm_cnp_selection_set(sd->o_scroller, + ELM_SEL_TYPE_CLIPBOARD, + ELM_SEL_FORMAT_URILIST, + str, strlen(str)); + elm_cnp_selection_loss_callback_set(sd->o_scroller, + ELM_SEL_TYPE_CLIPBOARD, + _cb_lost_selection, + sd->o_smart); + } + } + if (str) printf("XXX: COPY: [%s]\n", str); + else printf("XXX: COPY: no str\n"); + eina_strbuf_free(strbuf); +} + +static Eina_Bool +_cb_sel_get(void *data, + Evas_Object *_obj EINA_UNUSED, + Elm_Selection_Data *ev) +{ + Smart_Data *sd = evas_object_smart_data_get(data); + + if (!sd) return EINA_TRUE; + printf("XXX: GET SEL %i\n", ev->format); + if (ev->format & ELM_SEL_FORMAT_URILIST) + { + char **plist, **p, *esc, *tmp; + + tmp = malloc(ev->len + 1); + if (tmp) + { + memcpy(tmp, ev->data, ev->len); + tmp[ev->len] = 0; + plist = eina_str_split(tmp, "\n", -1); + for (p = plist; *p != NULL; ++p) + { + if (**p) + { + esc = _escape_parse(*p); + if (!esc) continue; + printf("XXX: PASTE FILE: [%s]\n", esc); + } + } + free(*plist); + free(plist); + free(tmp); + } + } + return EINA_TRUE; +} + +static void +_cnp_paste_files(Smart_Data *sd) +{ + elm_cnp_selection_get(sd->o_scroller, ELM_SEL_TYPE_CLIPBOARD, + ELM_SEL_FORMAT_URILIST, + _cb_sel_get, sd->o_smart); +} + +static void +_cb_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Smart_Data *sd = data; + Evas_Event_Key_Down *ev = event_info; + Eina_Bool handled = EINA_FALSE; + + printf("key: [%s]\n", ev->key); + // these keys we use/steal for navigation, so want them for sure + if (!strcmp(ev->key, "Up")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_UP); + else if (!strcmp(ev->key, "Down")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_DOWN); + else if (!strcmp(ev->key, "Left")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_LEFT); + else if (!strcmp(ev->key, "Right")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_RIGHT); + else if (!strcmp(ev->key, "Prior")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_PGUP); + else if (!strcmp(ev->key, "Next")) handled = _icon_focus_dir(sd, EFM_FOCUS_DIR_PGDN); + else if ((!strcmp(ev->key, "Return")) || + (!strcmp(ev->key, "KP_Enter"))) + { + if (sd->last_focused) + { + Icon *icon = sd->last_focused; + + if (!icon->selected) _icon_select(icon); + // XXX: handle "open" of all selected files/icons (double-click) + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "space")) + { + if (sd->last_focused) + { + Icon *icon = sd->last_focused; + + if (evas_key_modifier_is_set(ev->modifiers, "Shift")) + { // range select + if (icon->sd->last_selected) + _select_range(icon->sd->last_selected, icon); + else + _icon_select(icon); + } + else if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { // multi-single select toggle + if (!icon->selected) _icon_select(icon); + else _icon_unselect(icon); + } + else + { // select just one file so unselect previous files + _unselect_all(icon->sd); + if (!icon->selected) _icon_select(icon); + else _icon_unselect(icon); + } + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "Escape")) + { + handled = _unselect_all(sd); + } +// hmm - should we handle this? +// else if (!strcmp(ev->key, "Backspace")) +// { +// } + else if (!strcmp(ev->key, "Delete")) + { + // XXX: delete file + handled = EINA_TRUE; + } + else if (!strcmp(ev->key, "Home")) + { + if (sd->icons) + { + sd->last_focused_before = sd->last_focused; + sd->last_focused = sd->icons->data; + _icon_focus(sd); + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "End")) + { + if (sd->icons) + { + sd->last_focused_before = sd->last_focused; + sd->last_focused = eina_list_last(sd->icons)->data; + _icon_focus(sd); + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "c")) + { + if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { + sd->cnp_have = EINA_TRUE; + sd->cnp_cut = EINA_FALSE; + _cnp_copy_files(sd); + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "x")) + { + if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { + sd->cnp_have = EINA_TRUE; + sd->cnp_cut = EINA_TRUE; + _cnp_copy_files(sd); + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "v")) + { + if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { + _cnp_paste_files(sd); + handled = EINA_TRUE; + } + } + else if (!strcmp(ev->key, "Insert")) + { + if (evas_key_modifier_is_set(ev->modifiers, "Shift")) + { + _cnp_paste_files(sd); + handled = EINA_TRUE; + } + } + // flag on hold if we use the event - otherwise pass it on + if (handled) + { + ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD; + sd->key_control = EINA_TRUE; + } + printf("KEY: [%c] [%s]\n", handled ? '#' : ' ', ev->key); +} + +static void +_cb_refocus(void *data) +{ + Smart_Data *sd = data; + + sd->refocus_job = NULL; + if (sd->rename_icon) return; + printf("REFOCUS\n"); + if (sd->focused) + { + printf(" focus widget\n"); + evas_object_focus_set(sd->o_clip, EINA_TRUE); + if (sd->key_control) _icon_focus_show(sd); + } + else + { + printf(" unfocused widget\n"); + _icon_focus_hide(sd); + // XXX: exit rename mode if on... + } +} + +static void +_cb_focus_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Smart_Data *sd = data; + + printf("FOCUS OUT CLIP\n"); + if (sd->refocus_job) ecore_job_del(sd->refocus_job); + sd->refocus_job = ecore_job_add(_cb_refocus, sd); +} + +static void +_cb_back_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Down *ev = event_info; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return; + // XXX: exit rename mode if on... + elm_object_focus_set(sd->o_scroller, EINA_TRUE); + if (ev->button == 1) + { + sd->back_down = EINA_TRUE; + sd->back_down_x = ev->canvas.x; + sd->back_down_y = ev->canvas.y; + sd->sel_x1 = sd->back_down_x - sd->geom.x; + sd->sel_y1 = sd->back_down_y - sd->geom.y; + } + if (sd->rename_icon) _icon_rename_end(sd->rename_icon); + _icon_focus_hide(sd); +} + +static void +_cb_back_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Up *ev = event_info; + Evas_Coord dx, dy; + Eina_Bool dragged = EINA_FALSE; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return; + if (ev->button == 1) + { + // XXX: kill longpress timer + sd->back_down = EINA_FALSE; + _icon_focus_hide(sd); + if (!sd->sel_show) + { + dx = ev->canvas.x - sd->back_down_x; + dy = ev->canvas.y - sd->back_down_y; + if (((dx * dx) + (dy * dy)) > (5 * 5)) dragged = EINA_TRUE; + if (!dragged) + { + if ((!evas_key_modifier_is_set(ev->modifiers, "Shift")) && + (!evas_key_modifier_is_set(ev->modifiers, "Control"))) + _unselect_all(sd); + } + } + else + { + Eina_List *bl, *il; + Icon *icon; + Block *block; + Eina_Rectangle r = _efm_sel_rect_get(sd); + + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (eina_rectangles_intersect(&r, &(block->bounds))) + { + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (eina_rectangles_intersect(&r, &(icon->geom))) + _icon_select(icon); + } + } + } + sd->sel_show = EINA_FALSE; + evas_object_hide(sd->o_sel); + if (sd->scroll_timer) + { + ecore_timer_del(sd->scroll_timer); + sd->scroll_timer = NULL; + } + } + } + else if (ev->button == 3) + { // right mouse click + // XXX: handle ctxt menu for window + printf("XXX: right mouse back\n"); + } +} + +static Eina_Bool +_cb_sel_bounds_scroll_timer(void *data) +{ + Smart_Data *sd = data; + + if (sd->o_scroller) + { + Evas_Coord sx, sy, sw, sh; + + evas_object_geometry_get(sd->o_scroller, &sx, &sy, &sw, &sh); + if ((sd->back_x < sx) || (sd->back_y < sy) || + (sd->back_x >= (sx + sw)) || (sd->back_y >= (sy + sh))) + { + elm_scroller_region_bring_in(sd->o_scroller, + sd->sel_x2, sd->sel_y2, 1, 1); + } + else + { + sd->scroll_timer = NULL; + return EINA_FALSE; + } + } + return EINA_TRUE; +} + +static void +_cb_back_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Move *ev = event_info; + Evas_Coord dx, dy; + Eina_Bool dragged = EINA_FALSE; + + if (!sd->back_down) return; + // XXX: kill longpress timer + dx = ev->cur.canvas.x - sd->back_down_x; + dy = ev->cur.canvas.y - sd->back_down_y; + if (((dx * dx) + (dy * dy)) > (5 * 5)) dragged = EINA_TRUE; + if (!dragged) return; + if ((!evas_key_modifier_is_set(ev->modifiers, "Shift")) && + (!evas_key_modifier_is_set(ev->modifiers, "Control"))) + _unselect_all(sd); + if (!sd->sel_show) + { + _icon_focus_hide(sd); + sd->sel_show = EINA_TRUE; + evas_object_show(sd->o_sel); + evas_object_raise(sd->o_sel); + } + sd->back_x = ev->cur.canvas.x; + sd->back_y = ev->cur.canvas.y; + sd->sel_x2 = ev->cur.canvas.x - sd->geom.x; + sd->sel_y2 = ev->cur.canvas.y - sd->geom.y; + _efm_sel_position(sd); + if (!sd->scroll_timer) + sd->scroll_timer = ecore_timer_add + (SCROLL_SEL_TIMER, _cb_sel_bounds_scroll_timer, sd); +} + +static void +_reposition_detail_bars(Smart_Data *sd) +{ + int i; + Evas_Coord x, w; + + for (i = 0; i < 6; i++) + { + evas_object_geometry_get(sd->o_list_detail_swallow[i], + &x, NULL, &w, NULL); + x -= sd->geom.x; + elm_grid_pack(sd->o_overlay_grid, sd->o_list_detail[i], + x, 0, w, 100); + } + _reposition_detail_header_items(sd); +} + +static void +_cb_overlay_detail_swallow_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + _reposition_detail_bars(data); +} + +static void +_cb_overlay_detail_swallow_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + _reposition_detail_bars(data); +} + +static void +_cb_overlay_grid_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + Evas_Coord w; + + evas_object_geometry_get(obj, NULL, NULL, &w, NULL); + elm_grid_size_set(sd->o_overlay_grid, w, 100); +} + +static void +_cb_overlay_detail_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Down *ev = info; + int i; + + if (ev->button == 1) + { + sd->detail_down = EINA_TRUE; + sd->detail_down_x = ev->canvas.x; + sd->detail_down_y = ev->canvas.y; + for (i = 0; i < 6; i++) + { + if (obj == sd->o_list_detail[i]) + { + sd->detail_down_start_min_w = + sd->detail_min_w[i] * _scale_get(sd); + break; + } + } + } +} + +static void +_cb_overlay_detail_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Up *ev = info; + + if (ev->button == 1) + { + if (sd->detail_down) + { + sd->detail_down = EINA_FALSE; + } + } +} + +static void +_cb_overlay_detail_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *info) +{ + Smart_Data *sd = data; + Evas_Event_Mouse_Move *ev = info; + Evas_Object *o; + char buf[128]; + int i; + + if (sd->detail_down) + { + for (i = 0; i < 6; i++) + { + if (obj == sd->o_list_detail[i]) + { + o = sd->o_list_detail_swallow[i]; + sd->detail_min_w[i] = + (sd->detail_down_start_min_w - + (ev->cur.canvas.x - sd->detail_down_x)) / _scale_get(sd); + if (sd->detail_min_w[i] < 0) sd->detail_min_w[i] = 0; + snprintf(buf, sizeof(buf), "e.swallow.detail%i", i + 1); + evas_object_size_hint_min_set(o, sd->detail_min_w[i] * _scale_get(sd), 0); + edje_object_part_swallow(sd->o_list_detailed_dummy, buf, o); + _reposition_detail_bars(sd); + _detail_realized_items_resize(sd); + break; + } + } + } +} + +static void +_add_overlay_objects(Smart_Data *sd) +{ + Evas_Object *o; + Evas *e; + const char *theme_edj_file, *grp; + char buf[128]; + int i; + + e = evas_object_evas_get(sd->o_scroller); + + for (i = 0; i < 6; i++) + { + if (sd->o_list_detail_swallow[i]) + { + evas_object_del(sd->o_list_detail_swallow[i]); + sd->o_list_detail_swallow[i] = NULL; + } + } + if (sd->o_overlay_info) + { + evas_object_del(sd->o_overlay_info); + sd->o_overlay_info = NULL; + } + if (sd->o_overlay_grid) + { + evas_object_del(sd->o_overlay_grid); + sd->o_overlay_grid = NULL; + } + if (sd->o_overlay_grid_fill) + { + evas_object_del(sd->o_overlay_grid_fill); + sd->o_overlay_grid_fill = NULL; + } + for (i = 0; i < 6; i++) + { + sd->o_list_detail[i] = NULL; + } + + sd->o_overlay_grid_fill = o = elm_grid_add(sd->o_scroller); + elm_grid_size_set(o, 1, 1); + elm_object_part_content_set(sd->o_scroller, "elm.swallow.overlay", o); + evas_object_show(o); + + sd->o_overlay_grid = o = elm_grid_add(sd->o_scroller); + evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, + _cb_overlay_grid_resize, sd); + elm_grid_size_set(o, 100, 100); + elm_grid_pack(sd->o_overlay_grid_fill, o, 0, 0, 1, 1); + evas_object_show(o); + + sd->o_overlay_info = o = edje_object_add(e); + grp = "e/fileman/default/overlay"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + elm_grid_pack(sd->o_overlay_grid_fill, o, 0, 0, 1, 1); + evas_object_show(o); + + for (i = 0; i < 6; i++) + { + sd->o_list_detail[i] = o = edje_object_add(e); + grp = "e/fileman/default/detail-sizer"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + elm_grid_pack(sd->o_overlay_grid, o, 100 - i - 10, 0, 1, 100); + if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + evas_object_show(o); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, + _cb_overlay_detail_mouse_down, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, + _cb_overlay_detail_mouse_up, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, + _cb_overlay_detail_mouse_move, sd); + + sd->o_list_detail_swallow[i] = o = evas_object_rectangle_add(e); + snprintf(buf, sizeof(buf), "e.swallow.detail%i", i + 1); + evas_object_size_hint_min_set(o, sd->detail_min_w[i] * _scale_get(sd), 0); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOVE, + _cb_overlay_detail_swallow_move, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, + _cb_overlay_detail_swallow_resize, sd); + edje_object_part_swallow(sd->o_list_detailed_dummy, buf, o); + evas_object_show(o); + } +} + +// gui code +static void +_smart_add(Evas_Object *obj) +{ // create a new efm view + Smart_Data *sd; + Evas_Object *o; + Evas *e; + const char *theme_edj_file, *grp; + + if (_log_dom < 0) + _log_dom = eina_log_domain_register("efm", EINA_COLOR_WHITE); + sd = calloc(1, sizeof(Smart_Data)); + if (!sd) return; + evas_object_smart_data_set(obj, sd); + + _sc_parent.add(obj); + + e = evas_object_evas_get(obj); + + evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_VIEWPORT_RESIZE, + _cb_canvas_resize, sd); + + sd->config.view_mode = EFM_VIEW_MODE_ICONS; + sd->config.sort_mode = EFM_SORT_MODE_NAME | + EFM_SORT_MODE_LABEL_NOT_PATH | + EFM_SORT_MODE_DIRS_FIRST; + sd->config.icon_size = 40; + sd->detail_min_w[0] = 50; + sd->detail_min_w[1] = 120; + sd->detail_min_w[2] = 130; + sd->detail_min_w[3] = 60; + sd->detail_min_w[4] = 60; + sd->detail_min_w[5] = 120; + + evas_object_size_hint_min_set(obj, 1, 1); + + sd->o_smart = obj; + + sd->o_clip = o = evas_object_rectangle_add(e); + evas_object_smart_member_add(o, obj); + evas_object_color_set(o, 255, 255, 255, 255); + evas_object_show(o); + + sd->o_back = o = evas_object_rectangle_add(e); + evas_object_color_set(o, 0, 0, 0, 0); + evas_object_smart_member_add(o, obj); + evas_object_clip_set(o, sd->o_clip); + evas_object_show(o); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, + _cb_back_mouse_down, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, + _cb_back_mouse_up, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, + _cb_back_mouse_move, sd); + + sd->o_sel = o = edje_object_add(e); + evas_object_pass_events_set(o, EINA_TRUE); + evas_object_smart_member_add(o, obj); + evas_object_clip_set(o, sd->o_clip); + grp = "e/fileman/default/rubberband"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + + sd->o_focus = o = edje_object_add(e); + evas_object_pass_events_set(o, EINA_TRUE); + evas_object_smart_member_add(o, obj); + evas_object_clip_set(o, sd->o_clip); + grp = "elm/focus_highlight/top/default"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + + sd->o_over = o = edje_object_add(e); + evas_object_pass_events_set(o, EINA_TRUE); + evas_object_smart_member_add(o, obj); + evas_object_clip_set(o, sd->o_clip); + grp = "e/fileman/default/list/drop_in"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + + sd->handler_exe_del = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, + _cb_exe_del, sd); + sd->handler_exe_data = ecore_event_handler_add(ECORE_EXE_EVENT_DATA, + _cb_exe_data, sd); + + sd->thread_data = calloc(1, sizeof(Smart_Data_Thread)); + sd->thread_data->sd = sd; + sd->thread_data->thq = eina_thread_queue_new(); + sd->thread = ecore_thread_feedback_run(_cb_thread_main, _cb_thread_notify, + _cb_thread_done, _cb_thread_done, + sd->thread_data, EINA_TRUE); + evas_object_event_callback_priority_add + (sd->o_clip, EVAS_CALLBACK_KEY_DOWN, -100, _cb_key_down, sd); + evas_object_event_callback_add + (sd->o_clip, EVAS_CALLBACK_FOCUS_OUT, _cb_focus_out, sd); +} + +static void +_smart_del(Evas_Object *obj) +{ // delete/free efm view + Block *block; + Icon *icon; + Evas *e; + int i; + ENTRY; + + e = evas_object_evas_get(obj); + evas_event_callback_del_full(e, EVAS_CALLBACK_CANVAS_VIEWPORT_RESIZE, + _cb_canvas_resize, sd); + if (sd->size_bars_update_job) + { + ecore_job_del(sd->size_bars_update_job); + sd->size_bars_update_job = NULL; + } + if (sd->size_max_update_job) + { + ecore_job_del(sd->size_max_update_job); + sd->size_max_update_job = NULL; + } + if (sd->drag_icon) + { + if (sd->o_scroller) elm_drag_cancel(sd->o_scroller); + sd->drag_icon->sd = NULL; + sd->drag_icon = NULL; + } + if (sd->refocus_job) + { + ecore_job_del(sd->refocus_job); + sd->refocus_job = NULL; + } + if (sd->thread) + { + if (sd->thread_data) sd->thread_data->sd = NULL; + ecore_thread_cancel(sd->thread); + sd->thread = NULL; + } + eina_stringshare_replace(&(sd->path), NULL); + if (sd->exe_open) + { + Pending_Exe_Del *pend = calloc(1, sizeof(Pending_Exe_Del)); + + if (pend) + { + pend->exe = sd->exe_open; + pend->timer = ecore_timer_add(5.0, _cb_exe_pending_timer, pend); + _pending_exe_dels = eina_list_append(_pending_exe_dels, pend); + } + ecore_exe_interrupt(sd->exe_open); + sd->exe_open = NULL; + } + if (sd->handler_exe_del) + { + ecore_event_handler_del(sd->handler_exe_del); + sd->handler_exe_del = NULL; + } + if (sd->handler_exe_data) + { + ecore_event_handler_del(sd->handler_exe_data); + sd->handler_exe_data = NULL; + } + EINA_LIST_FREE(sd->blocks, block) _block_free(block); + EINA_LIST_FREE(sd->icons, icon) _icon_free(icon); + for (i = 0; i < 6; i++) + { + if (sd->o_list_detail_swallow[i]) + { + evas_object_del(sd->o_list_detail_swallow[i]); + sd->o_list_detail_swallow[i] = NULL; + } + } + if (sd->o_detail_header) + { + evas_object_del(sd->o_detail_header); + sd->o_detail_header = NULL; + } + if (sd->o_clip) + { + evas_object_del(sd->o_clip); + sd->o_clip = NULL; + } + if (sd->o_focus) + { + evas_object_del(sd->o_focus); + sd->o_focus = NULL; + } + if (sd->o_back) + { + evas_object_del(sd->o_back); + sd->o_back = NULL; + } + if (sd->o_sel) + { + evas_object_del(sd->o_sel); + sd->o_sel = NULL; + } + if (sd->o_over) + { + evas_object_del(sd->o_over); + sd->o_over = NULL; + } + if (sd->o_list_detailed_dummy) + { + evas_object_del(sd->o_list_detailed_dummy); + sd->o_list_detailed_dummy = NULL; + } + if (sd->o_overlay_grid) + { + evas_object_del(sd->o_overlay_grid); + sd->o_overlay_grid = NULL; + } + if (sd->o_overlay_grid_fill) + { + evas_object_del(sd->o_overlay_grid_fill); + sd->o_overlay_grid_fill = NULL; + } + if (sd->reblock_job) + { + ecore_job_del(sd->reblock_job); + sd->reblock_job = NULL; + } + if (sd->focus_animator) + { + ecore_animator_del(sd->focus_animator); + sd->focus_animator = NULL; + } + if (sd->scroll_timer) + { + ecore_timer_del(sd->scroll_timer); + sd->scroll_timer = NULL; + } + if (sd->dnd_over_open_timer) + { + ecore_timer_del(sd->dnd_over_open_timer); + sd->dnd_over_open_timer = NULL; + } + if (sd->dnd_scroll_timer) + { + ecore_timer_del(sd->dnd_scroll_timer); + sd->dnd_scroll_timer = NULL; + } + + // XXX: anything special with scroller? + sd->o_scroller = NULL; + sd->o_smart = NULL; + + _sc_parent.del(obj); + evas_object_smart_data_set(obj, NULL); +} + +static void +_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y) +{ // efm view object moved + ENTRY; + + if ((sd->geom.x == x) && (sd->geom.y == y)) return; + sd->geom.x = x; + sd->geom.y = y; + evas_object_smart_changed(obj); + evas_object_move(sd->o_clip, x, y); + evas_object_move(sd->o_list_detailed_dummy, x, y); + if (sd->sel_show) + { + sd->sel_x2 = sd->back_x - sd->geom.x; + sd->sel_y2 = sd->back_y - sd->geom.y; + _efm_sel_position(sd); + if (!sd->scroll_timer) + sd->scroll_timer = ecore_timer_add + (SCROLL_SEL_TIMER, _cb_sel_bounds_scroll_timer, sd); + } +} + +static void +_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h) +{ // efm veiw object resized + Eina_Bool width_change = EINA_FALSE; + ENTRY; + + if ((sd->geom.w == w) && (sd->geom.h == h)) return; + if (w != sd->geom.w) width_change = EINA_TRUE; + sd->geom.w = w; + sd->geom.h = h; + evas_object_smart_changed(obj); + evas_object_resize(sd->o_clip, w, h); + // if width changed we have to re-flow (relayout) icons + if (width_change) sd->relayout = EINA_TRUE; + if (width_change) + { + evas_object_resize(sd->o_list_detailed_dummy, + w, sd->list_detailed_min_h); + printf("RSZ: %p %i\n", sd->o_list_detailed_dummy, w); + } +} + +static void +_cb_canvas_resize(void *data, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED) +{ // canvas resized and so handle so new visible icons are visible etc. + Smart_Data *sd = data; + Evas_Object *obj = sd->o_smart; + + evas_object_smart_changed(obj); +} + +static void +_relayout_icons(Smart_Data *sd) +{ // wall all blocks and icons in blocks and assign them geometry + Eina_List *bl, *il; + Block *block; + Icon *icon; + Evas_Coord x, y, minw, minh; + + // this is a row by row top-left to bottom-right layout + x = y = 0; + minw = minh = 0; + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + EINA_LIST_FOREACH(block->icons, il, icon) + { // icon is at urrent x,y + icon->geom.x = x; + icon->geom.y = y; + icon->geom.w = sd->icon_min_w; + icon->geom.h = sd->icon_min_h; + x += icon->geom.w; + if ((x + icon->geom.w) > sd->geom.w) + { // if next icon over end of row, start a new row + x = 0; + y += icon->geom.h; + } + if (block->icons == il) + { // first icon in block - block bounds == icon geom + block->bounds.x = icon->geom.x; + block->bounds.y = icon->geom.y; + block->bounds.w = icon->geom.w; + block->bounds.h = icon->geom.h; + } + else + { // expand block bounds based on icon geom if it needs to + if (icon->geom.x < block->bounds.x) + block->bounds.x = icon->geom.x; + if (icon->geom.y < block->bounds.y) + block->bounds.y = icon->geom.y; + if ((icon->geom.x + icon->geom.w) > + (block->bounds.x + block->bounds.w)) + block->bounds.w = icon->geom.x + + icon->geom.w - block->bounds.x; + if ((icon->geom.y + icon->geom.h) > + (block->bounds.y + block->bounds.h)) + block->bounds.h = icon->geom.y + + icon->geom.h - block->bounds.y; + } + } + // adjust view minw/h if block expands it + if ((block->bounds.x + block->bounds.w) > minw) + minw = block->bounds.x + block->bounds.w; + if ((block->bounds.y + block->bounds.h) > minh) + minh = block->bounds.y + block->bounds.h; + } + // set min size for scroller to know content size + evas_object_size_hint_min_set(sd->o_smart, sd->icon_min_w, minh); +} + +static void +_relayout_list(Smart_Data *sd) +{ // wall all blocks and icons in blocks and assign them geometry + Eina_List *bl, *il; + Block *block; + Icon *icon; + Evas_Coord x, y, minw, minh; + + // this is a row by row top-left to bottom-right layout + x = y = 0; + minw = minh = 0; + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + EINA_LIST_FOREACH(block->icons, il, icon) + { // icon is at urrent x,y + icon->geom.x = x; + icon->geom.y = y; + icon->geom.w = sd->geom.w; + if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + icon->geom.h = sd->list_detailed_min_h; + else + icon->geom.h = sd->list_min_h; + y += icon->geom.h; + if (block->icons == il) + { // first icon in block - block bounds == icon geom + block->bounds.x = icon->geom.x; + block->bounds.y = icon->geom.y; + block->bounds.w = icon->geom.w; + block->bounds.h = icon->geom.h; + } + else + { // expand block bounds based on icon geom if it needs to + if (icon->geom.x < block->bounds.x) + block->bounds.x = icon->geom.x; + if (icon->geom.y < block->bounds.y) + block->bounds.y = icon->geom.y; + if ((icon->geom.x + icon->geom.w) > + (block->bounds.x + block->bounds.w)) + block->bounds.w = icon->geom.x + + icon->geom.w - block->bounds.x; + if ((icon->geom.y + icon->geom.h) > + (block->bounds.y + block->bounds.h)) + block->bounds.h = icon->geom.y + + icon->geom.h - block->bounds.y; + } + } + // adjust view minw/h if block expands it + if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + { + if ((sd->list_detailed_min_w) > minw) + minw = block->bounds.x + sd->list_detailed_min_w; + } + else + { + if ((sd->list_min_w) > minw) + minw = block->bounds.x + sd->list_min_w; + } + if ((block->bounds.y + block->bounds.h) > minh) + minh = block->bounds.y + block->bounds.h; + } + // set min size for scroller to know content size + evas_object_size_hint_min_set(sd->o_smart, minw, minh); +} + +static void +_relayout(Smart_Data *sd) +{ // wall all blocks and icons in blocks and assign them geometry +// printf("XXXXX relayout...\n"); + // XXX: add other layouts like: + // XXX: fixed position per icon layout (just calc block bounds) + // XXX: column layout (top-left down then next col along) + // XXX: list layout + if (sd->config.view_mode == EFM_VIEW_MODE_ICONS) + _relayout_icons(sd); + else if (sd->config.view_mode == EFM_VIEW_MODE_LIST) + _relayout_list(sd); + else if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + _relayout_list(sd); +} + +static void +_recalc(Smart_Data *sd) +{ // recalc position of icons and which may or may not be visible now + Eina_List *bl, *il; + Block *block; + Icon *icon; + Eina_Rectangle viewport, rect; + Evas *e; + int num = 0; + const char *theme_edj_file; + + e = evas_object_evas_get(sd->o_smart); + evas_output_viewport_get(e, + &(viewport.x), &(viewport.y), + &(viewport.w), &(viewport.h)); + theme_edj_file = elm_theme_group_path_find + (NULL, "e/fileman/default/icon/fixed"); + EINA_LIST_FOREACH(sd->blocks, bl, block) + { // walk all our blocks and per clock ... is that block visible + rect = block->bounds; + rect.x += sd->geom.x; + rect.y += sd->geom.y; + if (eina_rectangles_intersect(&rect, &viewport)) + { // if the block intersects the viewport, then look into it + // check all the icon to see if each is visible + EINA_LIST_FOREACH(block->icons, il, icon) + { // check each icon... + rect = icon->geom; + rect.x += sd->geom.x; + rect.y += sd->geom.y; + // if the icon is within the viewport ... + if (eina_rectangles_intersect(&rect, &viewport)) + { // we are within vierwport - so could be visible + if (icon->changed) + { // icon changed - let's redo it, so del the old + _icon_object_clear(icon); + } + icon->changed = EINA_FALSE; + // no icon yet ... it needs to be created + if (!icon->o_base) + { // no object - let's create one + // about to realize if it hasn't been + if (!icon->realized) icon->block->realized_num++; + _icon_object_add(icon, sd, e, theme_edj_file, + EINA_TRUE, num); + } + // position the icon object where it should be + evas_object_geometry_set(icon->o_base, + rect.x, rect.y, + rect.w, rect.h); + if (icon->over) + evas_object_geometry_set(icon->sd->o_over, + rect.x, rect.y, + rect.w, rect.h); + } + else + { // icon not in viewport + if (icon->realized) + { // it's realized so unrealize it + icon->realized = EINA_FALSE; + icon->block->realized_num--; + _icon_object_clear(icon); + } + } + num++; + } + } + else if (block->realized_num > 0) + { // block is realized but NOT in the viewport (not visible) + EINA_LIST_FOREACH(block->icons, il, icon) + { // go through each icon now + if (icon->realized) + { // if the icon was realized then unrealize it + if (icon->realized) block->realized_num--; + icon->realized = EINA_FALSE; + _icon_object_clear(icon); + } + } + num += eina_list_count(block->icons); + } + else + num += eina_list_count(block->icons); + } + + evas_object_geometry_set(sd->o_back, + sd->geom.x, sd->geom.y, sd->geom.w, sd->geom.h); + _efm_focus_position(sd); + _efm_sel_position(sd); +} + +static void +_reset(Smart_Data *sd) +{ + Eina_Strbuf *buf; + Block *block; + Icon *icon; + static char envbuf[4096]; + + // clear out stuff there so we can start listing again... + sd->file_max = 0; + EINA_LIST_FREE(sd->blocks, block) _block_free(block); + EINA_LIST_FREE(sd->icons, icon) _icon_free(icon); + if (sd->reblock_job) + { + ecore_job_del(sd->reblock_job); + sd->reblock_job = NULL; + } + if (sd->exe_open) + { + Pending_Exe_Del *pend = calloc(1, sizeof(Pending_Exe_Del)); + + if (pend) + { + pend->exe = sd->exe_open; + pend->timer = ecore_timer_add(5.0, _cb_exe_pending_timer, pend); + _pending_exe_dels = eina_list_append(_pending_exe_dels, pend); + } + ecore_exe_interrupt(sd->exe_open); + sd->exe_open = NULL; + } + if (sd->thread) + { + if (sd->thread_data) sd->thread_data->sd = NULL; + ecore_thread_cancel(sd->thread); + sd->thread = NULL; + } + // now start up opening up a dir again + sd->thread_data = calloc(1, sizeof(Smart_Data_Thread)); + sd->thread_data->sd = sd; + sd->thread_data->thq = eina_thread_queue_new(); + sd->thread = ecore_thread_feedback_run(_cb_thread_main, _cb_thread_notify, + _cb_thread_done, _cb_thread_done, + sd->thread_data, EINA_TRUE); + buf = eina_strbuf_new(); + eina_strbuf_append(buf, elm_app_lib_dir_get()); + eina_strbuf_append(buf, "/efm/backends/"); + // XXX: choose backend other than default + eina_strbuf_append(buf, "default"); + snprintf(envbuf, sizeof(envbuf), + "EFM_BACKEND_DIR=%s", eina_strbuf_string_get(buf)); + putenv(envbuf); + eina_strbuf_append(buf, "/open"); + sd->exe_open = ecore_exe_pipe_run(eina_strbuf_string_get(buf), + ECORE_EXE_NOT_LEADER | + ECORE_EXE_TERM_WITH_PARENT | + ECORE_EXE_PIPE_READ | + ECORE_EXE_PIPE_READ_LINE_BUFFERED | + ECORE_EXE_PIPE_WRITE, NULL); + eina_strbuf_free(buf); + + if (sd->path) + { + buf = cmd_strbuf_new("dir-set"); + cmd_strbuf_append(buf, "path", sd->path); + cmd_strbuf_exe_consume(buf, sd->exe_open); + } +} + +static void +_cb_scroller_focus(void *data, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + + printf("FOCUS\n"); + sd->focused = EINA_TRUE; + evas_object_focus_set(sd->o_clip, EINA_TRUE); +} + +static void +_cb_scroller_unfocus(void *data, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + + printf("UNFOCUS\n"); + sd->focused = EINA_FALSE; +} + +static void +_smart_calculate(Evas_Object *obj) +{ + ENTRY; + + if ((sd->reblocked) || (sd->relayout)) + { // if we red-d the list of blocks (added/removed files) or need to + // re-layout due to q width or otheer geometry change + _relayout(sd); + sd->reblocked = EINA_FALSE; + sd->relayout = EINA_FALSE; + } + // moved/resize icons and/or realize/unrealize (create or delete objects + // that we need to awe only keep a limited set of ivisible/active icons + // that we need) + _recalc(sd); +} + +////////////////////////////////////////////////////////////////////////////// + +Evas_Object *efm_add(Evas_Object *parent) +{ + if (!_smart) + { + evas_object_smart_clipped_smart_set(&_sc_parent); + _sc = _sc_parent; + _sc.name = "efm"; + _sc.version = EVAS_SMART_CLASS_VERSION; + _sc.add = _smart_add; + _sc.del = _smart_del; + _sc.resize = _smart_resize; + _sc.move = _smart_move; + _sc.calculate = _smart_calculate; + }; + if (!_smart) _smart = evas_smart_class_new(&_sc); + return evas_object_smart_add(evas_object_evas_get(parent), _smart); +} + +static void +_reposition_detail_header_items(Smart_Data *sd) +{ + int i, vw, vh; + Evas_Coord det_x, det_w, x, w, xp; + + if (!sd->o_detail_header) return; + evas_object_geometry_get(sd->o_overlay_grid, &det_x, NULL, &det_w, NULL); + elm_grid_size_get(sd->o_overlay_grid, &vw, &vh); + xp = 0; + elm_grid_size_set(sd->o_detail_header, vw, 100); + for (i = 0; i < 7; i++) + { + if (i < 6) + { + elm_grid_pack_get(sd->o_list_detail[i], + &x, NULL, NULL, NULL); + w = (x - xp); + } + else + { + w = vw - xp; + x = xp + w; + } + elm_grid_pack(sd->o_detail_header, sd->o_detail_header_item[i], + xp, 0, w, 100); + xp = x; + } +} + +static void +_cb_detail_header_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + + _reposition_detail_header_items(sd); +} + +static void +_cb_detail_header_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + int i; + + sd->o_detail_header = NULL; + for (i = 0; i < 7; i++) sd->o_detail_header_item[i] = NULL; +} + +static void +_cb_detail_header_item_hint(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + Evas_Coord h, minh = 0; + int i; + + if (!sd->o_detail_header) return; + for (i = 0; i < 7; i++) + { + if (obj == sd->o_detail_header_item[i]) + { + evas_object_size_hint_min_get(obj, NULL, &h); + sd->detail_header_min_h[i] = h; + break; + } + } + for (i = 0; i < 7; i++) + { + h = sd->detail_header_min_h[i]; + if (h > minh) minh = h; + } + evas_object_size_hint_min_set(sd->o_detail_header, 0, minh); +} + +static void +_cb_detail_radio_changed(void *data, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ + Smart_Data *sd = data; + Efm_Sort_Mode sort_mode; + + sort_mode = elm_radio_value_get(sd->o_detail_header_item[0]); + efm_path_sort_mode_set(sd->o_smart, + sort_mode | + (sd->config.sort_mode & EFM_SORT_MODE_FLAGS)); + evas_object_smart_callback_call(sd->o_smart, "sort_mode", NULL); +} + +static void +_detail_setup(Smart_Data *sd) +{ + Evas_Object *o, *o_grid, *o_radio_group; + + sd->o_detail_header = o_grid = o = elm_grid_add(sd->o_scroller); + evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, + _cb_detail_header_resize, sd); + evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, + _cb_detail_header_del, sd); + + o_radio_group = o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[0] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_NAME); + elm_grid_pack(o_grid, o, 0, 0, 40, 100); + elm_object_text_set(o, "Name"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[1] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_SIZE); + elm_grid_pack(o_grid, o, 40, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "Size"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[2] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_DATE); + elm_grid_pack(o_grid, o, 50, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "Date"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[3] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_MIME); + elm_grid_pack(o_grid, o, 60, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "Type"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[4] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_USER); + elm_grid_pack(o_grid, o, 70, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "User"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[5] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_GROUP); + elm_grid_pack(o_grid, o, 80, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "Group"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + o = elm_radio_add(sd->o_detail_header); + sd->o_detail_header_item[6] = o; + evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS, + _cb_detail_header_item_hint, sd); + elm_object_style_set(o, "sort_header"); + elm_radio_state_value_set(o, EFM_SORT_MODE_PERMISSIONS); + elm_grid_pack(o_grid, o, 90, 0, 10, 100); + elm_radio_group_add(o, o_radio_group); + elm_object_text_set(o, "Permissions"); + evas_object_smart_callback_add(o, "changed", _cb_detail_radio_changed, sd); + evas_object_show(o); + + elm_radio_value_set(o_radio_group, sd->config.sort_mode & EFM_SORT_MODE_MASK); +} + +//////// +void +efm_scroller_set(Evas_Object *obj, Evas_Object *scroller) +{ + Evas_Object *o, *o2; + const char *theme_edj_file, *grp; + Evas *e; + ENTRY; + + // XXX: do any necessary re-inits ? should not be needed as scroller should + // XXX: remove scroller drop target... + // never change once set up at start + sd->o_scroller = scroller; + + e = evas_object_evas_get(obj); + + o = edje_object_add(e); + grp = "e/fileman/default/icon/fixed"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + o2 = evas_object_rectangle_add(e); + evas_object_size_hint_min_set(o2, + sd->config.icon_size * _scale_get(sd), + sd->config.icon_size * _scale_get(sd)); + edje_object_part_swallow(o, "e.swallow.icon", o2); + edje_object_size_min_calc(o, &(sd->icon_min_w), &(sd->icon_min_h)); + evas_object_del(o2); + evas_object_del(o); + + o = edje_object_add(e); + grp = "e/fileman/default/list/fixed"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + edje_object_size_min_calc(o, &(sd->list_min_w), &(sd->list_min_h)); + evas_object_del(o); + + sd->o_list_detailed_dummy = o = edje_object_add(e); + evas_object_smart_member_add(o, obj); + grp = "e/fileman/default/list/detailed"; + theme_edj_file = elm_theme_group_path_find(NULL, grp); + edje_object_file_set(o, theme_edj_file, grp); + edje_object_size_min_calc(o, &(sd->list_detailed_min_w), &(sd->list_detailed_min_h)); + evas_object_color_set(o, 0, 0, 0, 0); + evas_object_pass_events_set(o, EINA_TRUE); + evas_object_show(o); + + evas_object_smart_callback_add(sd->o_scroller, "focused", + _cb_scroller_focus, sd); + evas_object_smart_callback_add(sd->o_scroller, "unfocused", + _cb_scroller_unfocus, sd); + _drop_init(sd); + _add_overlay_objects(sd); +} + +Evas_Object * +efm_scroller_get(Evas_Object *obj) +{ + ENTRY NULL; + + return sd->o_scroller; +} + +Evas_Object * +efm_detail_header_get(Evas_Object *obj) +{ + ENTRY NULL; + + if (!sd->o_detail_header) _detail_setup(sd); + return sd->o_detail_header; +} + +//////// +void +efm_path_set(Evas_Object *obj, const char *path) +{ + ENTRY; + + if ((sd->path) && (path) && (!strcmp(sd->path, path))) return; + eina_stringshare_replace(&(sd->path), path); + _reset(sd); +} + +const char * +efm_path_get(Evas_Object *obj) +{ + ENTRY NULL; + + return sd->path; +} + +//////// +void +efm_path_view_mode_set(Evas_Object *obj, Efm_View_Mode mode) +{ + int i; + ENTRY; + + if (sd->config.view_mode == mode) return; + sd->config.view_mode = mode; + for (i = 0; i < 6; i++) + { + if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + evas_object_show(sd->o_list_detail[i]); + else + evas_object_hide(sd->o_list_detail[i]); + } + _reset(sd); +} + +Efm_View_Mode +efm_path_view_mode_get(Evas_Object *obj) +{ + ENTRY EFM_VIEW_MODE_ICONS; + + return sd->config.view_mode; +} + +void +efm_path_sort_mode_set(Evas_Object *obj, Efm_Sort_Mode mode) +{ + ENTRY; + + if (sd->config.sort_mode == mode) return; + sd->config.sort_mode = mode; + _reset(sd); +} + +Efm_Sort_Mode +efm_path_sort_mode_get(Evas_Object *obj) +{ + ENTRY EFM_SORT_MODE_NAME; + + return sd->config.sort_mode; +} + +Evas_Coord +efm_column_min_get(Evas_Object *obj, int col) +{ + ENTRY 0; + + if (col < 0) return 0; + else if (col >= 6) return 0; + return sd->detail_min_w[col]; +} diff --git a/src/efm/efm.h b/src/efm/efm.h new file mode 100644 index 0000000..39ee29f --- /dev/null +++ b/src/efm/efm.h @@ -0,0 +1,66 @@ +#ifndef EFM_H +# define EFM_H 1 + +# include + +#include "efm_config.h" + +typedef enum { + EFM_VIEW_MODE_ICONS, + EFM_VIEW_MODE_ICONS_CUSTOM, + EFM_VIEW_MODE_LIST, + EFM_VIEW_MODE_LIST_DETAILED +} Efm_View_Mode; + +typedef enum { + EFM_SORT_MODE_FLAGS = (0xffff << 16), // upper 16 bits for flags + // flags - apper 16 bits + EFM_SORT_MODE_DIRS_FIRST = ( 1 << 16), // show dirs first then files + EFM_SORT_MODE_NOCASE = ( 1 << 17), // don't account for case when sorting by label or path + EFM_SORT_MODE_LABEL_NOT_PATH = ( 1 << 18), // use label either in path filename or inside a desktop file + // mask for field + EFM_SORT_MODE_MASK = 0xffff, // lower 16 bits is the sort field + // fields + EFM_SORT_MODE_NAME = ( 0 << 0), + EFM_SORT_MODE_SIZE = ( 1 << 0), + EFM_SORT_MODE_DATE = ( 2 << 0), + EFM_SORT_MODE_MIME = ( 3 << 0), + EFM_SORT_MODE_USER = ( 4 << 0), + EFM_SORT_MODE_GROUP = ( 5 << 0), + EFM_SORT_MODE_PERMISSIONS = ( 6 << 0), +} Efm_Sort_Mode; + +typedef enum { + EFM_FOCUS_DIR_UP, + EFM_FOCUS_DIR_DOWN, + EFM_FOCUS_DIR_LEFT, + EFM_FOCUS_DIR_RIGHT, + EFM_FOCUS_DIR_PGDN, + EFM_FOCUS_DIR_PGUP +} Efm_Focus_Dir; + +Evas_Object *efm_add(Evas_Object *parent); +void efm_scroller_set(Evas_Object *obj, Evas_Object *scroller); +Evas_Object *efm_scroller_get(Evas_Object *obj); +Evas_Object *efm_detail_header_get(Evas_Object *obj); +void efm_path_set(Evas_Object *obj, const char *path); +const char *efm_path_get(Evas_Object *obj); +void efm_path_view_mode_set(Evas_Object *obj, Efm_View_Mode mode); +Efm_View_Mode efm_path_view_mode_get(Evas_Object *obj); +void efm_path_sort_mode_set(Evas_Object *obj, Efm_Sort_Mode mode); +Efm_Sort_Mode efm_path_sort_mode_get(Evas_Object *obj); +Evas_Coord efm_column_min_get(Evas_Object *obj, int col); + +// XXX: set/get column sizes +// XXX: callback when column sizes change +// XXX: set/get icon size +// XXX: hidden files show set/get + +#define DBG(...) EINA_LOG_DOM_DBG(_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_log_dom, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_log_dom, __VA_ARGS__) +#define CRI(...) EINA_LOG_DOM_CRIT(_log_dom, __VA_ARGS__) +extern int _log_dom; + +#endif diff --git a/src/efm/efm_back_end.c b/src/efm/efm_back_end.c new file mode 100644 index 0000000..5c281ee --- /dev/null +++ b/src/efm/efm_back_end.c @@ -0,0 +1,608 @@ +static void +_size_message(Evas_Object *o, double v) +{ + Edje_Message_Float msg; + + // display sqrt of 0.0-?1.0 so we don't have single huge files push every + // other bar out + if (v > 0.0) v = sqrt(v); + msg.val = v; + edje_object_message_send(o, EDJE_MESSAGE_FLOAT, 1, &msg); + edje_object_message_signal_process(o); +} + +static void +_size_bars_update(Smart_Data *sd) +{ + Eina_List *bl, *il; + Icon *icon; + Block *block; + const char *s; + + if (sd->config.view_mode != EFM_VIEW_MODE_LIST_DETAILED) return; + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->realized_num <= 0) continue; + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (!icon->realized) continue; + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (sd->file_max > 0) + _size_message(icon->o_list_detail_swallow2[0], + (double)size / (double)sd->file_max); + else + _size_message(icon->o_list_detail_swallow2[0], 0.0); + } + } + } +} + +static void +_cb_size_bars_update_job(void *data) +{ + Smart_Data *sd = data; + + sd->size_bars_update_job = NULL; + _size_bars_update(sd); +} + +static void +_size_bars_update_queue(Smart_Data *sd) +{ + if (sd->size_bars_update_job) ecore_job_del(sd->size_bars_update_job); + sd->size_bars_update_job = ecore_job_add(_cb_size_bars_update_job, sd); +} + +static void +_cb_size_max_update_job(void *data) +{ + Smart_Data *sd = data; + Eina_List *il; + Icon *icon; + const char *s; + unsigned long long new_max = 0; + + sd->size_max_update_job = NULL; + EINA_LIST_FOREACH(sd->icons, il, icon) + { + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size > new_max) new_max = size; + } + } + if (sd->file_max != new_max) + { + sd->file_max = new_max; + _size_bars_update_queue(sd); + } +} + +static void +_size_bars_max_update_queue(Smart_Data *sd) +{ + if (sd->size_max_update_job) ecore_job_del(sd->size_max_update_job); + sd->size_max_update_job = ecore_job_add(_cb_size_max_update_job, sd); +} + +static void +_command(Smart_Data *sd, const char *cmd) +{ + Cmd *c = cmd_parse(cmd); + Msg *msg; + void *ref; + + if (!c) return; + // send the cmd to our thread to deal with + msg = eina_thread_queue_send(sd->thread_data->thq, sizeof(Msg), &ref); + msg->c = c; + eina_thread_queue_send_done(sd->thread_data->thq, ref); +} + +static void +_process(Smart_Data_Thread *std, Ecore_Thread *th, Eina_List *batch) +{ // process a batch of commands that come from the back-end open process + Cmd *c, *c_tmp = NULL; + Eina_List *batch_new = NULL; + Eina_List *batch_tmp = NULL; + + if (!batch) return; + + // sort batch into batch_new where each set of things like add, mod, del + // if they are the same cmd then the files are sorted by path. we do this + // to speed up inserts in the main loop so we can assume a sorted list + // per batch. this speeds up file adds a lot as we can just do a walk of + // the batch (as long as commands stay the same like file-add) and then also + // walk the current file/icon list at the same time and skip to the insert + // spot which makes inserts very fast as both lists are known to be + // pre-sorted so at worst we walk N file icons in the current icon list + // per batch (and loading a dir is probably a series of batches of N + // file-add's where N is white reasonable + EINA_LIST_FREE(batch, c) + { + c->sort_mode = std->sd->config.sort_mode; + if (!batch_tmp) + { + batch_tmp = eina_list_append(batch_tmp, c); + c_tmp = c; + } + else if (!strcmp(c->command, c_tmp->command)) + { + // works for file-add, file-del, file-mod + batch_tmp = eina_list_sorted_insert(batch_tmp, sort_cmd, c); + } + else + { + batch_new = eina_list_merge(batch_new, batch_tmp); + batch_tmp = NULL; + } + } + if (batch_tmp) + { + batch_new = eina_list_merge(batch_new, batch_tmp); + batch_tmp = NULL; + } + if (batch_new) ecore_thread_feedback(th, batch_new); +} + +static Eina_Bool +_cb_exe_del(void *data, int ev_type EINA_UNUSED, void *event) +{ + Smart_Data *sd = data; + Ecore_Exe_Event_Del *ev = event; + Eina_List *l; + Pending_Exe_Del *pend; + + // remove this exited slave process from our pending exe deletions + // this list should be pretty small of pending deletions so we don't + // need to optimize this with a hash or whatever + EINA_LIST_FOREACH(_pending_exe_dels, l, pend) + { + if (pend->exe == ev->exe) + { + pend->exe = NULL; + if (pend->timer) + { + ecore_timer_del(pend->timer); + pend->timer = NULL; + } + free(pend); + _pending_exe_dels = eina_list_remove_list(_pending_exe_dels, l); + break; + } + } + if (ev->exe == sd->exe_open) + { // this process exiting is the back-end open process for active view + printf("ERROR: back-end open process died unexpectedly\n"); + return ECORE_CALLBACK_DONE; + } + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_cb_exe_data(void *data, int ev_type EINA_UNUSED, void *event) +{ + Smart_Data *sd = data; + Ecore_Exe_Event_Data *ev = event; + int i; + + // if this exe doesn't match the view it is for - pass it on and skip + if (ev->exe != sd->exe_open) return ECORE_CALLBACK_PASS_ON; + // this exe data is for thijs view + for (i = 0; ev->lines[i].line; i++) _command(sd, ev->lines[i].line); + return ECORE_CALLBACK_DONE; +} + +static Eina_Bool +_cb_exe_pending_timer(void *data) +{ // timeout trying to kill off back-end open process + Pending_Exe_Del *pend = data; + + pend->timer = NULL; + _pending_exe_dels = eina_list_remove(_pending_exe_dels, pend); + if (pend->exe) + { // forcibly kill the back-end process as it did not exit on its own + ecore_exe_kill(pend->exe); + pend->exe = NULL; + } + free(pend); + return EINA_FALSE; +} + +static void +_cb_thread_main(void *data, Ecore_Thread *th) +{ // thread sits processing commands read from stdout from the back-end open + Smart_Data_Thread *std = data; + Msg *msg; + void *ref; + Cmd *c; + Eina_Bool block = EINA_FALSE; + const char *prev_cmd = NULL; + Eina_List *batch = NULL; + + for (;;) + { // sit in a loop soaking up commands on the input queue + if (ecore_thread_check(th)) break; + if (!block) msg = eina_thread_queue_poll(std->thq, &ref); + else + { + usleep(4000); // wait 4ms to collect more msg's + msg = eina_thread_queue_wait(std->thq, &ref); + block = EINA_FALSE; + } + if (msg) + { + if (!batch) + { + batch = eina_list_append(batch, msg->c); + eina_stringshare_replace(&(prev_cmd), msg->c->command); + } + else if ((prev_cmd) && (!strcmp(msg->c->command, prev_cmd))) + { + batch = eina_list_append(batch, msg->c); + } + else + { + _process(std, th, batch); + batch = NULL; + eina_stringshare_replace(&(prev_cmd), NULL); + batch = eina_list_append(batch, msg->c); + } + eina_thread_queue_wait_done(std->thq, ref); + } + else + { + block = EINA_TRUE; + _process(std, th, batch); + batch = NULL; + eina_stringshare_replace(&(prev_cmd), NULL); + } + } + EINA_LIST_FREE(batch, c) + { + cmd_free(c); + } + eina_stringshare_replace(&(prev_cmd), NULL); +} + +static void +_cb_thread_notify(void *data, Ecore_Thread *th EINA_UNUSED, void *msg) +{ // handle data from the view thread to the UI - this will be a batch of cmds + Smart_Data_Thread *std = data; + Smart_Data *sd = std->sd; + Eina_List *batch = msg; + Cmd *c, *cprev = NULL; + Eina_List *l, *il2, *il = NULL; + Icon *icon, *icon2; + Block *block; + const char *file, *label, *s; + int file_adds = 0, file_dels = 0; + + if (!sd) + { // on cusp point - view gone but buffered thread feedback exists + EINA_LIST_FREE(batch, c) cmd_free(c); + return; + } + printf("XXXXX BATCH %i\n", eina_list_count(batch)); + EINA_LIST_FOREACH(batch, l, c) + { + c->sort_mode = sd->config.sort_mode; +#define CMD_DONE cmd_free(c); continue + if (!strcmp(c->command, "list-begin")) + { + printf("XXXXX LIST BEGIN\n"); + edje_object_part_text_set(sd->o_overlay_info, + "e.text.busy_label", + "Loading"); + edje_object_signal_emit(sd->o_overlay_info, + "e,state,busy,start", "e"); + CMD_DONE; + } + else if (!strcmp(c->command, "list-end")) + { + printf("XXXXX LIST END\n"); + edje_object_signal_emit(sd->o_overlay_info, + "e,state,busy,stop", "e"); + CMD_DONE; + } + file = cmd_key_find(c, "path"); + printf("XXXXX [%s] [%s]\n", c->command, file); + if (file) + { + s = strrchr(file, '/'); + if (s) file = s + 1; + if (file[0] == '.') // XXX filter dor files or not + { + CMD_DONE; + } + } + if ((!file) || (!file[0])) + { // somehow we didn't get a sane filename from the back-end + CMD_DONE; + } + label = cmd_key_find(c, "link-label"); + if (!label) label = cmd_key_find(c, "label"); + + if ((!cprev) || (!!strcmp(cprev->command, c->command))) + { // we start a new batch of commands - these are sorted + il = sd->icons; + } + + if (!strcmp(c->command, "file-add")) + { + icon = calloc(1, sizeof(Icon)); + if (!icon) abort(); + + file_adds++; + icon->sd = sd; + icon->cmd = c; + icon->changed = EINA_TRUE; + icon->info.file = eina_stringshare_add(file); + eina_stringshare_replace(&(icon->info.label), label); + s = cmd_key_find(c, "label-clicked"); + if (!s) s = cmd_key_find(c, "link-label-clicked"); + eina_stringshare_replace(&(icon->info.label_clicked), s); + s = cmd_key_find(c, "label-selected"); + if (!s) s = cmd_key_find(c, "link-label-selected"); + eina_stringshare_replace(&(icon->info.label_selected), s); + s = cmd_key_find(c, "mime"); + if (s) eina_stringshare_replace(&(icon->info.mime), s); + if (s) printf("XXXXX mime=%s\n", icon->info.mime); + s = cmd_key_find(c, "desktop-icon"); + if (!s) s = cmd_key_find(c, "link-desktop-icon"); + if (s) eina_stringshare_replace(&(icon->info.pre_lookup_icon), s); + s = cmd_key_find(c, "desktop-icon.lookup"); + if (!s) s = cmd_key_find(c, "link-desktop-icon.lookup"); + if (!s) s = cmd_key_find(c, "icon"); + if ((s) && (s[0] == '/')) eina_stringshare_replace(&(icon->info.icon), s); + s = cmd_key_find(c, "link-desktop-icon-clicked"); + if (!s) s = cmd_key_find(c, "desktop-icon-clicked"); + if (s) eina_stringshare_replace(&(icon->info.icon_clicked), s); + s = cmd_key_find(c, "link-desktop-icon-selected"); + if (!s) s = cmd_key_find(c, "desktop-icon-selected"); + if (s) eina_stringshare_replace(&(icon->info.icon_selected), s); + s = cmd_key_find(c, "mime-icon"); + if (s) eina_stringshare_replace(&(icon->info.mime_icon), s); + s = cmd_key_find(c, "thumb"); + if (s) eina_stringshare_replace(&(icon->info.thumb), s); + s = cmd_key_find(c, "type"); + if (s) + { + if (!strcmp(s, "link")) + { + icon->info.link = EINA_TRUE; + s = cmd_key_find(c, "broken-link"); + if ((s) && (!strcmp(s, "true"))) + icon->info.broken = EINA_TRUE; + s = cmd_key_find(c, "link-type"); + } + if (s) + { + if (!strcmp(s, "dir")) + { + icon->info.dir = EINA_TRUE; + eina_stringshare_replace((&icon->info.mime), + "inode/directory"); + } + else if (!strcmp(s, "block")) + { + icon->info.special = EINA_TRUE; + eina_stringshare_replace((&icon->info.mime), + "inode/blockdevice"); + } + else if (!strcmp(s, "char")) + { + icon->info.special = EINA_TRUE; + eina_stringshare_replace((&icon->info.mime), + "inode/chardevice"); + } + else if (!strcmp(s, "fifo")) + { + icon->info.special = EINA_TRUE; + eina_stringshare_replace((&icon->info.mime), + "inode/fifo"); + } + else if (!strcmp(s, "socket")) + { + icon->info.special = EINA_TRUE; + eina_stringshare_replace((&icon->info.mime), + "inode/socket"); + } + } + } + s = cmd_key_find(c, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size > sd->file_max) + { + sd->file_max = size; + _size_bars_update_queue(sd); + } + } + if (!icon->info.mime) + eina_stringshare_replace((&icon->info.mime), + "inode/file"); + + for ( ; il; il = il->next) + { + icon2 = il->data; + if (!strcmp(file, icon2->info.file)) + { // handle the case we get an add for an existing file + file_dels++; + il2 = il->next; + sd->icons = eina_list_remove_list(sd->icons, il); + il = il2; + if (sd->last_focused) + { + // XXX: select prev or next icon + } + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size == sd->file_max) + { + _size_bars_max_update_queue(sd); + } + } + _icon_free(icon2); + if (il) icon2 = il->data; + else break; + } + if (sort_cmd(icon2->cmd, icon->cmd) > 0) + { + sd->icons = eina_list_prepend_relative_list + (sd->icons, icon, il); + break; + } + } + if (!il) + { + sd->icons = eina_list_append(sd->icons, icon); + } + } + else if (!strcmp(c->command, "file-del")) + { + for ( ; il; il = il->next) + { + icon = il->data; + if (!strcmp(file, icon->info.file)) + { + file_dels++; + il2 = il->next; + sd->icons = eina_list_remove_list(sd->icons, il); + il = il2; + if (sd->last_focused) + { + // XXX: select prev or next icon + } + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size == sd->file_max) + { + _size_bars_max_update_queue(sd); + } + } + _icon_free(icon); + break; + } + } + cmd_free(c); + c = NULL; + } + else if (!strcmp(c->command, "file-mod")) + { + for ( ; il; il = il->next) + { + icon = il->data; + if (!strcmp(file, icon->info.file)) + { + icon->changed = EINA_TRUE; + eina_stringshare_replace(&(icon->info.label), label); + s = cmd_key_find(c, "label-clicked"); + if (!s) s = cmd_key_find(c, "link-label-clicked"); + eina_stringshare_replace(&(icon->info.label_clicked), s); + s = cmd_key_find(c, "label-selected"); + if (!s) s = cmd_key_find(c, "link-label-selected"); + eina_stringshare_replace(&(icon->info.label_selected), s); + s = cmd_key_find(c, "mime"); + if (s) eina_stringshare_replace(&(icon->info.mime), s); + if (!icon->info.mime) + eina_stringshare_replace((&icon->info.mime), + "inode/file"); + s = cmd_key_find(c, "desktop-icon"); + if (!s) s = cmd_key_find(c, "link-desktop-icon"); + if (s) eina_stringshare_replace(&(icon->info.pre_lookup_icon), s); + s = cmd_key_find(c, "desktop-icon.lookup"); + if (!s) s = cmd_key_find(c, "link-desktop-icon.lookup"); + if (!s) s = cmd_key_find(c, "icon"); + if ((s) && (s[0] == '/')) eina_stringshare_replace(&(icon->info.icon), s); + s = cmd_key_find(c, "link-desktop-icon-clicked"); + if (!s) s = cmd_key_find(c, "desktop-icon-clicked"); + if (s) eina_stringshare_replace(&(icon->info.icon_clicked), s); + s = cmd_key_find(c, "link-desktop-icon-selected"); + if (!s) s = cmd_key_find(c, "desktop-icon-selected"); + if (s) eina_stringshare_replace(&(icon->info.icon_selected), s); + s = cmd_key_find(c, "mime-icon"); + if (s) eina_stringshare_replace(&(icon->info.mime_icon), s); + s = cmd_key_find(c, "thumb"); + if (s) eina_stringshare_replace(&(icon->info.thumb), s); + s = cmd_key_find(c, "broken-link"); + if ((s) && (!strcmp(s, "true"))) + icon->info.broken = EINA_TRUE; + else + icon->info.broken = EINA_FALSE; + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size == sd->file_max) + { + _size_bars_max_update_queue(sd); + } + } + s = cmd_key_find(c, "size"); + if (s) + { + unsigned long long size = atoll(s); + + if (size > sd->file_max) + { + sd->file_max = size; + _size_bars_update_queue(sd); + } + } + cmd_free(icon->cmd); + icon->cmd = c; + break; + } + } + } + cprev = c; + } + eina_list_free(batch); + if ((sd->config.view_mode >= EFM_VIEW_MODE_LIST) && + ((file_adds > 0) || (file_dels > 0))) + { // if it's one of the list modes, unrealize realized icons + EINA_LIST_FOREACH(sd->blocks, l, block) + { + if (block->realized_num <= 0) continue; + EINA_LIST_FOREACH(block->icons, il, icon) + { // unrealize the icon - we odd/event forces this + if (!icon->realized) continue; + icon->realized = EINA_FALSE; + icon->block->realized_num--; + _icon_object_clear(icon); + } + } + } + if (sd->reblock_job) ecore_job_del(sd->reblock_job); + sd->reblock_job = ecore_job_add(_cb_reblock, sd); +} + +static void +_cb_thread_done(void *data, Ecore_Thread *th EINA_UNUSED) +{ + Smart_Data_Thread *std = data; + + if (std->sd) std->sd->thread_data = NULL; + if (std->thq) + { + eina_thread_queue_free(std->thq); + std->thq = NULL; + } + free(std); +} diff --git a/src/efm/efm_dnd.c b/src/efm/efm_dnd.c new file mode 100644 index 0000000..b45d925 --- /dev/null +++ b/src/efm/efm_dnd.c @@ -0,0 +1,279 @@ +// utils for draga and drop handling +static Eina_Bool +_cb_dnd_scroll_timer(void *data) +{ + Smart_Data *sd = data; + Evas_Coord fmx, fmy, sx, sy, sw, sh, x, y; + Evas_Coord scr_edge_x, scr_edge_y, scr_mul; + + evas_object_geometry_get(sd->o_smart, &fmx, &fmy, NULL, NULL); + evas_object_geometry_get(sd->o_scroller, &sx, &sy, &sw, &sh); + scr_mul = 4; + scr_edge_x = sd->icon_min_w / 2; + scr_edge_y = sd->icon_min_h / 2; + if (sd->dnd_scroll_x < scr_edge_x) + x = sx - fmx + sd->dnd_scroll_x - scr_edge_x - ((scr_edge_x - sd->dnd_scroll_x) * scr_mul); + else if (sd->dnd_scroll_x > (sw - scr_edge_x)) + x = sx - fmx + sd->dnd_scroll_x + scr_edge_x + ((sd->dnd_scroll_x - (sw - scr_edge_x)) * scr_mul); + else + x = sx - fmx + sd->dnd_scroll_x; + if (sd->dnd_scroll_y < scr_edge_y) + y = sy - fmy + sd->dnd_scroll_y - scr_edge_y - ((scr_edge_y - sd->dnd_scroll_y) * scr_mul); + else if (sd->dnd_scroll_y > (sh - scr_edge_y)) + y = sy - fmy + sd->dnd_scroll_y + scr_edge_y + ((sd->dnd_scroll_y - (sh - scr_edge_y)) * scr_mul); + else + y = sy - fmy + sd->dnd_scroll_y; + printf("SSS %i %i | %i %i %ix%i | %i %i\n", x, y, sx, sy, sw, sh, sd->dnd_scroll_x, sd->dnd_scroll_y); + elm_scroller_region_bring_in(sd->o_scroller, x, y, 1, 1); + return EINA_TRUE; +} + +static void +_dnd_scroll_handle(Smart_Data *sd, Evas_Coord x, Evas_Coord y) +{ + sd->dnd_scroll_x = x; + sd->dnd_scroll_y = y; + if (!sd->dnd_scroll_timer) + sd->dnd_scroll_timer = ecore_timer_add + (SCROLL_DND_TIMER, _cb_dnd_scroll_timer, sd); +} + +static void +_dnd_scroll_end_handle(Smart_Data *sd) +{ + if (sd->dnd_scroll_timer) + { + ecore_timer_del(sd->dnd_scroll_timer); + sd->dnd_scroll_timer = NULL; + } +} + +static void +_dnd_over(Icon *icon) +{ + _icon_over_on(icon); +} + +static void +_dnd_over_none_handle(Smart_Data *sd) +{ + if (sd->over_icon) _icon_over_off(sd->over_icon); +} + +static void +_dnd_over_handle(Smart_Data *sd, Evas_Coord x, Evas_Coord y) +{ + Icon *icon; + Eina_List *bl, *il; + Block *block; + + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (!eina_rectangle_coords_inside(&(block->bounds), x, y)) continue; + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (!eina_rectangle_coords_inside(&(icon->geom), x, y)) continue; + _dnd_over(icon); + return; + } + } + _dnd_over_none_handle(sd); +} + +// drop handling +static void +_cb_drop_in(void *data EINA_UNUSED, Evas_Object *o EINA_UNUSED) +{ +// Smart_Data *sd = data; + printf("XXX: drop in.....\n"); + // XXX: call drop in smart callback} +} + +static void +_cb_drop_out(void *data, Evas_Object *o EINA_UNUSED) +{ + Smart_Data *sd = data; + _dnd_scroll_end_handle(sd); + _dnd_over_none_handle(sd); + printf("XXX: drop out.....\n"); + // XXX: call drop out smart callback} +} + +void +_cb_drop_pos(void *data, Evas_Object *o EINA_UNUSED, Evas_Coord x, Evas_Coord y, Elm_Xdnd_Action action EINA_UNUSED) +{ + Smart_Data *sd = data; + Evas_Coord fmx, fmy, fmw, fmh; + Evas_Coord sx, sy, sw, sh; + + sd->dnd_action = action; + evas_object_geometry_get(sd->o_smart, &fmx, &fmy, &fmw, &fmh); + evas_object_geometry_get(sd->o_scroller, &sx, &sy, &sw, &sh); + _dnd_scroll_handle(sd, x - sx, y - sy); + if (((x - sx) > 0) && ((y - sy) > 0) && + ((x - sx) < sw) && ((y - sy) < sh)) + _dnd_over_handle(sd, x - fmx, y - fmy); + else + _dnd_over_none_handle(sd); + if (sd->over_icon) sd->drop_over = sd->over_icon; + else sd->drop_over = NULL; +} + +static Eina_Bool +_cb_drop(void *data, Evas_Object *o EINA_UNUSED, Elm_Selection_Data *ev) +{ + Smart_Data *sd = data; + char **plist, **p, *esc, *tmp; + + if (sd->drop_over) printf("XXX: DND DROP OVER [%s]\n", sd->drop_over->info.file); + else printf("XXX: DND DROP ...\n"); + tmp = malloc(ev->len + 1); + if (!tmp) goto err; + memcpy(tmp, ev->data, ev->len); + tmp[ev->len] = 0; + plist = eina_str_split(tmp, "\n", -1); + for (p = plist; *p != NULL; ++p) + { + if (**p) + { + esc = _escape_parse(*p); + if (!esc) continue; + printf("XXX: DROP FILE: [%s]\n", esc); + } + } + free(*plist); + free(plist); + free(tmp); +err: + sd->drop_over = NULL; + return EINA_TRUE; +} + +static void +_drop_init(Smart_Data *sd) +{ // called once we have our elm scroller around our file view + elm_drop_target_add(sd->o_scroller, + ELM_SEL_FORMAT_URILIST, + _cb_drop_in, sd, + _cb_drop_out, sd, + _cb_drop_pos, sd, + _cb_drop, sd); +} + +// drag handling +static Evas_Object * +_cb_drag_icon_add(void *data, Evas_Object *parent, Evas_Coord *xoff, Evas_Coord *yoff) +{ + Icon *icon = data; + Icon *icon2; + Block *block; + Evas_Object *obj = icon->sd->o_smart; + Evas_Object *o; + const char *theme_edj_file; + Evas *e; + ENTRY NULL; + Eina_List *bl, *il; + int num = 0; + + if (sd->config.view_mode >= EFM_VIEW_MODE_LIST) + { + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->realized_num <= 0) + { + num += eina_list_count(block->icons); + continue; + } + EINA_LIST_FOREACH(block->icons, il, icon2) + { + if (icon == icon2) goto found; + num++; + } + num += eina_list_count(block->icons); + } + } +found: + e = evas_object_evas_get(parent); + theme_edj_file = elm_theme_group_path_find(NULL, + "e/fileman/default/icon/fixed"); + _icon_object_add(icon, icon->sd, e, theme_edj_file, EINA_FALSE, num); + o = icon->o_base; + evas_object_resize(o, icon->geom.w, icon->geom.h); + if (xoff) *xoff = (icon->geom.x + icon->sd->geom.x); + if (yoff) *yoff = (icon->geom.y + icon->sd->geom.y); + if (xoff && yoff) + printf("DND: drag begin %p %p off: %i %i\n", obj, o, *xoff, *yoff); + else + printf("DND: drag begin %p %p\n", obj, o); + return o; +} + +static void +_cb_drag_pos(void *data EINA_UNUSED, Evas_Object *obj_drag EINA_UNUSED, Evas_Coord x, Evas_Coord y, Elm_Xdnd_Action action) +{ + printf("DND: %i %i act=%i\n", x, y, action); +} + +static void +_cb_drag_accept(void *data EINA_UNUSED, Evas_Object *obj_drag EINA_UNUSED, Eina_Bool accept) +{ + printf("DND: accept %i\n", accept); +} + +static void +_cb_drag_done(void *data, Evas_Object *obj_drag EINA_UNUSED) +{ + Icon *icon = data; + Evas_Object *obj; + + if (icon->sd) + { + obj = icon->sd->o_smart; + ENTRY; + + printf("DND: drag done %p %p\n", obj, obj_drag); + sd->drag = EINA_FALSE; + icon->o_base = NULL; // try and not del this as dnd will do it + icon->sd->drag_icon = NULL; + } + _icon_free(icon); +} + +static void +_drag_start(Icon *icon) +{ + Eina_Strbuf *strbuf; + Icon *icon_dup; + + strbuf = eina_strbuf_new(); + if (!strbuf) return; + icon_dup = _icon_dup(icon); + if (!icon_dup) goto err; + + if (icon->sd->rename_icon) _icon_rename_end(icon->sd->rename_icon); + printf("XXX: begin dnd\n"); + + icon->sd->drag = EINA_TRUE; + icon->sd->just_dragged = EINA_TRUE; + icon->sd->drag_icon = icon_dup; + if (_selected_icons_uri_strbuf_append(icon->sd, strbuf)) + { // begin the drag with that list of files + // XXX: if you use no modifier - do default (move) + // XXX: if you hold ctrl then do copy + // XXX: on win ... to/from removable drive it copies always + // XXX: if you hold shift then do move + // XXX; on mac option key forces a copy normally except from + // removable device then it forces a move + // XXX: this "except to/from removable drive" is bad/inconsistent imho + elm_drag_start(icon_dup->sd->o_scroller, + ELM_SEL_FORMAT_URILIST, + eina_strbuf_string_get(strbuf), + ELM_XDND_ACTION_MOVE, + _cb_drag_icon_add, icon_dup, + _cb_drag_pos, icon_dup, + _cb_drag_accept, icon_dup, + _cb_drag_done, icon_dup); + } +err: + eina_strbuf_free(strbuf); +} diff --git a/src/efm/efm_icon.c b/src/efm/efm_icon.c new file mode 100644 index 0000000..ad8e1bf --- /dev/null +++ b/src/efm/efm_icon.c @@ -0,0 +1,650 @@ +#include "efm_icon.h" +#include + +// XXX: support animated files (anim gif etc.) +// XXX: support video files (mp4 etc.) +// XXX: support edj files ??? +// XXX: url's ? + +typedef struct _Smart_Data Smart_Data; + +struct _Smart_Data +{ + Evas_Object_Smart_Clipped_Data __clipped_data; + + Eina_Rectangle geom; + + Evas_Object *o_smart; // the smart object container itself + Evas_Object *o_image; // the image to be shown right now + Evas_Object *o_image2; // the image being loaded still + Evas_Object *o_video; // the image used for video/audio playback + + Ecore_Timer *settle_timer; // time to figure out when + Ecore_Job *wakeup; // a job to wake up the loop + Eina_Stringshare *file; // file path or... + Eina_Stringshare *thumb; // thumb path + Eina_Stringshare *video; // video path + Ecore_Timer *anim_timer; // timer for animation frame flipping + + int load_size; // the sie we want to load now + int orig_w, orig_h; // the sie of the img we loaded + int frame; // current frame to display + int frame_count; // number of frames in anim + double video_ratio; // aspect ratio for videos + Evas_Image_Animated_Loop_Hint loop_type; // animated loop type + Eina_Bool alpha : 1; // does the img have alpha + Eina_Bool svg : 1; // is the img a svg + Eina_Bool newfile : 1; // did we just set a new file + Eina_Bool animated : 1; // is this animated? + Eina_Bool vid_stream : 1; // is this video (has video frames?) + Eina_Bool audio : 1; // is this audio (has audo) + Eina_Bool mono_thumb : 1; // is thumb of mono white alpha img +}; + +static Evas_Smart *_smart = NULL; +static Evas_Smart_Class _sc = EVAS_SMART_CLASS_INIT_NULL; +static Evas_Smart_Class _sc_parent = EVAS_SMART_CLASS_INIT_NULL; + +static void _image_add(Smart_Data *sd); +static int _size_choose(Smart_Data *sd, const int *sizes); +static void _cb_wakeup(void *data); +static void _wakeup(Smart_Data *sd); +static void _image_file_set(Smart_Data *sd); +static void _image_thumb_set(Smart_Data *sd); +static void _image_resized(Smart_Data *sd); +static Eina_Bool _cb_settle_timer(void *data); +static void _cb_image_preload(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED); +static void _handle_frame(Smart_Data *sd); + +// sizes stored in thumbnail files that we might want to look at +static const int _thumb_sizes[] = { 512, 256, 128, 64, 32, 16, 0 }; +// sizes we might want to load/render svg's at +static const int _svg_sizes[] = { 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 0 }; + +#define ENTRY Smart_Data *sd = evas_object_smart_data_get(obj); if (!sd) return + +static void +_image_add(Smart_Data *sd) +{ // add a new hidden image object (image2) that will be busy loading + Evas_Object *o; + + if (sd->o_image2) evas_object_del(sd->o_image2); + sd->o_image2 = o = evas_object_image_filled_add + (evas_object_evas_get(sd->o_smart)); + evas_object_image_scale_hint_set(o, EVAS_IMAGE_SCALE_HINT_STATIC); + evas_object_smart_member_add(o, sd->o_smart); // this is a member + evas_object_image_load_head_skip_set(o, EINA_TRUE); // fileset no load head + // when the image is fully loaded then call this callback so its all + // ready and no more i/o is happening (this happens in a thread) + evas_object_event_callback_add(o, EVAS_CALLBACK_IMAGE_PRELOADED, + _cb_image_preload, sd); +} + +static int +_size_choose(Smart_Data *sd, const int *sizes) +{ // find the next size up we want given the current object size + int i, max_size = 0; + + // use the biggest axis as our desired size + if (sd->geom.w > sd->geom.h) max_size = sd->geom.w; + else max_size = sd->geom.h; + for (i = 0; sizes[i] > 0; i++) + { + if (i > 0) + { // if we're not the first item in the sizes + // if size is bigger than this slot - return the size up + if (max_size > sizes[i]) return sizes[i - 1]; + } + else + { // we're after the first item in our sizes so use biggest + if (max_size > sizes[i]) return sizes[i]; + } + } + return sizes[i - 1]; // no size found - choose smallest +} + +static void +_cb_wakeup(void *data) +{ // we woke up - mark obj as changed.... + Smart_Data *sd = data; + sd->wakeup = NULL; + evas_object_smart_changed(sd->o_smart); +} + +static void +_wakeup(Smart_Data *sd) +{ // wake up the main loop so we spin around and do things + if (sd->wakeup) ecore_job_del(sd->wakeup); + sd->wakeup = ecore_job_add(_cb_wakeup, sd); +} + +static void +_image_file_set_final(Smart_Data *sd, const char *file, const char *group) +{ // complete image file load/setup, gtrigger preload etc. + const char *ext; + + // skip head will make animated not work - so skip it if format might + // be animated so we can flip frames + ext = strrchr(file, '.'); + if (ext && ((!strcasecmp(ext, ".gif")) || + (!strcasecmp(ext, ".webp")) || + (!strcasecmp(ext, ".avif")) || + (!strcasecmp(ext, ".avifs")) || + (!strcasecmp(ext, ".jxl")))) + evas_object_image_load_head_skip_set(sd->o_image2, EINA_FALSE); + evas_object_image_file_set(sd->o_image2, file, group); + evas_object_image_preload(sd->o_image2, EINA_FALSE); + evas_object_smart_changed(sd->o_smart); + _wakeup(sd); +} + +static void +_image_file_set(Smart_Data *sd) +{ // set a generic target image file + int load_size = _size_choose(sd, _svg_sizes); + + if (sd->o_video) + { // nuke previous video object if there + evas_object_del(sd->o_video); + sd->o_video = NULL; + } + if (sd->svg) + { // if it's a svg file... + if (!sd->settle_timer) + { // if we dont have a pending settle timer anymore use an EXACT sz + if (sd->geom.w > sd->geom.h) load_size = sd->geom.w; + else load_size = sd->geom.h; + } + } + // we already have this thumbnail size so no point loading it again + if (load_size == sd->load_size) return; + _image_add(sd); + sd->load_size = load_size; + if (sd->svg) + { // it's a svg so tell the svg loader to render at this size + if (sd->load_size == 0) return; + evas_object_image_load_size_set(sd->o_image2, + sd->load_size, sd->load_size); + } + _image_file_set_final(sd, sd->file, NULL); +} + +static void +_image_thumb_set(Smart_Data *sd) +{ // set a thumb file from our stored thumbnails + char buf[64]; + int load_size = _size_choose(sd, _thumb_sizes); + + // we already have this thumbnail size so no point loading it again + if (load_size == sd->load_size) return; + _image_add(sd); + if (sd->newfile) + { + Eet_File *ef = eet_open(sd->thumb, EET_FILE_MODE_READ); + if (ef) + { + int size; + char *mono = eet_read(ef, "image/thumb/mono", &size); + + if (mono) + { + sd->mono_thumb = *mono; + free(mono); + } + eet_close(ef); + } + } + sd->load_size = load_size; + snprintf(buf, sizeof(buf), "image/thumb/%i", sd->load_size); + _image_file_set_final(sd, sd->thumb, buf); +} + +static void +_cb_vid_frame(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED) +{ // we decoded a frame - make sure vide3o is shown now we have a frame + Smart_Data *sd = data; + + evas_object_show(sd->o_video); +} + +static void +_cb_vid_resize(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED) +{ // the video changed video size - tell owner we loaded or resized + Smart_Data *sd = data; + + emotion_object_size_get(sd->o_video, &(sd->orig_w), &(sd->orig_h)); + sd->video_ratio = emotion_object_ratio_get(sd->o_video); + if (sd->newfile) + { // it's a new file, so say we loaded as its the first time + evas_object_smart_callback_call(sd->o_smart, "loaded", NULL); + sd->newfile = EINA_FALSE; + } + else // already playing the file - not new, si resized + evas_object_smart_callback_call(sd->o_smart, "resized", NULL); +} + +static void +_cb_vid_open_done(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED) +{ // we finished opening - now go to 0 and play + Smart_Data *sd = data; + + emotion_object_position_set(sd->o_video, 0.0); + emotion_object_play_set(sd->o_video, EINA_TRUE); +} + +static void +_cb_vid_play_finish(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED) +{ // we finished playing go back to 0 and restart so we loop + Smart_Data *sd = data; + + emotion_object_play_set(sd->o_video, EINA_FALSE); + emotion_object_position_set(sd->o_video, 0.0); + emotion_object_play_set(sd->o_video, EINA_TRUE); +} + +static void +_image_video_set(Smart_Data *sd) +{ // set a video file + static Eina_Bool emotion_initted = EINA_FALSE; + Evas_Object *o; + + if (sd->o_image) + { // nuke previous image object if there + evas_object_del(sd->o_image); + sd->o_image = NULL; + } + if (sd->o_image2) + { // nuke previous image object if there + evas_object_del(sd->o_image2); + sd->o_image2 = NULL; + } + if (sd->o_video) + { // nuke previous video object if there + evas_object_del(sd->o_video); + sd->o_video = NULL; + } + + if (!emotion_initted) + { // emotion needs an init - so init it first time only + emotion_init(); + emotion_initted = EINA_TRUE; + } + // XXX: sometimes this stalls because gstreamer is updating registry data + // in gstreamer_module_init -> gst_init_check -> g_option_context_parse + // -> gst_update_registry -> gst_poll_wait + sd->o_video = o = emotion_object_add(evas_object_evas_get(sd->o_smart)); + emotion_object_keep_aspect_set(o, EMOTION_ASPECT_KEEP_NONE); + evas_object_smart_member_add(o, sd->o_smart); // this is a member + evas_object_smart_callback_add(o, "frame_decode", _cb_vid_frame, sd); + evas_object_smart_callback_add(o, "frame_resize", _cb_vid_resize, sd); + evas_object_smart_callback_add(o, "open_done", _cb_vid_open_done, sd); + evas_object_smart_callback_add(o, "playback_finished", _cb_vid_play_finish, sd); +// other callbacks - we don't need these for now +// evas_object_smart_callback_add(o, "decode_stop", _cb_vid_stop, obj); +// evas_object_smart_callback_add(o, "progress_change", _cb_vid_progress, obj); +// evas_object_smart_callback_add(o, "position_update", _cb_position_update, obj); +// evas_object_smart_callback_add(o, "length_change", _cb_length_change, obj); +// evas_object_smart_callback_add(o, "title_change", _cb_title_change, obj); +// evas_object_smart_callback_add(o, "audio_level_change", _cb_audio_change, obj); +// evas_object_smart_callback_add(o, "playback_started", _cb_play_start, obj); + emotion_object_file_set(o, sd->video); + emotion_object_audio_mute_set(o, EINA_TRUE); + emotion_object_audio_volume_set(o, 0.0); +} + +static void +_image_resized(Smart_Data *sd) +{ // the image was just resized to handle that settling "idle" timer + if (sd->svg) + { // it's an svg - so set up a timer so if we dont resize again for 0.2s + if (sd->settle_timer) ecore_timer_reset(sd->settle_timer); + else sd->settle_timer = ecore_timer_add(0.2, _cb_settle_timer, sd); + } + else + { // not a svg - nuke settle timer from space - the only way to be sure + if (sd->settle_timer) + { + ecore_timer_del(sd->settle_timer); + sd->settle_timer = NULL; + } + } +} + +static void +_image_update(Smart_Data *sd) +{ // update the image - perhaps size changed... + if (sd->thumb) _image_thumb_set(sd); + else if (sd->file) _image_file_set(sd); +} + +static Eina_Bool +_cb_settle_timer(void *data) +{ // when the image settles and stops being resized after a bit... + Smart_Data *sd = data; + + sd->settle_timer = NULL; + _image_update(sd); + return EINA_FALSE; +} + +static int +_frame_num_get(Smart_Data *sd) +{ + int fr, fr2; + + // ping pong - first and last frame only appear once at each end + if (sd->loop_type == EVAS_IMAGE_ANIMATED_HINT_PINGPONG) + { + fr = sd->frame % ((sd->frame_count * 2) - 2); + fr2 = sd->frame % sd->frame_count + 1; + if (fr >= sd->frame_count) fr = sd->frame_count - 1 - fr2; + } + // loop + else fr = sd->frame % sd->frame_count; + return fr; +} + +static Eina_Bool +_cb_anim_timer(void *data) +{ + Smart_Data *sd = data; + int fr; + + sd->anim_timer = NULL; + sd->frame++; + fr = _frame_num_get(sd); + printf("T: %1.3f %i / %i\n", ecore_time_get(), fr, sd->frame_count); + evas_object_image_animated_frame_set(sd->o_image, fr); + _handle_frame(sd); + return EINA_FALSE; +} + +static void +_handle_frame(Smart_Data *sd) +{ + int fr; + double t; + + fr = _frame_num_get(sd); + t = evas_object_image_animated_frame_duration_get(sd->o_image, fr, 0); + if (sd->anim_timer) ecore_timer_del(sd->anim_timer); + sd->anim_timer = ecore_timer_add(t, _cb_anim_timer, sd); +} + +static void +_cb_image_preload(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ // whenb an image that was being loaded in the background has now finished + Smart_Data *sd = data; + + if (sd->o_image) evas_object_del(sd->o_image); + sd->o_image = sd->o_image2; + if (!sd->o_image2) return; + sd->o_image2 = NULL; + evas_object_image_size_get(sd->o_image, &(sd->orig_w), &(sd->orig_h)); + sd->alpha = evas_object_image_alpha_get(sd->o_image); + evas_object_show(sd->o_image); + if (sd->newfile) + { + sd->newfile = EINA_FALSE; + evas_object_smart_callback_call(sd->o_smart, "loaded", NULL); + } + sd->animated = evas_object_image_animated_get(sd->o_image); + if (sd->animated) + { + sd->loop_type = evas_object_image_animated_loop_type_get(sd->o_image); + sd->frame_count = evas_object_image_animated_frame_count_get(sd->o_image); + if (sd->frame_count < 3) sd->loop_type = EVAS_IMAGE_ANIMATED_HINT_LOOP; + else if (sd->frame_count < 2) sd->animated = EINA_FALSE; + if (sd->animated) _handle_frame(sd); + } +} + +// gui code +static void +_smart_add(Evas_Object *obj) +{ // create a new efm icon + Smart_Data *sd; + + sd = calloc(1, sizeof(Smart_Data)); + if (!sd) return; + evas_object_smart_data_set(obj, sd); + + _sc_parent.add(obj); + + sd->o_smart = obj; +} + +static void +_smart_del(Evas_Object *obj) +{ // delete/free efm view + ENTRY; + + if (sd->o_image) + { + evas_object_del(sd->o_image); + sd->o_image = NULL; + } + if (sd->o_image2) + { + evas_object_del(sd->o_image2); + sd->o_image2 = NULL; + } + if (sd->o_video) + { + evas_object_del(sd->o_video); + sd->o_video = NULL; + } + if (sd->settle_timer) + { + ecore_timer_del(sd->settle_timer); + sd->settle_timer = NULL; + } + if (sd->wakeup) + { + ecore_job_del(sd->wakeup); + sd->wakeup = NULL; + } + if (sd->anim_timer) + { + ecore_timer_del(sd->anim_timer); + sd->anim_timer = NULL; + } + eina_stringshare_replace(&(sd->thumb), NULL); + eina_stringshare_replace(&(sd->file), NULL); + eina_stringshare_replace(&(sd->video), NULL); + + _sc_parent.del(obj); + evas_object_smart_data_set(obj, NULL); +} + +static void +_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y) +{ // efm icon object moved + ENTRY; + + if ((sd->geom.x == x) && (sd->geom.y == y)) return; + sd->geom.x = x; + sd->geom.y = y; + evas_object_smart_changed(obj); +} + +static void +_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h) +{ // efm icon object resized + ENTRY; + + if ((sd->geom.w == w) && (sd->geom.h == h)) return; + sd->geom.w = w; + sd->geom.h = h; + _image_resized(sd); + _image_update(sd); + evas_object_smart_changed(obj); +} + +static void +_smart_calculate(Evas_Object *obj) +{ // recalce position/size + ENTRY; + + if (sd->o_image) + evas_object_geometry_set(sd->o_image, + sd->geom.x, sd->geom.y, + sd->geom.w, sd->geom.h); + if (sd->o_image2) + evas_object_geometry_set(sd->o_image2, + sd->geom.x, sd->geom.y, + sd->geom.w, sd->geom.h); + if (sd->o_video) + evas_object_geometry_set(sd->o_video, + sd->geom.x, sd->geom.y, + sd->geom.w, sd->geom.h); + _image_update(sd); +} + +Evas_Object * +efm_icon_add(Evas_Object *parent) +{ // add new icon object + if (!_smart) + { + evas_object_smart_clipped_smart_set(&_sc_parent); + _sc = _sc_parent; + _sc.name = "efm_icon"; + _sc.version = EVAS_SMART_CLASS_VERSION; + _sc.add = _smart_add; + _sc.del = _smart_del; + _sc.resize = _smart_resize; + _sc.move = _smart_move; + _sc.calculate = _smart_calculate; + }; + if (!_smart) _smart = evas_smart_class_new(&_sc); + return evas_object_smart_add(evas_object_evas_get(parent), _smart); +} + +void +efm_icon_file_set(Evas_Object *obj, const char *file) +{ // set a regular file as the icon + ENTRY; + + if ((sd->file) && (file) && (!strcmp(sd->file, file))) return; + if ((!sd->file) && (!file)) return; + eina_stringshare_replace(&(sd->thumb), NULL); + eina_stringshare_replace(&(sd->file), file); + eina_stringshare_replace(&(sd->video), NULL); + sd->svg = EINA_FALSE; + if ((sd->file) && + ((eina_fnmatch("*.svg", sd->file, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("*.svgz", sd->file, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("*.svg.gz", sd->file, EINA_FNMATCH_CASEFOLD)))) + sd->svg = EINA_TRUE; + sd->load_size = -1; + sd->newfile = EINA_TRUE; + sd->mono_thumb = EINA_FALSE; + sd->frame = 0; + if (sd->anim_timer) + { + ecore_timer_del(sd->anim_timer); + sd->anim_timer = NULL; + } + if (sd->settle_timer) + { // thumbnails dont need settle timers like svg's might + ecore_timer_del(sd->settle_timer); + sd->settle_timer = NULL; + } + _image_resized(sd); + _image_file_set(sd); +} + +void +efm_icon_thumb_set(Evas_Object *obj, const char *thumb) +{ // specifically add generated thumb file + ENTRY; + + if ((sd->thumb) && (thumb) && (!strcmp(sd->thumb, thumb))) return; + if ((!sd->thumb) && (!thumb)) return; + eina_stringshare_replace(&(sd->file), NULL); + eina_stringshare_replace(&(sd->thumb), thumb); + eina_stringshare_replace(&(sd->video), NULL); + sd->svg = EINA_FALSE; + sd->load_size = -1; + sd->newfile = EINA_TRUE; + sd->mono_thumb = EINA_FALSE; + sd->frame = 0; + if (sd->anim_timer) + { + ecore_timer_del(sd->anim_timer); + sd->anim_timer = NULL; + } + if (sd->settle_timer) + { // thumbnails dont need settle timers like svg's might + ecore_timer_del(sd->settle_timer); + sd->settle_timer = NULL; + } + _image_thumb_set(sd); +} + +void +efm_icon_video_set(Evas_Object *obj, const char *video) +{ // specifically load file as a video (or audio) and not as a still image + ENTRY; + + if ((sd->video) && (video) && (!strcmp(sd->video, video))) return; + if ((!sd->video) && (!video)) return; + eina_stringshare_replace(&(sd->file), NULL); + eina_stringshare_replace(&(sd->thumb), NULL); + eina_stringshare_replace(&(sd->video), video); + sd->svg = EINA_FALSE; + sd->load_size = -1; + sd->newfile = EINA_TRUE; + sd->mono_thumb = EINA_FALSE; + sd->frame = 0; + sd->video_ratio = 0.0; + if (sd->anim_timer) + { + ecore_timer_del(sd->anim_timer); + sd->anim_timer = NULL; + } + if (sd->settle_timer) + { // thumbnails dont need settle timers like svg's might + ecore_timer_del(sd->settle_timer); + sd->settle_timer = NULL; + } + _image_resized(sd); + _image_video_set(sd); +} + +void +efm_icon_size_get(Evas_Object *obj, int *w, int *h) +{ // get image pixel size + ENTRY; + + if ((sd->o_video) && (sd->video_ratio > 0.0)) + { + int vid_w, vid_h; + + vid_w = ((double)sd->orig_h + 0.5) * sd->video_ratio; + vid_h = sd->orig_h; + if (w) *w = vid_w; + if (h) *h = vid_h; + } + else + { + if (w) *w = sd->orig_w; + if (h) *h = sd->orig_h; + } +} + +Eina_Bool +efm_icon_alpha_get(Evas_Object *obj) +{ // get image alpha flag + ENTRY EINA_FALSE; + + return sd->alpha; +} + +Eina_Bool +efm_icon_mono_get(Evas_Object *obj) +{ // get thumb mono flag + ENTRY EINA_FALSE; + + return sd->mono_thumb; +} diff --git a/src/efm/efm_icon.h b/src/efm/efm_icon.h new file mode 100644 index 0000000..0fdce30 --- /dev/null +++ b/src/efm/efm_icon.h @@ -0,0 +1,17 @@ +#ifndef EFM_ICON_H +# define EFM_ICON_H 1 + +# include + +Evas_Object *efm_icon_add(Evas_Object *parent); +void efm_icon_file_set(Evas_Object *obj, const char *file); +void efm_icon_thumb_set(Evas_Object *obj, const char *thumb); +void efm_icon_video_set(Evas_Object *obj, const char *video); +void efm_icon_size_get(Evas_Object *obj, int *w, int *h); +Eina_Bool efm_icon_alpha_get(Evas_Object *obj); +Eina_Bool efm_icon_mono_get(Evas_Object *obj); + +// smart callbacks: +// "loaded" + +#endif diff --git a/src/efm/efm_private.h b/src/efm/efm_private.h new file mode 100644 index 0000000..eeada22 --- /dev/null +++ b/src/efm/efm_private.h @@ -0,0 +1,20 @@ +// functions used between the split up parts of the efm view + +static void _command (Smart_Data *sd, const char *cmd); +static void _process (Smart_Data_Thread *std, Ecore_Thread *th, Eina_List *batch); + +static Eina_Bool _cb_exe_del (void *data, int ev_type EINA_UNUSED, void *event); +static Eina_Bool _cb_exe_data (void *data, int ev_type EINA_UNUSED, void *event); +static Eina_Bool _cb_exe_pending_timer (void *data); + +static void _cb_thread_main (void *data, Ecore_Thread *th); +static void _cb_thread_notify (void *data, Ecore_Thread *th EINA_UNUSED, void *msg); +static void _cb_thread_done (void *data, Ecore_Thread *th EINA_UNUSED); + +static void _icon_object_clear (Icon *icon); +static void _icon_object_add (Icon *icon, Smart_Data *sd, Evas *e, const char *theme_edj_file, Eina_Bool clip_set, int num); +static void _icon_free (Icon *icon); +static void _block_free (Block *block); +static void _cb_reblock (void *data); + +static void _drag_start (Icon *icon); diff --git a/src/efm/efm_structs.h b/src/efm/efm_structs.h new file mode 100644 index 0000000..294bf38 --- /dev/null +++ b/src/efm/efm_structs.h @@ -0,0 +1,175 @@ +// data structs used for the efm view +// +typedef struct _Smart_Data Smart_Data; +typedef struct _Smart_Data_Thread Smart_Data_Thread; +typedef struct _Block Block; +typedef struct _Icon Icon; + +typedef struct _File_Info File_Info; + +// data kept around as long as the thread is so it knows what it is for +struct _Smart_Data_Thread +{ + Smart_Data *sd; + Eina_Thread_Queue *thq; +}; + +// an icon view gui data +struct _Smart_Data +{ + Evas_Object_Smart_Clipped_Data __clipped_data; + + Eina_Rectangle geom; + + Evas_Object *o_smart; + Evas_Object *o_back; + Evas_Object *o_clip; + Evas_Object *o_scroller; + Evas_Object *o_focus; + Evas_Object *o_sel; + Evas_Object *o_over; + Evas_Object *o_detail_header; + Evas_Object *o_detail_header_item[7]; + + Evas_Object *o_list_detailed_dummy; + Evas_Object *o_list_detail[6]; + Evas_Object *o_list_detail_swallow[6]; + Evas_Object *o_overlay_grid_fill; + Evas_Object *o_overlay_grid; + Evas_Object *o_overlay_info; + + Eina_Stringshare *path; + + Ecore_Exe *exe_open; + Ecore_Event_Handler *handler_exe_del; + Ecore_Event_Handler *handler_exe_data; + + Smart_Data_Thread *thread_data; + Ecore_Thread *thread; + + Eina_List *icons; + Eina_List *blocks; + Ecore_Job *reblock_job; + Ecore_Job *refocus_job; + Ecore_Job *size_bars_update_job; + Ecore_Job *size_max_update_job; + Icon *last_selected; + Icon *last_focused_before; + Icon *last_focused; + Icon *drag_icon; + Icon *over_icon; + Icon *drop_over; + Icon *rename_icon; + + Evas_Coord icon_min_w, icon_min_h; + Evas_Coord list_min_w, list_min_h; + Evas_Coord list_detailed_min_w, list_detailed_min_h; + Evas_Coord back_down_x, back_down_y; + Evas_Coord back_x, back_y; + Evas_Coord sel_x1, sel_y1, sel_x2, sel_y2; + Evas_Coord dnd_scroll_x, dnd_scroll_y; + Evas_Coord detail_down_x, detail_down_y; + Evas_Coord detail_down_start_min_w; + Evas_Coord detail_min_w[6]; + Evas_Coord detail_header_min_h[7]; + double focus_start_time; + double focus_pos; + Ecore_Animator *focus_animator; + Ecore_Timer *scroll_timer; + Ecore_Timer *dnd_scroll_timer; + Ecore_Timer *dnd_over_open_timer; + Elm_Xdnd_Action dnd_action; + + unsigned long long file_max; + + Eina_Bool reblocked : 1; + Eina_Bool relayout : 1; + Eina_Bool focused : 1; + Eina_Bool focus_show : 1; + Eina_Bool key_control : 1; + Eina_Bool back_down : 1; + Eina_Bool sel_show : 1; + Eina_Bool drag : 1; + Eina_Bool just_dragged : 1; + Eina_Bool cnp_have : 1; + Eina_Bool cnp_cut : 1; + Eina_Bool detail_down : 1; + + struct { + Efm_View_Mode view_mode; + Efm_Sort_Mode sort_mode; + int icon_size; + } config; +}; + +// file info gagthered +struct _File_Info +{ + const char *file; + const char *label; + const char *label_selected; + const char *label_clicked; + const char *mime; + const char *icon; + const char *icon_selected; + const char *icon_clicked; + const char *mime_icon; + const char *pre_lookup_icon; + const char *thumb; + Eina_Bool dir : 1; + Eina_Bool link : 1; + Eina_Bool broken : 1; + Eina_Bool special : 1; +}; + +// a block of icons as a group +struct _Block +{ + Eina_Rectangle bounds; // relative to top-left of obj + Smart_Data *sd; + Eina_List *icons; + int realized_num; + int selected_num; + Eina_Bool changed : 1; +}; + +// an icon in a block of icons +struct _Icon +{ + Eina_Rectangle geom; + Evas_Object *o_base; + Evas_Object *o_icon; + Evas_Object *o_entry; + Evas_Object *o_list_detail_swallow[6]; + Evas_Object *o_list_detail_swallow2[6]; + Smart_Data *sd; + Cmd *cmd; + Block *block; + Eina_List *block_list; + Ecore_Timer *longpress_timer; + File_Info info; + Evas_Coord down_x, down_y; + Eina_Bool realized : 1; + Eina_Bool selected : 1; + Eina_Bool down : 1; + Eina_Bool over : 1; + Eina_Bool changed : 1; + Eina_Bool edje : 1; + Eina_Bool renaming : 1; +}; + +typedef struct _Pending_Exe_Del Pending_Exe_Del; + +// an entry in our list of back-end open processes waiting to exit +struct _Pending_Exe_Del +{ + Ecore_Exe *exe; + Ecore_Timer *timer; +}; + +// a message from thread to front-end ui in main loop +typedef struct +{ + Eina_Thread_Queue_Msg head; + Cmd *c; +} Msg; diff --git a/src/efm/efm_util.c b/src/efm/efm_util.c new file mode 100644 index 0000000..5108e53 --- /dev/null +++ b/src/efm/efm_util.c @@ -0,0 +1,1463 @@ +// util funcs for the efm view +static inline int +_xtov(char x) +{ + if ((x >= '0') && (x <= '9')) return x - '0'; + if ((x >= 'a') && (x <= 'f')) return 10 + (x - 'a'); + if ((x >= 'A') && (x <= 'F')) return 10 + (x - 'A'); + return 0; +} + +static unsigned int +_xtoi(const char *str) +{ + unsigned int v = 0; + const char *s; + + for (s = str; *s; s++) + { + v <<= 4; + v += _xtov(*s); + } + return v; +} + +static char * +_escape_parse(const char *str) +{ + char *dest = malloc(strlen(str) + 1); + char *d; + const char *s; + + for (d = dest, s = str; *s; d++) + { + if ((s[0] == '%') && (!isspace(s[1]))) + { + if ((s[1]) && (s[2])) + { + *d = (_xtov(s[1]) << 4) | (_xtov(s[2])); + s += 3; + } + else s++; + } + else + { + *d = s[0]; + s++; + } + } + *d = 0; + return dest; +} + +static void +_strbuf_escape_append(Eina_Strbuf *strbuf, const char *str) +{ + const char hex[] = "0123456789abcdef"; + const char *s; + + for (s = str; *s; s++) + { + if ((*s <- ',') || + (*s == '%') || + ((*s >= ':') && (*s <= '@')) || + ((*s >= '[') && (*s <= ' ')) || + (*s >= '{')) + { + eina_strbuf_append_char(strbuf, '%'); + eina_strbuf_append_char(strbuf, hex[(*s >> 4) & 0xf]); + eina_strbuf_append_char(strbuf, hex[*s & 0xf]); + } + else + eina_strbuf_append_char(strbuf, *s); + } +} + +static double +_scale_get(Smart_Data *sd) +{ + double s1 = elm_object_scale_get(sd->o_scroller); + double s2 = elm_config_scale_get(); + return s1 * s2; +} + +static Eina_Bool +_selected_icons_uri_strbuf_append(Smart_Data *sd, Eina_Strbuf *strbuf) +{ + Eina_List *bl, *il; + Block *block; + Icon *icon; + Eina_Bool added = EINA_FALSE; + + // walk all files and add the selected ones to the uri list buf + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->selected_num == 0) continue; // skip blocks with 0 sel + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (!icon->selected) continue; // skip icons not selected + eina_strbuf_append(strbuf, "file://"); + _strbuf_escape_append(strbuf, icon->sd->path); + eina_strbuf_append_char(strbuf, '/'); + _strbuf_escape_append(strbuf, icon->info.file); + eina_strbuf_append_char(strbuf, '\n'); + added = EINA_TRUE; + } + } + return added; +} + +static void +_detail_realized_items_resize(Smart_Data *sd) +{ + Eina_List *bl, *il; + Block *block; + Icon *icon; + Evas_Object *o; + int i; + char buf[128]; + + if (sd->config.view_mode != EFM_VIEW_MODE_LIST_DETAILED) return; + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->realized_num == 0) continue; // skip non-realized + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (!icon->realized) continue; // skip non-realized + for (i = 0; i < 6; i++) + { + snprintf(buf, sizeof(buf), "e.swallow.detail%i", i + 1); + o = icon->o_list_detail_swallow[i]; + evas_object_size_hint_min_set(o, sd->detail_min_w[i] * _scale_get(sd), 0); + edje_object_part_swallow(icon->o_base, buf, o); + } + } + } +} + +static Icon * +_icon_find_closest(Eina_List *list, Evas_Coord x, Evas_Coord y, Icon *not_icon, Efm_Focus_Dir dir) +{ + Eina_List *l; + Icon *icon, *closest = NULL; + Evas_Coord closest_dist = 0x7fffffff, ix, iy, dx, dy, dist; + + EINA_LIST_FOREACH(list, l, icon) + { + if (icon == not_icon) continue; + ix = icon->geom.x + (icon->geom.w / 2); + iy = icon->geom.y + (icon->geom.h / 2); + switch (dir) + { + case EFM_FOCUS_DIR_UP: + case EFM_FOCUS_DIR_PGUP: + if (iy >= y) continue; + break; + case EFM_FOCUS_DIR_DOWN: + case EFM_FOCUS_DIR_PGDN: + if (iy <= y) continue; + break; + case EFM_FOCUS_DIR_LEFT: + if (ix >= x) continue; + break; + case EFM_FOCUS_DIR_RIGHT: + if (ix <= x) continue; + break; + default: + break; + } + dx = x - ix; + dy = y - iy; + dist = (dx * dx) + (dy * dy); + if (dist < closest_dist) + { + closest = icon; + closest_dist = dist; + } + } + return closest; +} + +static void +_efm_focus_position(Smart_Data *sd) +{ + Evas_Coord ix, iy, iw, ih, ix2, iy2, iw2, ih2; + + if (!sd->last_focused) return; + ix2 = ix = sd->last_focused->geom.x; + iy2 = iy = sd->last_focused->geom.y; + iw2 = iw = sd->last_focused->geom.w; + ih2 = ih = sd->last_focused->geom.h; + if (sd->last_focused_before) + { + ix = sd->last_focused_before->geom.x; + iy = sd->last_focused_before->geom.y; + iw = sd->last_focused_before->geom.w; + ih = sd->last_focused_before->geom.h; + } + if (sd->focus_pos < 1.0) + { + ix2 = ((ix * (1.0 - sd->focus_pos)) + (ix2 * sd->focus_pos)); + iy2 = ((iy * (1.0 - sd->focus_pos)) + (iy2 * sd->focus_pos)); + iw2 = ((iw * (1.0 - sd->focus_pos)) + (iw2 * sd->focus_pos)); + ih2 = ((ih * (1.0 - sd->focus_pos)) + (ih2 * sd->focus_pos)); + } + evas_object_geometry_set(sd->o_focus, + sd->geom.x + ix2, sd->geom.y + iy2, + iw2, ih2); +} + +static Eina_Rectangle +_efm_sel_rect_get(Smart_Data *sd) +{ + Evas_Coord x1, y1, x2, y2; + Eina_Rectangle rect; + + if (sd->sel_x1 < sd->sel_x2) + { + x1 = sd->sel_x1; + x2 = sd->sel_x2; + } + else + { + x1 = sd->sel_x2; + x2 = sd->sel_x1; + } + if (sd->sel_y1 < sd->sel_y2) + { + y1 = sd->sel_y1; + y2 = sd->sel_y2; + } + else + { + y1 = sd->sel_y2; + y2 = sd->sel_y1; + } + rect.x = x1; + rect.y = y1; + rect.w = x2 - x1 + 1; + rect.h = y2 - y1 + 1; + return rect; +} + +static void +_efm_sel_position(Smart_Data *sd) +{ + Eina_Rectangle rect; + + if (!sd->sel_show) return; + rect = _efm_sel_rect_get(sd); + evas_object_geometry_set(sd->o_sel, + sd->geom.x + rect.x, sd->geom.y + rect.y, + rect.w, rect.h); +} + +static Eina_Bool +_cb_focus_anim(void *data) +{ + Smart_Data *sd = data; + + sd->focus_pos = (ecore_loop_time_get() - sd->focus_start_time) / FOCUS_ANIM_TIME; + if (sd->focus_pos < 0.0) sd->focus_pos = 0.0; + _efm_focus_position(sd); + if (sd->focus_pos >= 1.0) + { + sd->focus_animator = NULL; + return EINA_FALSE; + } + return EINA_TRUE; +} + +static void +_icon_focus(Smart_Data *sd) +{ + Evas_Coord ix, iy, iw, ih; + + if ((!sd->last_focused) && (!sd->last_focused_before)) return; + if (!sd->focus_show) + { // just show the focus now + if (sd->last_focused) + { + sd->focus_show = EINA_TRUE; + ix = sd->last_focused->geom.x; + iy = sd->last_focused->geom.y; + iw = sd->last_focused->geom.w; + ih = sd->last_focused->geom.h; + evas_object_geometry_set(sd->o_focus, + sd->geom.x + ix, sd->geom.y + iy, iw, ih); + edje_object_signal_emit(sd->o_focus, "elm,action,focus,show", "elm"); + evas_object_raise(sd->o_focus); + evas_object_show(sd->o_focus); + sd->last_focused_before = sd->last_focused; + } + } + else + { // transition focus to new place + if (sd->last_focused_before) + { + ix = sd->last_focused_before->geom.x; + iy = sd->last_focused_before->geom.y; + iw = sd->last_focused_before->geom.w; + ih = sd->last_focused_before->geom.h; + } + else if (sd->last_focused) + { + ix = sd->last_focused->geom.x; + iy = sd->last_focused->geom.y; + iw = sd->last_focused->geom.w; + ih = sd->last_focused->geom.h; + } + evas_object_geometry_set(sd->o_focus, + sd->geom.x + ix, sd->geom.y + iy, iw, ih); + edje_object_signal_emit(sd->o_focus, "elm,action,focus,show", "elm"); + evas_object_raise(sd->o_focus); + evas_object_show(sd->o_focus); + if (!sd->focus_animator) + sd->focus_animator = ecore_animator_add(_cb_focus_anim, sd); + } + if (sd->o_scroller) + { + if (sd->last_focused) + { + ix = sd->last_focused->geom.x; + iy = sd->last_focused->geom.y; + iw = sd->last_focused->geom.w; + ih = sd->last_focused->geom.h; + elm_scroller_region_bring_in(sd->o_scroller, ix, iy, iw, ih); + } + } +} + +static Eina_Bool +_icon_focus_dir(Smart_Data *sd, Efm_Focus_Dir dir) +{ + Eina_List *bl, *il, *realized = NULL; + Icon *icon; + Block *block; + Evas_Coord ix, iy, iw, ih, vh; + + if (!sd->icons) return EINA_FALSE; + if (!sd->last_focused) + { + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->realized_num > 0) + { + EINA_LIST_FOREACH(block->icons, il, icon) + { + if (icon->realized) + realized = eina_list_append(realized, icon); + } + } + } + icon = NULL; + + switch (dir) + { + case EFM_FOCUS_DIR_UP: + case EFM_FOCUS_DIR_PGUP: // bottom right + icon = _icon_find_closest(realized, sd->geom.w, sd->geom.h, NULL, dir); + break; + case EFM_FOCUS_DIR_DOWN: + case EFM_FOCUS_DIR_RIGHT: + case EFM_FOCUS_DIR_PGDN: // top left + icon = _icon_find_closest(realized, 0, 0, NULL, dir); + break; + case EFM_FOCUS_DIR_LEFT: // top right + icon = _icon_find_closest(realized, sd->geom.w, 0, NULL, dir); + break; + default: + break; + } + eina_list_free(realized); + if (!icon) return EINA_FALSE; + printf("focus %s\n", icon->info.file); + sd->last_focused_before = NULL; + sd->last_focused = icon; + sd->focus_pos = 1.0; + } + else + { + ix = sd->last_focused->geom.x; + iy = sd->last_focused->geom.y; + iw = sd->last_focused->geom.w; + ih = sd->last_focused->geom.h; + icon = NULL; + switch (dir) + { + case EFM_FOCUS_DIR_UP: + if (!((sd->last_focused) && + (sd->last_focused == sd->icons->data))) // first item + { + icon = _icon_find_closest(sd->icons, ix + (iw / 2), iy, sd->last_focused, dir); + } + break; + case EFM_FOCUS_DIR_DOWN: + if (!((sd->last_focused) && + (sd->last_focused == eina_list_last(sd->icons)->data))) // last item + { + icon = _icon_find_closest(sd->icons, ix + (iw / 2), iy + ih, sd->last_focused, dir); + } + break; + case EFM_FOCUS_DIR_LEFT: + if (!((sd->last_focused) && + (sd->last_focused == sd->icons->data))) // first item + { + icon = _icon_find_closest(sd->icons, ix, iy + (ih / 2), sd->last_focused, dir); + if (!icon) + { + EINA_LIST_FOREACH(sd->icons, il, icon) + { + if (icon == sd->last_focused) + { + if (il->prev) icon = il->prev->data; + break; + } + } + } + } + break; + case EFM_FOCUS_DIR_RIGHT: + if (!((sd->last_focused) && + (sd->last_focused == eina_list_last(sd->icons)->data))) // last item + { + icon = _icon_find_closest(sd->icons, ix + iw, iy + (ih / 2), sd->last_focused, dir); + if (!icon) + { + EINA_LIST_FOREACH(sd->icons, il, icon) + { + if (icon == sd->last_focused) + { + if (il->next) icon = il->next->data; + break; + } + } + } + } + break; + case EFM_FOCUS_DIR_PGUP: + if (!((sd->last_focused) && + (sd->last_focused == sd->icons->data))) // first item + { + vh = 0; + if (sd->o_scroller) + evas_object_geometry_get(sd->o_scroller, NULL, NULL, NULL, &vh); + icon = _icon_find_closest(sd->icons, ix + (iw / 2), iy - vh, sd->last_focused, dir); + if (!icon) icon = sd->icons->data; + printf("pu %p\n", icon); + } + break; + case EFM_FOCUS_DIR_PGDN: + if (!((sd->last_focused) && + (sd->last_focused == eina_list_last(sd->icons)->data))) // last item + { + vh = 0; + if (sd->o_scroller) + evas_object_geometry_get(sd->o_scroller, NULL, NULL, NULL, &vh); + icon = _icon_find_closest(sd->icons, ix + (iw / 2), iy + ih + vh, sd->last_focused, dir); + if (!icon) icon = eina_list_last(sd->icons)->data; + printf("pd %p + %i\n", icon, vh); + } + break; + default: + break; + } + if (!icon) return EINA_FALSE; + printf("focus %s\n", icon->info.file); + sd->last_focused_before = sd->last_focused; + sd->last_focused = icon; + sd->focus_pos = 0.0; + sd->focus_start_time = ecore_loop_time_get(); + } + + _icon_focus(sd); + return EINA_TRUE; +} + +static void +_icon_focus_show(Smart_Data *sd) +{ + if (sd->focus_show) return; + sd->focus_show = EINA_TRUE; + evas_object_show(sd->o_focus); +} + +static void +_icon_focus_hide(Smart_Data *sd) +{ + if (!sd->focus_show) return; + sd->focus_show = EINA_FALSE; + evas_object_hide(sd->o_focus); +} + +static void +_icon_object_clear(Icon *icon) +{ + int i; + + // XXX: have a cache for base and icon objects to avoid re-creating them + for (i = 0; i < 6; i++) + { + if (icon->o_list_detail_swallow[i]) + { + evas_object_del(icon->o_list_detail_swallow[i]); + icon->o_list_detail_swallow[i] = NULL; + } + if (icon->o_list_detail_swallow2[i]) + { + evas_object_del(icon->o_list_detail_swallow2[i]); + icon->o_list_detail_swallow2[i] = NULL; + } + } + if (icon->o_icon) + { + evas_object_del(icon->o_icon); + icon->o_icon = NULL; + } + if (icon->o_entry) + { + evas_object_del(icon->o_entry); + icon->o_entry = NULL; + } + if (icon->o_base) + { + evas_object_del(icon->o_base); + icon->o_base = NULL; + } + if ((icon->over) && (icon->sd)) + evas_object_hide(icon->sd->o_over); +} + +static void +_icon_text_update(Icon *icon) +{ // update the label text to be the file or correct label string + const char *str = NULL; + char *txt = NULL; + + if (!icon->o_base) return; + if (icon->down) + { + if (icon->info.label_clicked) str = icon->info.label_clicked; + } + if ((icon->selected) && (!str)) + { + if (icon->info.label_selected) str = icon->info.label_selected; + } + if (!str) + { + if (icon->info.label) str = icon->info.label; + else if (icon->info.file) str = icon->info.file; + } + if (!str) str = "???"; + if (icon->sd->config.view_mode >= EFM_VIEW_MODE_LIST) txt = strdup(str); + else txt = elm_entry_utf8_to_markup(str); + edje_object_part_text_set(icon->o_base, "e.text.label", txt); + free(txt); +} + +static void +_icon_rename_end(Icon *icon) +{ + if (icon->sd->rename_icon != icon) return; + icon->sd->rename_icon = NULL; + printf("end renaming icon %s\n", icon->info.file); + icon->renaming = EINA_FALSE; + evas_object_del(icon->o_entry); + icon->o_entry = NULL; + elm_object_focus_set(icon->sd->o_scroller, EINA_TRUE); +} + +static void +_cb_entry_aborted(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Icon *icon = data; + + _icon_rename_end(icon); +} + +static void +_cb_entry_activated(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Icon *icon = data; + const char *txt; + char *utf; + + txt = elm_object_text_get(icon->o_entry); + if (txt) + { + utf = elm_entry_markup_to_utf8(txt); + if (utf) + { + printf("XXX: rename to [%s]\n", utf); + free(utf); + } + } + _icon_rename_end(icon); +} + +static void +_icon_rename_begin(Icon *icon) +{ + Evas_Object *o; + char *mkup; + + if (icon->sd->rename_icon) _icon_rename_end(icon->sd->rename_icon); + if (icon->sd->drag) return; + icon->sd->rename_icon = icon; + icon->renaming = EINA_TRUE; + icon->o_entry = o = elm_entry_add(icon->sd->o_scroller); + elm_entry_single_line_set(o, EINA_TRUE); + elm_entry_scrollable_set(o, EINA_TRUE); + elm_scroller_policy_set(o, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_OFF); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + // s = elm_entry_markup_to_utf8(s); + mkup = elm_entry_utf8_to_markup(icon->info.file); + elm_object_text_set(o, mkup); + free(mkup); + evas_object_smart_callback_add(o, "aborted", _cb_entry_aborted, icon); + evas_object_smart_callback_add(o, "activated", _cb_entry_activated, icon); + edje_object_part_swallow(icon->o_base, "e.swallow.entry", o); + evas_object_show(o); + elm_object_focus_set(o, EINA_TRUE); +} + +static Eina_Bool +_cb_icon_longpress_timer(void *data) +{ + Icon *icon = data; + + icon->longpress_timer = NULL; + printf("XXX: Long press [%s]\n", icon->info.file); + return EINA_FALSE; +} + +static void +_icon_file_set(Icon *icon, const char *file) +{ + const char *ext; + + ext = strrchr(file, '.'); + if (ext && ((!strcasecmp(ext, ".webm")) || + (!strcasecmp(ext, ".mp4")) || + (!strcasecmp(ext, ".m4v")))) + { + efm_icon_video_set(icon->o_icon, file); + } + else + { + efm_icon_file_set(icon->o_icon, file); + } +} + +static void +_cb_icon_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Evas_Event_Mouse_Down *ev = event_info; + Icon *icon = data; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return; + if ((icon->sd->rename_icon) && (icon->sd->rename_icon != icon)) + _icon_rename_end(icon->sd->rename_icon); + elm_object_focus_set(icon->sd->o_scroller, EINA_TRUE); + icon->sd->key_control = EINA_FALSE; + icon->sd->last_focused = NULL; + icon->sd->last_focused_before = NULL; + icon->sd->just_dragged = EINA_FALSE; + _icon_focus_hide(icon->sd); + if (ev->button == 1) + { + if (icon->longpress_timer) ecore_timer_del(icon->longpress_timer); + icon->longpress_timer = ecore_timer_add(ICON_LONGPRESS_TIMER, + _cb_icon_longpress_timer, + icon); + icon->down = EINA_TRUE; + if (ev->flags == EVAS_BUTTON_NONE) + { // a click - not double-clicked. regular click + icon->down_x = ev->canvas.x; + icon->down_y = ev->canvas.y; + if (!icon->info.thumb) + { + if (icon->edje) + edje_object_signal_emit(icon->o_icon, "e,state,clicked", "e"); + else if (icon->info.icon_clicked) + _icon_file_set(icon, icon->info.icon_clicked); + } + _icon_text_update(icon); + // XXX; start longpress timer + } + else if (ev->flags == EVAS_BUTTON_DOUBLE_CLICK) + { // double clicked + // XXX: + printf("XXX: DBL\n"); + } + } +} + +static void +_icon_select_update(Icon *icon) +{ + int i; + + if (!icon->o_base) return; + if (icon->selected) + { + edje_object_signal_emit(icon->o_base, "e,state,selected", "e"); + if (icon->edje) + edje_object_signal_emit(icon->o_icon, "e,state,selected", "e"); + else if (icon->info.icon_selected) + _icon_file_set(icon, icon->info.icon_selected); + for (i = 0; i < 6; i++) + { + if (icon->o_list_detail_swallow2[i]) + edje_object_signal_emit(icon->o_list_detail_swallow2[i], + "e,state,selected", "e"); + } + } + else + { + edje_object_signal_emit(icon->o_base, "e,state,unselected", "e"); + if (icon->edje) + edje_object_signal_emit(icon->o_icon, "e,state,normal", "e"); + else if ((icon->info.pre_lookup_icon) && + (icon->info.pre_lookup_icon[0] == '/')) + _icon_file_set(icon, icon->info.pre_lookup_icon); + for (i = 0; i < 6; i++) + { + if (icon->o_list_detail_swallow2[i]) + edje_object_signal_emit(icon->o_list_detail_swallow2[i], + "e,state,unselected", "e"); + } + } + _icon_text_update(icon); +} + +static Eina_Bool +_cb_dnd_over_open_timer(void *data) + +{ + Smart_Data *sd = data; + + sd->dnd_over_open_timer = NULL; + if (sd->drop_over) + { + printf("XXX: open hover dir [%s]\n", sd->drop_over->info.file); + // XXX: open dir smart callback + } + return EINA_FALSE; +} + +static void +_icon_over_off(Icon *icon) +{ + icon->over = EINA_FALSE; + edje_object_signal_emit(icon->sd->o_over, "e,state,unselected", "e"); + evas_object_hide(icon->sd->o_over); + if (icon->sd->dnd_over_open_timer) + { + ecore_timer_del(icon->sd->dnd_over_open_timer); + icon->sd->dnd_over_open_timer = NULL; + } +} + +static void +_icon_over_on(Icon *icon) +{ + if (icon->over) return; + if (icon->sd->over_icon) + { + _icon_over_off(icon->sd->over_icon); + icon->sd->over_icon = NULL; + } + if (!icon->info.dir) return; + if ((icon->sd->drag_icon) && (icon->info.file) && + (icon->sd->drag_icon->info.file) && + (!strcmp(icon->sd->drag_icon->info.file, icon->info.file))) + return; + if (icon->sd->dnd_over_open_timer) + ecore_timer_del(icon->sd->dnd_over_open_timer); + icon->sd->dnd_over_open_timer = ecore_timer_add(DND_OVER_OPEN_TIMER, + _cb_dnd_over_open_timer, + icon->sd); + icon->sd->over_icon = icon; + icon->over = EINA_TRUE; + edje_object_signal_emit(icon->sd->o_over, "e,state,selected", "e"); + evas_object_raise(icon->sd->o_over); + evas_object_show(icon->sd->o_over); + evas_object_geometry_set(icon->sd->o_over, + icon->sd->geom.x + icon->geom.x, + icon->sd->geom.y + icon->geom.y, + icon->geom.w, + icon->geom.h); +} + +static void +_icon_select(Icon *icon) +{ + if (icon->selected) return; + icon->selected = EINA_TRUE; + icon->block->selected_num++; + icon->sd->last_selected = icon; + _icon_select_update(icon); +} + +static void +_icon_unselect(Icon *icon) +{ + if (!icon->selected) return; + if (icon->sd->last_selected == icon) icon->sd->last_selected = NULL; + icon->selected = EINA_FALSE; + icon->block->selected_num--; + _icon_select_update(icon); +} + +static Eina_Bool +_unselect_all(Smart_Data *sd) +{ + Eina_List *bl, *il; + Icon *icon; + Block *block; + Eina_Bool had_selected = EINA_FALSE; + + EINA_LIST_FOREACH(sd->blocks, bl, block) + { + if (block->selected_num > 0) + { + had_selected = EINA_TRUE; + EINA_LIST_FOREACH(block->icons, il, icon) + { + _icon_unselect(icon); + } + } + } + return had_selected; +} + +static void +_select_range(Icon *icon_from, Icon *icon_to) +{ + Eina_List *l; + Icon *icon; + Eina_Bool sel = EINA_FALSE; + + EINA_LIST_FOREACH(icon_from->sd->icons, l, icon) + { + if (!sel) + { + if ((icon_from == icon) || (icon_to == icon)) + { + _icon_select(icon); + sel = EINA_TRUE; + } + } + else + { + _icon_select(icon); + if ((icon_from == icon) || (icon_to == icon)) return; + } + } +} + +static void +_cb_icon_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Evas_Event_Mouse_Up *ev = event_info; + Icon *icon = data; + Evas_Coord dx, dy; + Eina_Bool dragged = EINA_FALSE; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) goto done; + if ((ev->button == 1) && (icon->down)) + { // click + release + // XXX: kill longpress timer + dx = ev->canvas.x - icon->down_x; + dy = ev->canvas.y - icon->down_y; + if (((dx * dx) + (dy * dy)) > (5 * 5)) dragged = EINA_TRUE; + if (icon->longpress_timer) + { + ecore_timer_del(icon->longpress_timer); + icon->longpress_timer = NULL; + } + if (!dragged) + { + printf("XXX: mouse clicked\n"); + if (evas_key_modifier_is_set(ev->modifiers, "Shift")) + { // range select + if (icon->sd->last_selected) + _select_range(icon->sd->last_selected, icon); + else + _icon_select(icon); + } + else if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { // multi-single select toggle + if (!icon->selected) _icon_select(icon); + else _icon_unselect(icon); + } + else + { // select just one file so unselect previous files + _unselect_all(icon->sd); + if (!icon->selected) _icon_select(icon); + else _icon_unselect(icon); + } + } + else _icon_select_update(icon); + icon->sd->last_focused = icon; + } + else if (ev->button == 3) + { // right mouse click + // XXX: handle right mouse click on an icon + printf("XXX: right mouse\n"); + } + _icon_focus_hide(icon->sd); +done: + icon->down = EINA_FALSE; +} + +static Icon * +_icon_dup(Icon *icon) +{ + Icon *icon2; + + icon2 = calloc(1, sizeof(Icon)); + if (!icon2) return NULL; + icon2->geom = icon->geom; + icon2->sd = icon->sd; + icon2->down_x = icon->down_x; + icon2->down_y = icon->down_y; + icon2->selected = icon->selected; + icon2->down = icon->down; + icon2->edje = icon->edje; + icon2->info = icon->info; +#define DUPSTRSHARE(X) if (icon->info.X) icon2->info.X = eina_stringshare_add(icon->info.X) + DUPSTRSHARE(file); + DUPSTRSHARE(label); + DUPSTRSHARE(label_selected); + DUPSTRSHARE(label_clicked); + DUPSTRSHARE(mime); + DUPSTRSHARE(icon); + DUPSTRSHARE(icon_selected); + DUPSTRSHARE(icon_clicked); + DUPSTRSHARE(mime_icon); + DUPSTRSHARE(pre_lookup_icon); + DUPSTRSHARE(thumb); +#undef DUPSTRSHARE + icon2->cmd = cmd_dup(icon->cmd); + return icon2; +} + +static void +_cb_icon_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Evas_Event_Mouse_Move *ev = event_info; + Icon *icon = data; + Evas_Coord dx, dy; + Eina_Bool dragged = EINA_FALSE; + + if (!icon->down) return; + // XXX: kill longpress timer + dx = ev->cur.canvas.x - icon->down_x; + dy = ev->cur.canvas.y - icon->down_y; + if (((dx * dx) + (dy * dy)) > (5 * 5)) dragged = EINA_TRUE; + if (!dragged) return; + if (icon->sd->drag) return; + if (icon->longpress_timer) + { + ecore_timer_del(icon->longpress_timer); + icon->longpress_timer = NULL; + } + // mouse has been dragged + if (!icon->selected) + { + if ((!evas_key_modifier_is_set(ev->modifiers, "Shift")) && + (!evas_key_modifier_is_set(ev->modifiers, "Control"))) + _unselect_all(icon->sd); + _icon_select(icon); + } + icon->down = EINA_FALSE; + _icon_select_update(icon); + if (icon->renaming) return; + _drag_start(icon); +} + +static void +_icon_resized(Icon *icon) +{ + int w, h; + + // get the icon size and if it has alpha + efm_icon_size_get(icon->o_icon, &w, &h); + // set the aspect ratio hint based on the above and re-swallow + evas_object_size_hint_aspect_set(icon->o_icon, EVAS_ASPECT_CONTROL_BOTH, w, h); + if ((efm_icon_mono_get(icon->o_icon)) && + (edje_object_part_exists(icon->o_base, "e.swallow.icon_mono"))) + edje_object_part_swallow(icon->o_base, "e.swallow.icon_mono", icon->o_icon); + else + edje_object_part_swallow(icon->o_base, "e.swallow.icon", icon->o_icon); +} + +static void +_cb_icon_resized(void *data, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ // the icon image/thumb has been loaded so update the icon base + Icon *icon = data; + + _icon_resized(icon); +} + +static void +_cb_icon_loaded(void *data, Evas_Object *obj EINA_UNUSED, void *info EINA_UNUSED) +{ // the icon image/thumb has been loaded so update the icon base + Icon *icon = data; + Eina_Bool alpha; + + alpha = efm_icon_alpha_get(icon->o_icon); + _icon_resized(icon); + if (icon->info.thumb) + { // if it's a thumbnail - let's emit to show it + if (alpha) + edje_object_signal_emit(icon->o_base, "e,action,thumb,gen,alpha", "e"); + else + edje_object_signal_emit(icon->o_base, "e,action,thumb,gen", "e"); + } +} + +static void +_cb_icon_label_longpress(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED) +{ + Icon *icon = data; + + if ((icon->sd->drag) || (icon->sd->just_dragged)) return; + _icon_rename_begin(icon); +} + +static void +_icon_object_add(Icon *icon, Smart_Data *sd, Evas *e, const char *theme_edj_file, Eina_Bool clip_set, int num) +{ // either image or theme element + Evas_Object *o, *o2; + const char *icon_file = NULL, *icon_group = NULL, *icon_thumb = NULL; + const char *ic; + char buf[PATH_MAX], buf2[128], *tmps; + int i; + + if (icon->o_base) return; + icon_thumb = icon->info.thumb; + if ((!icon->info.thumb) && (icon->info.pre_lookup_icon)) + { // we're being asked for a std theme icon + ic = icon->info.pre_lookup_icon; + if (icon->down) + { + if (icon->info.icon_clicked) ic = icon->info.icon_clicked; + } + else if (icon->selected) + { + if (icon->info.icon_selected) ic = icon->info.icon_selected; + } + if (ic[0] == '/') + icon_file = ic; + else + { + snprintf(buf, sizeof(buf), "e/icons/%s", ic); + icon_file = elm_theme_group_path_find(NULL, buf); + if (icon_file) icon_group = buf; + } + } + if ((!icon_file) && (!icon_thumb)) + { // theme icon doesn't exit or other reason + // use this icon file + if (icon->info.icon) icon_file = icon->info.icon; + else + { // try a mime type std icon + snprintf(buf, sizeof(buf), "e/icons/fileman/mime/%s", + icon->info.mime); + icon_file = elm_theme_group_path_find(NULL, buf); + // mime type icon exists + if (icon_file) icon_group = buf; + else + { // use an out-of-theme std mime icon + if (icon->info.mime_icon) icon_file = icon->info.mime_icon; + } + } + } + if ((!icon_group) && (!icon_file) && (!icon_thumb)) + { // fallback to plain empty file when no icon found + snprintf(buf, sizeof(buf), "e/icons/fileman/mime/inode/file"); + icon_file = elm_theme_group_path_find(NULL, buf); + icon_group = buf; + } + // about to realize if it hasn't been + // XXX: have a cache for base and icon objects to avoid re-creating them + icon->o_base = o = edje_object_add(e); + if (sd->config.view_mode == EFM_VIEW_MODE_ICONS) + edje_object_file_set(o, theme_edj_file, "e/fileman/default/icon/fixed"); + else if (sd->config.view_mode == EFM_VIEW_MODE_LIST) + { + if (num & 0x1) + edje_object_file_set(o, theme_edj_file, + "e/fileman/default/list_odd/fixed"); + else + edje_object_file_set(o, theme_edj_file, + "e/fileman/default/list/fixed"); + } + else if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + { + const char *s; + + if (num & 0x1) + edje_object_file_set(o, theme_edj_file, + "e/fileman/default/list_odd/detailed"); + else + edje_object_file_set(o, theme_edj_file, + "e/fileman/default/list/detailed"); + s = cmd_key_find(icon->cmd, "size"); + if (s) + { + unsigned long long size = atoll(s); + + icon->o_list_detail_swallow[0] = o2 = + elm_grid_add(sd->o_scroller); + elm_grid_size_set(o2, 1, 1); + evas_object_size_hint_min_set + (o2, sd->detail_min_w[0] * _scale_get(sd), 0); + edje_object_part_swallow(icon->o_base, "e.swallow.detail1", o2); + icon->o_list_detail_swallow2[0] = o2 = + edje_object_add(e); + edje_object_file_set(o2, theme_edj_file, + "e/fileman/default/filesize"); + if (icon->selected) + edje_object_signal_emit(o2, "e,state,selected", "e"); + else + edje_object_signal_emit(o2, "e,state,unselected", "e"); + elm_grid_pack(icon->o_list_detail_swallow[0], o2, 0, 0, 1, 1); + evas_object_show(o2); + + if (sd->file_max > 0) + _size_message(o2, (double)size / (double)sd->file_max); + else + _size_message(o2, 0.0); + + if (size < 1024) + { + edje_object_part_text_set(o2, "e.text.unit", "b"); + } + else if (size < (1024LL * 1024LL)) + { + size = ((size + ((1024LL) - 1)) >> 10); + edje_object_part_text_set(o2, "e.text.unit", "K"); + } + else if (size < (1024LL * 1024LL * 1024LL)) + { + size = ((size + ((1024LL * 1024LL) - 1)) >> 20); + edje_object_part_text_set(o2, "e.text.unit", "M"); + } + else if (size < (1024LL * 1024LL * 1024LL * 1024LL)) + { + size = ((size + ((1024LL * 1024LL * 1024LL) - 1)) >> 30); + edje_object_part_text_set(o2, "e.text.unit", "G"); + } + else if (size < (1024LL * 1024LL * 1024LL * 1024LL * 1024LL)) + { + size = ((size + ((1024LL * 1024LL * 1024LL * 1024LL) - 1)) >> 40); + edje_object_part_text_set(o2, "e.text.unit", "T"); + } + snprintf(buf2, sizeof(buf2), "%i", (int)size); + edje_object_part_text_set(o2, "e.text.label", buf2); + edje_object_message_signal_process(o2); + } + s = cmd_key_find(icon->cmd, "mtime"); + if (s) + { + time_t tmpt = atoll(s); + struct tm *info; + + info = localtime(&tmpt); + + icon->o_list_detail_swallow[1] = o2 = + elm_grid_add(sd->o_scroller); + elm_grid_size_set(o2, 1, 1); + evas_object_size_hint_min_set + (o2, sd->detail_min_w[1] * _scale_get(sd), 0); + edje_object_part_swallow(icon->o_base, "e.swallow.detail2", o2); + icon->o_list_detail_swallow2[1] = o2 = + edje_object_add(e); + edje_object_file_set(o2, theme_edj_file, + "e/fileman/default/filedate"); + if (icon->selected) + edje_object_signal_emit(o2, "e,state,selected", "e"); + else + edje_object_signal_emit(o2, "e,state,unselected", "e"); + elm_grid_pack(icon->o_list_detail_swallow[1], o2, 0, 0, 1, 1); + evas_object_show(o2); + + tmps = eina_strftime("%y", info); + edje_object_part_text_set(o2, "e.text.year", tmps); + free(tmps); + tmps = eina_strftime("%b", info); + edje_object_part_text_set(o2, "e.text.month", tmps); + free(tmps); + tmps = eina_strftime("%d", info); + edje_object_part_text_set(o2, "e.text.day", tmps); + free(tmps); + tmps = eina_strftime("%H:%M:%S", info); + edje_object_part_text_set(o2, "e.text.time", tmps); + free(tmps); +// tmps = eina_strftime("%b %d %y %H:%M:%S", info); +// edje_object_part_text_set(icon->o_base, "e.text.detail2", tmps); +// free(tmps); + } + s = icon->info.mime; + if (!s) s = ""; + edje_object_part_text_set(icon->o_base, "e.text.detail3", s); + s = cmd_key_find(icon->cmd, "user"); + if (s) edje_object_part_text_set(icon->o_base, "e.text.detail4", s); + s = cmd_key_find(icon->cmd, "group"); + if (s) edje_object_part_text_set(icon->o_base, "e.text.detail5", s); + s = cmd_key_find(icon->cmd, "mode"); + if (s) + { + int mode = _xtoi(s); + + icon->o_list_detail_swallow[5] = o2 = + elm_grid_add(sd->o_scroller); + elm_grid_size_set(o2, 1, 1); + evas_object_size_hint_min_set + (o2, sd->detail_min_w[5] * _scale_get(sd), 0); + edje_object_part_swallow(icon->o_base, "e.swallow.detail6", o2); + icon->o_list_detail_swallow2[5] = o2 = + edje_object_add(e); + edje_object_file_set(o2, theme_edj_file, + "e/fileman/default/fileperms"); + if (icon->selected) + edje_object_signal_emit(o2, "e,state,selected", "e"); + else + edje_object_signal_emit(o2, "e,state,unselected", "e"); + elm_grid_pack(icon->o_list_detail_swallow[5], o2, 0, 0, 1, 1); + evas_object_show(o2); + edje_object_signal_emit(o2, "e,type,reset", "e"); + if (S_ISREG(mode)) edje_object_signal_emit(o2, "e,type,none", "e"); + else if (S_ISDIR(mode)) edje_object_signal_emit(o2, "e,type,dir", "e"); + else if (S_ISLNK(mode)) edje_object_signal_emit(o2, "e,type,link", "e"); + else if (S_ISFIFO(mode)) edje_object_signal_emit(o2, "e,type,pipe", "e"); + else if (S_ISSOCK(mode)) edje_object_signal_emit(o2, "e,type,socket", "e"); + else if (S_ISBLK(mode)) edje_object_signal_emit(o2, "e,type,block", "e"); + else if (S_ISCHR(mode)) edje_object_signal_emit(o2, "e,type,char", "e"); + edje_object_signal_emit(o2, "e,perm,reset", "e"); + if ((mode & S_ISUID)) edje_object_signal_emit(o2, "e,perm,user,setuid", "e"); + if ((mode & S_IRUSR)) edje_object_signal_emit(o2, "e,perm,user,read", "e"); + if ((mode & S_IWUSR)) edje_object_signal_emit(o2, "e,perm,user,write", "e"); + if ((mode & S_IXUSR)) edje_object_signal_emit(o2, "e,perm,user,execute", "e"); + if ((mode & S_ISGID)) edje_object_signal_emit(o2, "e,perm,group,setuid", "e"); + if ((mode & S_IRGRP)) edje_object_signal_emit(o2, "e,perm,group,read", "e"); + if ((mode & S_IWGRP)) edje_object_signal_emit(o2, "e,perm,group,write", "e"); + if ((mode & S_IXGRP)) edje_object_signal_emit(o2, "e,perm,group,execute", "e"); + if ((mode & S_IROTH)) edje_object_signal_emit(o2, "e,perm,other,read", "e"); + if ((mode & S_IWOTH)) edje_object_signal_emit(o2, "e,perm,other,write", "e"); + if ((mode & S_IXOTH)) edje_object_signal_emit(o2, "e,perm,other,execute", "e"); + edje_object_message_signal_process(o2); + } + if (sd->config.view_mode == EFM_VIEW_MODE_LIST_DETAILED) + { + for (i = 2; i < 5; i++) + { + icon->o_list_detail_swallow[i] = o2 = + evas_object_rectangle_add(e); + evas_object_color_set(o2, 0, 0, 0, 0); + evas_object_size_hint_min_set + (o2, sd->detail_min_w[i] * _scale_get(sd), 0); + snprintf(buf2, sizeof(buf2), "e.swallow.detail%i", i + 1); + edje_object_part_swallow(icon->o_base, buf2, o2); + } + } + } + edje_object_preload(o, EINA_FALSE); + edje_object_signal_callback_add(o, "e,action,label,click", "e", + _cb_icon_label_longpress, icon); + icon->edje = EINA_FALSE; + if ((!icon_group) && (icon_file)) + { // image file + icon->o_icon = o = efm_icon_add(o); + evas_object_smart_callback_add(o, "resized", _cb_icon_resized, icon); + evas_object_smart_callback_add(o, "loaded", _cb_icon_loaded, icon); + _icon_file_set(icon, icon_file); + } + else if ((icon_group) && (icon_file)) + { // theme element + icon->o_icon = o = edje_object_add(e); + edje_object_file_set(o, icon_file, icon_group); + edje_object_preload(o, EINA_FALSE); + icon->edje = EINA_TRUE; + } + else if (icon_thumb) + { // thumbnail file + icon->o_icon = o = efm_icon_add(o); + evas_object_smart_callback_add(o, "resized", _cb_icon_resized, icon); + evas_object_smart_callback_add(o, "loaded", _cb_icon_loaded, icon); + efm_icon_thumb_set(o, icon_thumb); + } + _icon_text_update(icon); + edje_object_part_swallow(icon->o_base, "e.swallow.icon", o); + evas_object_show(icon->o_icon); + if (clip_set) + { + evas_object_smart_member_add(icon->o_base, sd->o_smart); + evas_object_clip_set(icon->o_base, sd->o_clip); + } + + if (icon->selected) + edje_object_signal_emit(icon->o_base, "e,state,selected", "e"); + else + edje_object_signal_emit(icon->o_base, "e,state,unselected", "e"); + if (icon->info.broken) + edje_object_signal_emit(icon->o_base, "e,state,broken", "e"); + else if (icon->info.link) + edje_object_signal_emit(icon->o_base, "e,state,link", "e"); + if (icon->info.special) + edje_object_signal_emit(icon->o_base, "e,type,special", "e"); + if (icon->info.thumb) + { + if (efm_icon_mono_get(icon->o_icon)) + { + edje_object_signal_emit(icon->o_base, "e,type,thumb,mono", "e"); + if (edje_object_part_exists(icon->o_base, "e.swallow.icon_mono")) + edje_object_part_swallow(icon->o_base, "e.swallow.icon_mono", o); + evas_object_show(icon->o_icon); + } + else + edje_object_signal_emit(icon->o_base, "e,type,thumb", "e"); + } + + evas_object_event_callback_add(icon->o_base, EVAS_CALLBACK_MOUSE_DOWN, + _cb_icon_mouse_down, icon); + evas_object_event_callback_add(icon->o_base, EVAS_CALLBACK_MOUSE_UP, + _cb_icon_mouse_up, icon); + evas_object_event_callback_add(icon->o_base, EVAS_CALLBACK_MOUSE_MOVE, + _cb_icon_mouse_move, icon); + + evas_object_show(icon->o_base); + icon->realized = EINA_TRUE; + if (clip_set) + { + evas_object_raise(sd->o_focus); + evas_object_raise(sd->o_sel); + } + if (icon->renaming) + { + icon->sd->rename_icon = NULL; + _icon_rename_begin(icon); + } +} + +static void +_icon_free(Icon *icon) +{ // free an icon - not needed anymore + if (icon->sd) + { + if (icon->sd->last_selected == icon) icon->sd->last_selected = NULL; + if (icon->sd->last_focused == icon) icon->sd->last_focused = NULL; + if (icon->sd->over_icon == icon) _icon_over_off(icon); + if (icon->sd->drop_over == icon) icon->sd->drop_over = NULL; + if (icon->sd->rename_icon == icon) _icon_rename_end(icon); + } + if (icon->block) + { + if (icon->selected) icon->block->selected_num--; + if (icon->realized) icon->block->realized_num--; + icon->block->icons = eina_list_remove(icon->block->icons, icon); + } + if (icon->longpress_timer) + { + ecore_timer_del(icon->longpress_timer); + icon->longpress_timer = NULL; + } + _icon_object_clear(icon); + eina_stringshare_replace(&icon->info.file, NULL); + eina_stringshare_replace(&icon->info.label, NULL); + eina_stringshare_replace(&icon->info.label_selected, NULL); + eina_stringshare_replace(&icon->info.label_clicked, NULL); + eina_stringshare_replace(&icon->info.mime, NULL); + eina_stringshare_replace(&icon->info.icon, NULL); + eina_stringshare_replace(&icon->info.icon_selected, NULL); + eina_stringshare_replace(&icon->info.icon_clicked, NULL); + eina_stringshare_replace(&icon->info.mime_icon, NULL); + eina_stringshare_replace(&icon->info.pre_lookup_icon, NULL); + eina_stringshare_replace(&icon->info.thumb, NULL); + cmd_free(icon->cmd); + icon->cmd = NULL; + free(icon); +} + +static void +_block_free_final(Block *block) +{ // remove our block from storage + block->icons = eina_list_free(block->icons); + free(block); +} + +static void +_block_free(Block *block) +{ // free a block when we're not done with it - not used when reblocking + Eina_List *il; + Icon *icon; + + if (block->realized_num > 0) + { // we have some realized icons - so clear them + EINA_LIST_FOREACH(block->icons, il, icon) + { + icon->realized = EINA_FALSE; + icon->block = NULL; + _icon_object_clear(icon); + } + } + else + { + EINA_LIST_FOREACH(block->icons, il, icon) + { + icon->block = NULL; + } + } + _block_free_final(block); +} + +static void +_cb_reblock(void *data) +{ // re-do all our blocks of icons that we divide into to avoid looking at + // batches of icons as a group/block when recvalculating when that whole + // group is already known to not be visible as a whole, so skip it as + // a while thus keeping the recalculations to a reasonably small set + // instead of everything + Smart_Data *sd = data; + Eina_List *l; + Icon *icon; + Block *block; + + sd->reblock_job = NULL; + // special - remove our blocks but leave icons realized as we rebuild + // the block list next + EINA_LIST_FREE(sd->blocks, block) _block_free_final(block); + // walk all icons and figure out which blocks they belong to + block = NULL; + EINA_LIST_FOREACH(sd->icons, l, icon) + { + // if our block is full move onto making a new block + if ((block) && (eina_list_count(block->icons) >= BLOCK_MAX)) + block = NULL; + // we don't have a block to add this icon to - so make one + if (!block) + { + block = calloc(1, sizeof(Block)); + if (!block) abort(); + // add the block to our block list + sd->blocks = eina_list_append(sd->blocks, block); + block->sd = sd; + } + // add icon to block icons + block->icons = eina_list_append(block->icons, icon); + icon->block = block; + icon->block_list = eina_list_last(block->icons); + // adjust block reslize num if icon is realized + if (icon->realized) block->realized_num++; + if (icon->selected) block->selected_num++; + } + // flag the obj to have had our blocks re-done so we also then re-calc + // which blocks are visible or not and unrealize/realize icons as + // needed etc. + sd->reblocked = EINA_TRUE; + evas_object_smart_changed(sd->o_smart); +} diff --git a/src/efm/main.c b/src/efm/main.c new file mode 100644 index 0000000..57b9454 --- /dev/null +++ b/src/efm/main.c @@ -0,0 +1,189 @@ +// test gui front-end for new efm +#include "efm.h" + +//#include "efm_icon.h" + +static Evas_Object *o_detail_header_box = NULL; +static Evas_Object *o_detail_header = NULL; + +static void +_cb_icons(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + efm_path_view_mode_set(data, EFM_VIEW_MODE_ICONS); + if (o_detail_header) + { + evas_object_del(o_detail_header); + o_detail_header = NULL; + } +} + +static void +_cb_icons_custom(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + efm_path_view_mode_set(data, EFM_VIEW_MODE_ICONS_CUSTOM); + if (o_detail_header) + { + evas_object_del(o_detail_header); + o_detail_header = NULL; + } +} + +static void +_cb_list(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + efm_path_view_mode_set(data, EFM_VIEW_MODE_LIST); + if (o_detail_header) + { + evas_object_del(o_detail_header); + o_detail_header = NULL; + } +} + +static void +_cb_list_detailed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *o; + + efm_path_view_mode_set(data, EFM_VIEW_MODE_LIST_DETAILED); + + if (!o_detail_header) + { + o_detail_header = o = efm_detail_header_get(data); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + elm_box_pack_end(o_detail_header_box, o); + evas_object_show(o); + } +} + +EAPI_MAIN int +elm_main(int argc, char **argv) +{ + const char *path; + Evas_Object *o, *win, *sc, *efm, *bx, *bx2; + char buf[PATH_MAX]; + + if (argc > 1) + { + path = ecore_file_realpath(argv[1]); + } + else + { + getcwd(buf, sizeof(buf)); + path = buf; + } + + elm_need_efreet(); + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR); + elm_app_compile_lib_dir_set(PACKAGE_LIB_DIR); + elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); + elm_app_info_set(elm_main, "efm", "checkme"); + + win = o = elm_win_util_standard_add("Main", "Files"); + elm_win_icon_name_set(o, "Files"); + elm_win_role_set(o, "efm_win_files"); + elm_win_autodel_set(o, EINA_TRUE); + +/* + ic = o = evas_object_image_add(evas_object_evas_get(win)); + icon = efreet_icon_path_find(elm_config_icon_theme_get(), + "inode-directory", 0); + if (icon) printf("%s\n", icon); + evas_object_image_file_set(o, icon, NULL); + elm_win_icon_object_set(win, o); + evas_object_show(o); + */ + + bx = o = elm_box_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_box_horizontal_set(o, EINA_FALSE); + elm_win_resize_object_add(win, o); + evas_object_show(o); + + bx2 = o = elm_box_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_box_homogeneous_set(o, EINA_TRUE); + elm_box_horizontal_set(o, EINA_TRUE); + elm_box_pack_end(bx, o); + evas_object_show(o); + + o = elm_button_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_object_text_set(o, "X"); + elm_box_pack_end(bx2, o); + evas_object_show(o); + + o_detail_header_box = o = elm_box_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_box_pack_end(bx, o); + evas_object_show(o); + + sc = o = elm_scroller_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_box_pack_end(bx, o); + evas_object_show(o); + + efm = o = efm_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_object_content_set(sc, o); + efm_scroller_set(o, sc); + efm_path_set(o, path); + evas_object_show(o); + + bx2 = o = elm_box_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_box_homogeneous_set(o, EINA_TRUE); + elm_box_horizontal_set(o, EINA_TRUE); + elm_box_pack_end(bx, o); + evas_object_show(o); + + o = elm_button_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_object_text_set(o, "Icons"); + evas_object_smart_callback_add(o, "clicked", _cb_icons, efm); + elm_box_pack_end(bx2, o); + evas_object_show(o); + + o = elm_button_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_object_text_set(o, "List"); + evas_object_smart_callback_add(o, "clicked", _cb_list, efm); + elm_box_pack_end(bx2, o); + evas_object_show(o); + + o = elm_button_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_object_text_set(o, "Detailed"); + evas_object_smart_callback_add(o, "clicked", _cb_list_detailed, efm); + elm_box_pack_end(bx2, o); + evas_object_show(o); + + o = elm_button_add(win); + evas_object_size_hint_fill_set(o, EVAS_HINT_FILL, 0); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0); + elm_object_text_set(o, "Custom"); + evas_object_smart_callback_add(o, "clicked", _cb_icons_custom, efm); + elm_box_pack_end(bx2, o); + evas_object_show(o); + + evas_object_resize(win, ELM_SCALE_SIZE(700), ELM_SCALE_SIZE(300)); + evas_object_show(win); + + elm_object_focus_set(sc, EINA_TRUE); + + elm_run(); + + return 0; +} +ELM_MAIN() diff --git a/src/efm/meson.build b/src/efm/meson.build new file mode 100644 index 0000000..19515f8 --- /dev/null +++ b/src/efm/meson.build @@ -0,0 +1,18 @@ +dir = join_paths(dir_bin) +inc = include_directories( + '.', + '../..', + '../shared/commands', + '../shared/common' +) +executable('efm', [ + '../shared/common/cmd.c', + 'efm.c', + 'sort.c', + 'efm_icon.c', + 'main.c' + ], + include_directories: inc, + dependencies: deps, + install: true, + install_dir: dir) diff --git a/src/efm/sort.c b/src/efm/sort.c new file mode 100644 index 0000000..704b8a5 --- /dev/null +++ b/src/efm/sort.c @@ -0,0 +1,240 @@ +#include "cmd.h" +#include "sort.h" +#include "efm.h" +#include +#include +#include +#include +#include + +// sorting helpers +enum +{ + SORT_LESS = -1, + SORT_MORE = 1, + SORT_INVALID = -100 +}; + +static char +_str_mode_file_type(int mode) +{ + char c = '?'; + + if (S_ISREG(mode)) c = '-'; + else if (S_ISDIR(mode)) c = 'd'; + else if (S_ISBLK(mode)) c = 'b'; + else if (S_ISCHR(mode)) c = 'c'; +#ifdef S_ISFIFO + else if (S_ISFIFO(mode)) c = 'p'; +#endif +#ifdef S_ISLNK + else if (S_ISLNK(mode)) c = 'l'; +#endif +#ifdef S_ISSOCK + else if (S_ISSOCK(mode)) c = 's'; +#endif +#ifdef S_ISDOOR + else if (S_ISDOOR(mode)) c = 'D'; +#endif + return c; +} + +static void +_str_mode(int mode, char str[11]) +{ // generate string mode i9nto dest str buffer + static const char *rwx[] = { + "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" + }; + + str[0] = _str_mode_file_type(mode); + strcpy(&str[1], rwx[(mode >> 6)& 7]); + strcpy(&str[4], rwx[(mode >> 3)& 7]); + strcpy(&str[7], rwx[(mode & 7)]); + if (mode & S_ISUID) str[3] = (mode & S_IXUSR) ? 's' : 'S'; + if (mode & S_ISGID) str[6] = (mode & S_IXGRP) ? 's' : 'l'; + if (mode & S_ISVTX) str[9] = (mode & S_IXOTH) ? 't' : 'T'; + str[10] = 0; +} + +static int +_sort_str_field(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2), const char *field) +{ // a string fields - get it and sort using compare func + const char *field1, *field2, *path1, *path2; + int res; + + field1 = cmd_key_find(c1, field); + if (!field1) return 0; + + field2 = cmd_key_find(c2, field); + if (!field2) return 0; + + res = cmpfn(field1, field2); + if (!res) + { // they are the same string! use pure filename path as it's unique + path1 = cmd_key_find(c1, "path"); + path2 = cmd_key_find(c2, "path"); + res = cmpfn(path1, path2); + if (!res) return strcmp(path1, path2); + return res; + } + return cmpfn(field1, field2); +} + +static int +_sort_num_field(const Cmd *c1, const Cmd *c2, const char *field) +{ // user a named field that is a number like mtime, ctime, size etc ... + const char *f, *path1, *path2; + unsigned long long field1 = 0, field2 = 0; + + // get field and convert to ulonglong + f = cmd_key_find(c1, field); + if (f) field1 = strtoull(f, NULL, 10); + + // get field and convert to ulonglong + f = cmd_key_find(c2, field); + if (f) field2 = strtoull(f, NULL, 10); + + if (field1 == field2) + { // they are the same string! use pure filename path as it's unique + path1 = cmd_key_find(c1, "path"); + path2 = cmd_key_find(c2, "path"); + return strcmp(path1, path2); + } + // sort result + if (field1 > field2) return SORT_MORE; + return SORT_LESS; +} + +static int +_sort_path(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2)) +{ // use pure filename path not label + return _sort_str_field(c1, c2, cmpfn, "path"); +} + +static int +_sort_label(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2)) +{ // sort by label or path - some files like .desktop files have labels + const char *path1, *path2; + int res; + + // get a label instead of filename path element if available + path1 = cmd_key_find(c1, "label"); + if (!path1) path1 = cmd_key_find(c1, "path"); + + // get a label instead of filename path element if available + path2 = cmd_key_find(c2, "label"); + if (!path2) path2 = cmd_key_find(c2, "path"); + + res = cmpfn(path1, path2); + if (!res) + { // they are the same string! use pure filename path as it's unique + path1 = cmd_key_find(c1, "path"); + path2 = cmd_key_find(c2, "path"); + res = cmpfn(path1, path2); + if (!res) return strcmp(path1, path2); + return res; + } + return res; +} + +static int +_sort_mode(const Cmd *c1, const Cmd *c2) +{ // sort by mode string like "drwxr-xr-x" or "-rw-r--r--" + const char *f; + int mode1 = 0, mode2 = 0, ret = 0; + char m1[11], m2[11]; + + // get mdoe and generate mode string + f = cmd_key_find(c1, "mode"); + if (f) mode1 = strtoull(f, NULL, 16); + _str_mode(mode1, m1); + + // get mdoe and generate mode string + f = cmd_key_find(c2, "mode"); + if (f) mode2 = strtoull(f, NULL, 16); + _str_mode(mode2, m2); + + // sort by the mode string + ret = strcmp(m1, m2); + return ret; +} + +static int +_sort_dir_not_dir(const Cmd *c1, const Cmd *c2) +{ + const char *type1, *type2; + + // get file types for both - and if link resolve the link dest type + type1 = cmd_key_find(c1, "type"); + if ((type1) && (!strcmp(type1, "link"))) + type1 = cmd_key_find(c1, "link-type"); + if (!type1) type1 = "file"; + + type2 = cmd_key_find(c2, "type"); + if ((type2) && (!strcmp(type2, "link"))) + type2 = cmd_key_find(c2, "link-type"); + if (!type2) type2 = "file"; + + // 1st is dir, 2nd is not dir + if (( !strcmp(type1, "dir")) && (!!strcmp(type2, "dir"))) return SORT_LESS; + // 1st is not dir, 2nd is dir + if ((!!strcmp(type1, "dir")) && ( !strcmp(type2, "dir"))) return SORT_MORE; + // both are dir or both are not dir + return SORT_INVALID; +} + +// sorter logic +static int +_sort_cmd_do(const Cmd *c1, const Cmd *c2) +{ + int dir_not_dir = SORT_INVALID; + Efm_Sort_Mode sort_mode; + int (*cmpfn) (const char *s1, const char *s2) = strcmp; + Eina_Bool label_sort = EINA_FALSE; + + sort_mode = c1->sort_mode; + // handle flags + if (sort_mode & EFM_SORT_MODE_DIRS_FIRST) + dir_not_dir = _sort_dir_not_dir(c1, c2); + if (sort_mode & EFM_SORT_MODE_NOCASE) + cmpfn = strcasecmp; + if (sort_mode & EFM_SORT_MODE_LABEL_NOT_PATH) + label_sort = EINA_TRUE; + + // handle each sort mode + + // if one is a dir and one is not - other sorting compares are not useful + // so just return this status + if (dir_not_dir != SORT_INVALID) return dir_not_dir; + + // handle the actual specific sort field to use + switch (sort_mode & EFM_SORT_MODE_MASK) + { + case EFM_SORT_MODE_NAME: + if (label_sort) return _sort_label(c1, c2, cmpfn); + return _sort_path(c1, c2, cmpfn); + case EFM_SORT_MODE_SIZE: + return _sort_num_field(c1, c2, "size"); + case EFM_SORT_MODE_DATE: + return _sort_num_field(c1, c2, "mtime"); + case EFM_SORT_MODE_MIME: + return _sort_str_field(c1, c2, strcmp, "mime"); + case EFM_SORT_MODE_USER: + return _sort_str_field(c1, c2, strcmp, "user"); + case EFM_SORT_MODE_GROUP: + return _sort_str_field(c1, c2, strcmp, "group"); + case EFM_SORT_MODE_PERMISSIONS: + return _sort_mode(c1, c2); + default: // unknown - so just plain path + goto def; + break; + } +def: + return _sort_path(c1, c2, cmpfn); +} + +int +sort_cmd(const void *c1, const void *c2) +{ + return _sort_cmd_do(c1, c2); +} diff --git a/src/efm/sort.h b/src/efm/sort.h new file mode 100644 index 0000000..0a0b0dc --- /dev/null +++ b/src/efm/sort.h @@ -0,0 +1,6 @@ +#ifndef SORT_H +# define SORT_H 1 + +int sort_cmd(const void *c1, const void *c2); + +#endif diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..a143634 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,2 @@ +subdir('backends') +subdir('efm') diff --git a/src/shared/commands/.dummy b/src/shared/commands/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/common/cmd.c b/src/shared/common/cmd.c new file mode 100644 index 0000000..95285cb --- /dev/null +++ b/src/shared/common/cmd.c @@ -0,0 +1,226 @@ + // basic command parsing, building and sending funcs used by other files to +// deal with a string command stream sanely so all the parsing of the core +// formatting is on one place +#include "cmd.h" +#include +#include +#include +#include +#include + +// parse a single command line that is: +// cmd xxx=yyy a=bbb ... +// and return the command struct +Cmd * +cmd_parse(const char *cmd) +{ + char *b; + const char *s, *begin; + int d, dmax; + Cmd *cnew, *c = malloc(sizeof(Cmd) + (2 * sizeof(char *))); + + if (strncmp(cmd, "CMD ", 4)) return NULL; + cmd += 4; + if (!c) return NULL; + c->buf_size = strlen(cmd) + 1; + c->buf = malloc(c->buf_size); // buf that unescapes always same/smaller + c->command = NULL; + c->dict[0] = c->dict[1] = NULL; + // parse out command string until spaces - then skip them, then begin dict + b = c->buf; + for (s = cmd; (!isspace(*s)) && (*s); s++) + { + *b = *s; + b++; + } + *b = 0; + b++; + c->command = c->buf; + for (; (*s) && isspace(*s); s++); + d = 0; + dmax = 2; + while (*s) + { + begin = b; + // parse up to = + while ((*s) && (*s != '=') && !isspace(*s)) *b++ = *s++; + if (*s == '=') + { + s++; // skip = + *b++ = 0; + c->dict[d++] = begin; + begin = b; + // parse up to space - handle escapes + while ((*s) && (!isspace(*s))) + { + if (*s == '\\') + { + s++; + if (*s == 'x') + { + s++; + if (s[0] && s[1]) + { + int hex1 = 0, hex2 = 0; + if ((s[0] >= '0') && (s[0] <= '9')) hex1 = s[0] - '0'; + if ((s[0] >= 'a') && (s[0] <= 'f')) hex1 = 10 + s[0] - 'a'; + if ((s[0] >= 'A') && (s[0] <= 'F')) hex1 = 10 + s[0] - 'A'; + if ((s[1] >= '0') && (s[1] <= '9')) hex2 = s[1] - '0'; + if ((s[1] >= 'a') && (s[1] <= 'f')) hex2 = 10 + s[1] - 'a'; + if ((s[1] >= 'A') && (s[1] <= 'F')) hex2 = 10 + s[1] - 'A'; + *b++ = (hex1 << 4) | hex2; + s += 2; + } + } + else if (*s == ' ') *b++ = ' '; + else if (*s == 'a') *b++ = '\a'; + else if (*s == 'b') *b++ = '\b'; + else if (*s == 't') *b++ = '\t'; + else if (*s == 'n') *b++ = '\n'; + else if (*s == 'v') *b++ = '\v'; + else if (*s == 'f') *b++ = '\f'; + else if (*s == 'r') *b++ = '\r'; + else if (*s == '`') *b++ = '`'; + else if (((*s >= '>') && (*s <= '*')) || + ((*s >= ';') && (*s <= '?')) || + ((*s >= '[') && (*s <= ']')) || + ((*s >= '{') && (*s <= '~'))) + *b++ = *s++; + } + else *b++ = *s++; + } + *b++ = 0; + c->dict[d++] = begin; + dmax += 2; + cnew = realloc(c, sizeof(Cmd) + (sizeof(char *) * (dmax + 2))); + if (!cnew) + { + free(c->buf); + free(c); + return NULL; + } + c = cnew; + c->dict[d] = NULL; + c->dict[d + 1] = NULL; + } + // skip spaces + for (; (*s) && isspace(*s); s++); + } + return c; +} + +void +cmd_free(Cmd *c) +{ + if (!c) return; + free(c->buf); + free(c); +} + +Cmd * +cmd_dup(const Cmd *c) +{ + Cmd *c2; + int i, dict_num = 0; + + for (i = 0; c->dict[i]; i++); // count dict keys + dict_num = i + 2; + c2 = calloc(1, sizeof(Cmd) + (sizeof(char *) * (dict_num + 2))); + if (!c2) return NULL; + c2->buf_size = c->buf_size; + if (c2->buf_size != 0) + { + c2->buf = malloc(c->buf_size); + if (!c2->buf) goto err; + memcpy(c2->buf, c->buf, c->buf_size); + } + c2->command = c2->buf; + for (i = 0; c->dict[i]; i++) + c2->dict[i] = c2->buf + (c->dict[i] - c->buf); + return c2; +err: + cmd_free(c2); + return NULL; +} + +void +cmd_dump_sterr(Cmd *c) +{ + int i = 0; + + fprintf(stderr, "CMD: [%s]\n", c->command); + while (c->dict[i]) + { + fprintf(stderr, " %s=%s\n", c->dict[i], c->dict[i + 1]); + i += 2; + } +} + +Eina_Strbuf * +cmd_strbuf_new(const char *command) +{ + Eina_Strbuf *strbuf; + + strbuf = eina_strbuf_new(); + eina_strbuf_append(strbuf, "CMD "); + eina_strbuf_append(strbuf, command); + return strbuf; +} + +void +cmd_strbuf_append(Eina_Strbuf *strbuf, const char *key, const char *val) +{ + const char *s; + + eina_strbuf_append_char(strbuf, ' '); + eina_strbuf_append(strbuf, key); + eina_strbuf_append_char(strbuf, '='); + for (s = val; *s; s++) + { + if ((*s <= '*') || + ((*s >= ';') && (*s <= '?')) || + ((*s >= '[') && (*s <= ']')) || + (*s == '`') || + (*s >= '{')) + { + unsigned char tmp; + + tmp = s[0]; + eina_strbuf_append_printf(strbuf, "\\x%02x", tmp); + } + else + eina_strbuf_append_char(strbuf, *s); + } +} + +void +cmd_strbuf_print_consume(Eina_Strbuf *strbuf) +{ + eina_strbuf_append_char(strbuf, '\n'); + write(1, + eina_strbuf_string_get(strbuf), + eina_strbuf_length_get(strbuf)); + eina_strbuf_free(strbuf); +} + +void +cmd_strbuf_exe_consume(Eina_Strbuf *strbuf, Ecore_Exe *exe) +{ + eina_strbuf_append_char(strbuf, '\n'); + ecore_exe_send(exe, + eina_strbuf_string_get(strbuf), + eina_strbuf_length_get(strbuf)); + eina_strbuf_free(strbuf); +} + +const char * +cmd_key_find(const Cmd *c, const char *key) +{ + int i; + + for (i = 0; c->dict[i]; i += 2) + { + if (!strcmp(key, c->dict[i])) return c->dict[i + 1]; + } + return NULL; +} diff --git a/src/shared/common/cmd.h b/src/shared/common/cmd.h new file mode 100644 index 0000000..48041c8 --- /dev/null +++ b/src/shared/common/cmd.h @@ -0,0 +1,37 @@ +#ifndef CMD_H +# define CMD_H 1 + +# include +# include +# include "efm.h" + +// commands: +// file-add // a dile in a dir being monitored has been added +// file-del // a file in a dir being monitored has been deleted +// file-mod // a file in a dir being monitored has been modified +// dir-del // the dir being monitored/listed has been deleted +// +// future: +// dir-usage // how much data, number of files, dirs etc. + +typedef struct _Cmd +{ + size_t buf_size; + char *buf; + const char *command; + Efm_Sort_Mode sort_mode; + const char *dict[]; +} Cmd; + +Cmd *cmd_parse(const char *cmd); +void cmd_free(Cmd *c); +Cmd *cmd_dup(const Cmd *c); +void cmd_dump_sterr(Cmd *c); + +Eina_Strbuf *cmd_strbuf_new(const char *command); +void cmd_strbuf_append(Eina_Strbuf *strbuf, const char *key, const char *val); +void cmd_strbuf_print_consume(Eina_Strbuf *strbuf); +void cmd_strbuf_exe_consume(Eina_Strbuf *strbuf, Ecore_Exe *exe); +const char *cmd_key_find(const Cmd *c, const char *key); + +#endif diff --git a/src/shared/common/sha.c b/src/shared/common/sha.c new file mode 100644 index 0000000..248e8fd --- /dev/null +++ b/src/shared/common/sha.c @@ -0,0 +1,50 @@ +#include "sha.h" +#include +#include +#include +#include + +void +sha1_stat(const struct stat *st, unsigned char dst[20]) +{ + char buf[128]; + +#ifdef STAT_NSEC +# if (defined __USE_MISC && defined st_mtime) +# define STAT_NSEC_MTIME(st) (unsigned long long)((st)->st_mtim.tv_nsec) +# define STAT_NSEC_CTIME(st) (unsigned long long)((st)->st_ctim.tv_nsec) +# else +# define STAT_NSEC_MTIME(st) (unsigned long long)((st)->st_mtimensec) +# define STAT_NSEC_CTIME(st) (unsigned long long)((st)->st_ctimensec) +# endif +#else +# define STAT_NSEC_MTIME(st) (unsigned long long)(0) +# define STAT_NSEC_CTIME(st) (unsigned long long)(0) +#endif + + snprintf(buf, sizeof(buf), + "%llu %llu %llu %llu %llu %llu %llu %llu", + (unsigned long long)(st->st_mode), + (unsigned long long)(st->st_uid), + (unsigned long long)(st->st_gid), + (unsigned long long)(st->st_size), + (unsigned long long)(st->st_mtime), + (unsigned long long)(st->st_ctime), + STAT_NSEC_MTIME(st), + STAT_NSEC_CTIME(st)); + eina_sha1((unsigned char *)buf, strlen(buf), dst); +} + +void +sha1_str(unsigned char sha[20], char shastr[41]) +{ + const char *chmap = "0123456789abcdef"; + int i; + + for (i = 0; i < 20; i++) + { + shastr[(i * 2) ] = chmap[(sha[i] >> 4) & 0xf]; + shastr[(i * 2) + 1] = chmap[ sha[i] & 0xf]; + } + shastr[i * 2] = 0; +} diff --git a/src/shared/common/sha.h b/src/shared/common/sha.h new file mode 100644 index 0000000..f343131 --- /dev/null +++ b/src/shared/common/sha.h @@ -0,0 +1,11 @@ +#ifndef SHA_H +# define SHA_H 1 + +// get nsec in stat +#define STAT_NSEC 1 +#include + +void sha1_stat(const struct stat *st, unsigned char dst[20]); +void sha1_str(unsigned char sha[20], char shastr[41]); + +#endif diff --git a/src/shared/common/thumb_check.h b/src/shared/common/thumb_check.h new file mode 100644 index 0000000..45afe10 --- /dev/null +++ b/src/shared/common/thumb_check.h @@ -0,0 +1,73 @@ +// XXX: should make this config + +static inline Eina_Bool +check_thumb_image(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("image/*", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/x-docbook+xml", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_font(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("font/*", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_paged(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("application/*pdf", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/acrobat", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/postscript", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/vnd.ms-powerpoint", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/msword", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/vnd.ms-word", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/vnd.openxmlformats-officedocument.wordprocessingml.document", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("application/vnd.oasis.opendocument.text*", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_music(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("audio/mpeg", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("audio/ogg", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("audio/aac", mime, EINA_FNMATCH_CASEFOLD) || + eina_fnmatch("audio/flac", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_video(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("video/*", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_edje(const char *path EINA_UNUSED, const char *mime) +{ + if (eina_fnmatch("application/x-edje", mime, EINA_FNMATCH_CASEFOLD)) + return EINA_TRUE; + return EINA_FALSE; +} + +static inline Eina_Bool +check_thumb_any(const char *path, const char *mime) +{ + if (check_thumb_image(path, mime) || + check_thumb_font (path, mime) || + check_thumb_paged(path, mime) || + check_thumb_music(path, mime) || + check_thumb_video(path, mime) || + check_thumb_edje (path, mime)) + return EINA_TRUE; + return EINA_FALSE; +}