gMTP/000075501651440000012000000000001205070656000123315ustar00darranstaff00003030200010gMTP/ChangeLog000064401651440000012000000237011205070646500141120ustar00darranstaff00003030200010-- v1.3.4 - 14 Nov 2012 + Add new preference to ignore album handling errors (Android enhancement). Note: updated gconf/gsettings schema. + Add new preference to use alternate access method for devices (uncached mode and as needed file data). (Android enhancement). This may slow done gMTP as it no longer caches file information and will get file information each time the folder is changed. + Added note for new Slackware version on gMTP home page. * Fix date handling for some devices, ensuring date format is as per MTP v1.0 Specification Section 3.2.5. * Modify GTK3 version to remove some depreciated GTK widgets. (Still have to consider boxes vs grid). * WARNING: gMTP now requires libmtp 1.1.5 or newer due to the alternate access method. -- v1.3.3 - 15 Jun 2012 * Correct handling of delete and download operation when '..' folder is selected. -- v1.3.2 - 14 Jun 2012 * Correct Makefile to fix when building with GTK+-3.4. (Missing gthread-2.0 from pkg-config). * Reworked pkg-config handling to respect $(PKG_CONFIG) in Makefile. (Thank you Mike Frysinger for pointing this out, and supplying a patch). * Add additional error checking on connection of new devices, to hopefully avoid seg-fault with some very problematic devices. (Ubuntu Bug #917314, #948621, #968798, #995503) * Fix issue with some Android 3.2 implementations and GetStorage() MTP API function. (Uses cached information, rather than full item rescan in some instances). (Debian bug #667795 - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=667795 ) * Correct German Translations. (Something went wrong in saving the file, and was changed from being recognised as UTF-8 to being recognised as ASCII - My apologies). (Debian bug #672298 - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=672298) * Fix FLAC file handling. (Bug only showed up in Gentoo Linux for some reason)? (Thank you Martin Benirschka for helping troubleshoot this one). -- v1.3.1 - 20 Feb 2012 * Change default to ask for Download folder upon initial installation. * Fix folder view on devices with multiple storage pools (Android enhancement). * Change album metadata update error only once during file upload session. (Android enhancement). -- v1.3.0 - 25 Dec 2011 + Added folder name to title bar as per Gnome 2.0 HIG Section 2.1. + Added search toolbar for searching for files/tracks/folders on the device. + Added add to playlist and remove from playlist options in context menu. + Added column selection context menu. + Added folder view to main window pane. (updated gconf/gsetting schema). + Extended drag'n'drop support to include folder view. ie, drag files/folders from Nautilus onto a folder in the folder view, and upload to that particular folder. + Added support for Solaris 11. + Updated German translation - thanks to Laurenz Kamp. + Added a few keyboard shortcuts. + Added Select All function. * Fix javascript issue on website (duplicate function name with an imported JS library). * Fix mp3 track length calculation on "MPEG2 - Layer 3" files. (All others were correct). * Fix 'doesn't prompt for download path on doubleclick when prompt for download path option enabled'. * Fix non-display of default image art if album has no album art attached, in cases when album representative data is found, but is not valid image data. * Fix WM_CLASS for Gnome 3 environments. * Fix Properties dialog with GTK 3.2+. * Fix a few very minor compiler warnings. * Using GTK About Dialog on supported platforms. * SYSV Solaris package is now for Solaris 11. -- v1.2.0 - 4 Aug 2011 + Added ability to download entire folder tree in one operation. + Added import and export of playlists in m3u format. (updated gconf/gsetting schema). * Disallowed upload of *.PLA or *.ALB files as these map directly to Playlists and Album metadata pointers on the device. (Allowing this may result in corrupt metadata on the device). * New website design... (hope you like it). -- v1.1.0 - 5 Jul 2011 * Fix Solaris SYSV Package (fix missing image files). * Fix potential double-free memory error in mtp.c. reported here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=632454 * Fixed filename when downloading files from device to PC. (Bug introduced in v1.0.0). + Added automatic add new track to playlist option. (updated gconf/gsetting schema). -- v1.0.1 - 27 Jun 2011 * Fix application icon size with Window Maker. + Added Format Device Option. - General code cleanup. -- v1.0.0 - 18 Jun 2011 * Fix compile issues with GTK3 based systems. * Fix segfault if user closes progress dialog mid-transfer. * Added TEXT file type detection. + Add Cancel button to stop download/upload of files. (Needed for GNOME3 (gnome-shell)). + In Album Art Dialog, show current Album Art File if present. + In Album Art Dialog, allow removal or download of Album Art. + Rework Album Art Dialog to improve workflow. + Added ability to rename files/folders on the device. + Added icons within file view. + Updated Properties Window layout to better suit smaller screens. -- v0.9.1 - 28 Apr 2011 * Fix "File continues to be uploaded, even if cancelling the upload" bug, reported here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=624117 -- v0.9 - 18 Feb 2011 + Add experimental GNOME 3.0 support. Use 'gmake gtk3' to build using GTK3 and GSettings instead of GTK2 and GConf. * Fix some minor memleaks in mtp.c * Add in check for album name only when looking for an album when updating album info. -- v0.8.1 - 25 Jan 2011 * Update Italian Translation - thanks to Francesca Ciceri ! * Modify Makefile for separate GConf schema installation. -- v0.8 -23 Jan 2011 + Licence change from CDDL to BSD. Updated all source files to include licence notice. + Added basic playlist support. + "make install" no longer installation documentation, use "make install-doc" instead. (As requested for inclusion in some Linux distributions). + Multilingual support in UI - currently English, French, German, Spanish, Italian and Danish Translations. + Add in additional columns in main view to show audio file information. Columns shown can be selected using the 'View' menu. + Updated gconf schema file. * Restructured directory setup for source code. * Fixed Makefile build option for some Linux systems that use "--as-needed" flag. * Changed extension of GConf schema file to be inline with GConf documentation. * Fixed possible buffer overflow in WMA metatag code. (This would be result of malformed header object fields). * Ensured all errors to console are displayed to stderr. * Fixed track length metadata for all file audio file types. * Fixed sorting of columns for filename, so that folders are always first/last. -- v0.7 - 25 Nov 2010 + Added FLAC metadata support (required FLAC 1.2+) + Added OggVorbis metadata support (requires vorbis 1.0.1+) + Added WMA metadata support. * Fixed missing header in mtp.c for some Linux distro's (string.h). * Made application and file paths all lower case inline with freedesktop guidelines. * Changed *.desktop file inline with freedesktop guidelines. * Fixed NULL pointer fault with MP3's that have no TRACKNUMBER tag. -- v0.6 - 23 Jun 2010 + Added abilty to Drag'n'Drop in Folders and have them upload entire folder structure. * Fixed compliation issue on Linux based systems. * Updated gconf handling. * Fixed gconf schema installation. (You need to logout and login on Solaris 10 for the schema to be made available to the application after installation). -- v0.5 - 1 Jun 2010 + Added Tooltips to toolbar buttons. * Made default window size now 760px to handle JDS default DPI setting of 96. -- v0.4 - 12 Jan 2010 + Added right click interace on the main file display area. + Added check to see if we are about to overwrite a file on the devices filesystem, and prompt user to overwrite file? + Added support for Albums and Album Art. (Albums are created automatically, while art is uploaded by user). * Fixed if no lines selected, then display info box to user indicating this on file delete, file download and folder delete operation. (Rather than asking the user to confirm operation on selection and then not doing anything). * Cleaned up the find_filetype() function. * Fixed column sorting for file size column. * Fixed reading MP3 ID3 Tags in relation to the artist. (Now scan all 5 fields that the artist can live in, rather than just the one it should be in). -- v0.3 - 8 Jan 2010 + gMTP is now relocatable within a filesytem. At startup, it will look at the command used to invoke it, and if an absolute path was used, it will find it's image data from that otherwise will attempt to find itself in the filesystem using the %PATH variable. Just make sure that you copy "../share/gMTP/*" to be relative to the binary if moved, otherwise no pretty icons. You can override any file paths with --datapath option at the CLI. (This was done to allow the SYSV and IPS packages to be relocatable). See main.c (last 2 functions) to see how this is done. If you know a better way that works everywhere, please let me know. + Added check to see if we are about to overwrite a file on the hosts filesystem, and prompt user to overwrite file? + Added Drag'n'Drop interface (well only drop into gMTP at this time). * Changed Toolbar button for connect device description. * Fixed process when uploading mp3 to a device to include file metadata as well. eg Artist, Album, Track, etc. (requires libid3tag). -- v0.2 - 30 Dec 2009 + Added new menu items to mimic toolbar options, eg file upload, file download, etc. + Added ability to add and remove folders. + Added confirm delete file/folder option when removing files/folder from device. + Added ability to change device name. + Added in icon for application. + Added in SYSV pkg creation in Makefile. + Added support for multiple device detection and storage device detection, and selection of device during connection. + Updated schema for gconf for new options (confirm remove of file/folders). + Updated About Dialog Box. -- v0.1 - 16 Dec 2009 + Initial version for public. Only does basic items such as upload, download and remove files. 0 from pkg-config). * Reworked pkg-config handling to respect $gMTP/Makefile000064401651440000012000000237701200772457300140100ustar00darranstaff00003030200010# gMTP Sync tool PKG_NAME = gmtp PREFIX ?= /usr/local VER = 1.3.4 # Note: If you update above, please update the config.h and pkginfo file as well. PKG = gmtp ARCH = i386 PKGFILE = $(PKG)-$(VER)-$(ARCH).pkg TARFILE = $(PKG_NAME)-$(VER)-$(ARCH).tar UNAME = $(shell uname) # See what OS we are, and set things for Solaris, otherwise use a default # that should work. ifeq ($(UNAME), SunOS) CC = cc CFLAGS += -Xc -v -xc99 #-native -fast SUNVERSION = $(shell uname -r) ifeq ($(SUNVERSION), 5.11) INSTALL = ginstall -c MSGFMT = msgfmt --strict else INSTALL = /usr/ucb/install -c MSGFMT = /usr/bin/msgfmt --strict LDFLAGS += -L/usr/sfw/lib -R/usr/sfw/lib endif else CC = gcc CFLAGS += -std=c99 -Wall INSTALL = install -c MSGFMT = msgfmt endif GCONFTOOL = gconftool-2 TAR = tar CFLAGS += -c -g #-O LDFLAGS += LIBS += PKG_CONFIG ?= pkg-config .SUFFIXES: .c .o .po .mo ifeq ($(MAKECMDGOALS),gtk3) PKGS = gtk+-3.0 gio-2.0 CFLAGS += -DGMTP_USE_GTK3 else PKGS = gtk+-2.0 gconf-2.0 endif PKGS += gthread-2.0 libmtp id3tag flac vorbisfile GTK_CFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` GTK_LDFLAGS = `$(PKG_CONFIG) --libs $(PKGS)` objects = src/main.o src/mtp.o src/interface.o src/callbacks.o src/prefs.o src/dnd.o src/metatag_info.o headers = src/main.h src/mtp.h src/interface.h src/callbacks.h src/prefs.h src/dnd.h src/metatag_info.h src/config.h catalogues = po/es.mo po/it.mo po/fr.mo po/da.mo po/de.mo POFILES = po/es.po po/it.po po/fr.po po/da.po po/de.po all: gmtp $(catalogues) # GTK3 build gtk3: gmtp $(catalogues) # GTK2 build gtk2: gmtp $(catalogues) # Main executable gmtp: $(objects) $(CC) -o gmtp $(LDFLAGS) $(objects) $(GTK_LDFLAGS) $(LIBS) # Object Files src/main.o: src/main.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/main.c src/mtp.o: src/mtp.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/mtp.c src/interface.o: src/interface.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/interface.c src/callbacks.o: src/callbacks.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/callbacks.c src/prefs.o: src/prefs.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/prefs.c src/dnd.o: src/dnd.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/dnd.c src/metatag_info.o: src/metatag_info.c $(headers) $(CC) $(GTK_CFLAGS) $(CFLAGS) -o $@ src/metatag_info.c # Language Files po/es.mo: po/es.po $(MSGFMT) -o po/es.mo po/es.po po/it.mo: po/it.po $(MSGFMT) -o po/it.mo po/it.po po/de.mo: po/de.po $(MSGFMT) -o po/de.mo po/de.po po/da.mo: po/da.po $(MSGFMT) -o po/da.mo po/da.po po/fr.mo: po/fr.po $(MSGFMT) -o po/fr.mo po/fr.po # Installation install: gmtp $(catalogues) $(INSTALL) -d $(DESTDIR)$(PREFIX) $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin $(INSTALL) -d $(DESTDIR)$(PREFIX)/share $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/applications $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/pixmaps $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/gconf $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/gconf/schemas $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/es $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/it $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/fr $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/da $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/de $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/es/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/it/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/fr/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/da/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/de/LC_MESSAGES $(INSTALL) -m 755 gmtp $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 644 images/icon.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/logo.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/icon-16.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/stock-about-16.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/audio-x-mp3-playlist.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/audio-x-mpeg.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/folder.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/image-x-generic.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/media-cdrom-audio.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/text-plain.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/video-x-generic.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/empty.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/view-refresh.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 misc/gMTP.desktop $(DESTDIR)$(PREFIX)/share/applications $(INSTALL) -m 644 images/icon.png $(DESTDIR)$(PREFIX)/share/pixmaps $(INSTALL) -m 644 misc/gMTP.schemas $(DESTDIR)$(PREFIX)/share/gconf/schemas mv $(DESTDIR)$(PREFIX)/share/pixmaps/icon.png $(DESTDIR)$(PREFIX)/share/pixmaps/gMTPicon.png cp po/es.mo $(DESTDIR)$(PREFIX)/share/locale/es/LC_MESSAGES/gmtp.mo cp po/fr.mo $(DESTDIR)$(PREFIX)/share/locale/fr/LC_MESSAGES/gmtp.mo cp po/it.mo $(DESTDIR)$(PREFIX)/share/locale/it/LC_MESSAGES/gmtp.mo cp po/da.mo $(DESTDIR)$(PREFIX)/share/locale/da/LC_MESSAGES/gmtp.mo cp po/de.mo $(DESTDIR)$(PREFIX)/share/locale/de/LC_MESSAGES/gmtp.mo mv $(DESTDIR)$(PREFIX)/share/gconf/schemas/gMTP.schemas $(DESTDIR)$(PREFIX)/share/gconf/schemas/gmtp.schemas register-gconf-schemas: install GCONF_CONFIG_SOURCE=`$(GCONFTOOL) --get-default-source` $(GCONFTOOL) --makefile-install-rule $(DESTDIR)$(PREFIX)/share/gconf/schemas/gmtp.schemas install-gtk3: gmtp $(catalogues) $(INSTALL) -d $(DESTDIR)$(PREFIX) $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin $(INSTALL) -d $(DESTDIR)$(PREFIX)/share $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/applications $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/pixmaps $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/glib-2.0 $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/glib-2.0/schemas $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/es $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/it $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/fr $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/da $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/de $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/es/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/it/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/fr/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/da/LC_MESSAGES $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/locale/de/LC_MESSAGES $(INSTALL) -m 755 gmtp $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 644 images/icon.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/icon-16.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/logo.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/stock-about-16.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 misc/gMTP.desktop $(DESTDIR)$(PREFIX)/share/applications $(INSTALL) -m 644 images/icon.png $(DESTDIR)$(PREFIX)/share/pixmaps $(INSTALL) -m 644 images/audio-x-mp3-playlist.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/audio-x-mpeg.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/folder.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/image-x-generic.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/media-cdrom-audio.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/text-plain.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/video-x-generic.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/empty.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) $(INSTALL) -m 644 images/view-refresh.png $(DESTDIR)$(PREFIX)/share/$(PKG_NAME) mv $(DESTDIR)$(PREFIX)/share/pixmaps/icon.png $(DESTDIR)$(PREFIX)/share/pixmaps/gMTPicon.png cp po/es.mo $(DESTDIR)$(PREFIX)/share/locale/es/LC_MESSAGES/gmtp.mo cp po/fr.mo $(DESTDIR)$(PREFIX)/share/locale/fr/LC_MESSAGES/gmtp.mo cp po/it.mo $(DESTDIR)$(PREFIX)/share/locale/it/LC_MESSAGES/gmtp.mo cp po/da.mo $(DESTDIR)$(PREFIX)/share/locale/da/LC_MESSAGES/gmtp.mo cp po/de.mo $(DESTDIR)$(PREFIX)/share/locale/de/LC_MESSAGES/gmtp.mo register-gsettings-schemas: install-gtk3 $(INSTALL) -m 644 misc/org.gnome.gmtp.gschema.xml $(DESTDIR)$(PREFIX)/share/glib-2.0/schemas glib-compile-schemas $(DESTDIR)$(PREFIX)/share/glib-2.0/schemas install-doc: $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/doc $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME) $(INSTALL) -m 644 README $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME) $(INSTALL) -m 644 COPYING $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME) $(INSTALL) -m 644 ChangeLog $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME) $(INSTALL) -m 644 AUTHORS $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME) uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/gmtp rm -f $(DESTDIR)$(PREFIX)/share/$(PKG_NAME)/* rm -f $(DESTDIR)$(PREFIX)/share/applications/gMTP.desktop rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/gMTPicon.png rm -f $(DESTDIR)$(PREFIX)/share/gconf/schemas/gmtp.schemas rm -f $(DESTDIR)$(PREFIX)/share/glib-2.0/schemas/org.gnome.gMTP.gschema.xml rm -f $(DESTDIR)$(PREFIX)/share/locale/es/LC_MESSAGES/gmtp.mo rm -f $(DESTDIR)$(PREFIX)/share/locale/fr/LC_MESSAGES/gmtp.mo rm -f $(DESTDIR)$(PREFIX)/share/locale/it/LC_MESSAGES/gmtp.mo rm -f $(DESTDIR)$(PREFIX)/share/locale/da/LC_MESSAGES/gmtp.mo rm -f $(DESTDIR)$(PREFIX)/share/locale/de/LC_MESSAGES/gmtp.mo uninstall-doc: rm $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME)/README rm $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME)/COPYING rm $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME)/ChangeLog rm $(DESTDIR)$(PREFIX)/share/doc/$(PKG_NAME)/AUTHORS clean: rm -f $(objects) core gmtp src/*.o src/*~ $(PKGFILE).gz po/*.mo po/*~ dist: rm -f $(objects) core gmtp src/*.o src/*~ po/*.mo po/*~ cd .. && $(TAR) -cf $(TARFILE) gMTP && gzip $(TARFILE) && cd gMTP pkg: gmtp $(catalogues) pkgmk -o -d /tmp -a $(ARCH) touch $(PKGFILE) pkgtrans -s /tmp $(PKGFILE) $(PKG) rm -r /tmp/$(PKG) gzip $(PKGFILE) # GTK2gMTP/INSTALL000064401651440000012000000022301176537741700134000ustar00darranstaff00003030200010gMTP - a basic MP3 player client. Requirements: gtk+2.0 v2.4+ OR gtk+3.0 (for GNOME 3.0). Gconf-2.0 OR GSettings (for GNOME 3.0). libmtp libid3tag libusb libflac (FLAC) v1.2+ libvorbis v1.0.1+ Installation. For JDS / GNOME 2 platforms (GTK2 + GConf): $ make # make install # make register-gconf-schemas Note: Use 'gmake' on Solaris and *BSD. Note: On Solaris 10, you need to logout and back in again once installed. (This is a gconf issue on Solaris 10). For GNOME 3.0 based platforms (GTK3 + GSettings): $ make gtk3 # make install-gtk3 # make register-gsettings-schemas If it doesn't compile, check the top of the Makefile for custom CFLAGS. Note: Solaris 10 does not ship with a PKGCONFIG file for libusb. One can be found in this package in the 'misc' directory. Note: libid3tag does not provide a PKGCONFIG file. One can be found in this package in the 'misc' directory. Known to compile with: gcc v3.4.3 gcc v4.2.1 gcc v4.5.0 gcc v4.5.2 gcc v4.6.2 Sun Studio 12u1 Oracle Studio 12.2 Oracle Studio 12.3 clang 3.1 Known to work on: Solaris 10 u8 and Solaris 10 u9 Solaris 11 11/11 OpenSolaris 2009.06 Arch Linux Ubuntu 10.04 Debian Sid Gentoo Linux FreeBSD 8.1 gMTP/postinstall000064401651440000012000000001421151556143300146300ustar00darranstaff00003030200010#!/bin/bash /usr/bin/gconftool-2 --install-schema-file=/usr/local/share/gconf/schemas/gMTP.schemasgMTP/web/000075501651440000012000000000001205070641300131035ustar00darranstaff00003030200010gMTP/web/footer.jpg000064401651440000012000000066271161625545400151310ustar00darranstaff00003030200010JFIFHHExifMM*C  !"$"$C9"0!a"1AQ#$q2B%43b ?:{qv*^~ c*[prEoX*.e7ssX-4)iI⩏ko[1JɹRd=6ZQnFN4O"s{դ)F@^ZPQK"_~9%PA`U^en8 Ơ5M_ae^G~9{Zmi^8L*w.WKAijy?G{T 2q5{UʺZǨ^{%YU<~#Z*"9?v٥VJp0Ǥ-x4iіUhGt7ZM% tӷ 7箑5`.t*laV9zz_FoA]U$ tgvY;(xYU8ʶ[mosJZO*A_b~nݷƌ8/ƟwW*۲mosGQQtOtKĭ)9mn42.Ft5dTuBQ ;rt'=>ʓT)BrÓʿ2'*BZx-%Oמep+py]+JZGT'zz7*s!IoS,#ueo+K%t uNmOPIs'ױ7*Ӷx*-_unyxܗ(\֊U?y:_GBފ/^!^sѩ/W|<n8:jq>M4rz ">!^VZ𺨽%L򨞞yn烨/N]> jiJR~/ozqfR*Ub̶B8ۖW)*|z>u %IIVVܵ-+ Λź®eBܧz_/eOƧS+{n黛X_~/-[6[uʀ羑J<#)Te'-ޞw_өSZYZ'RePQc*2`G>]i%Ioܶͬ蟅t1'ZY%n7~_*Pm;juArOozdT*X%LћzgV(y]'[*Ptj uJqkIIs'o7<4Ko:Us cwk /^!}.<[nBԷho}^8et]56+ξҿǥѨ*&5B)j껻yxʉ̀. T9ujq|ǣiI9h+4 RT7q{|ӎNd2qE^H53-7M[кFj4Kĭj[xWʻ x[tޖoVNeBq/ӛ>&~+N[MvKkc*O<,^[̶lzPO15~%oU6-}7skz{_U[Lc޷74>&zĜͬ茠rEZZUujf[g,כRt7N: M:mi+u99=eő1iOKR:NGY8qdkvhtjZIkOMVső2#/[ക LT$pF UuRiۅPwQ8O"7M[ɦU{ίp<>SnN+SZYAі}{Q*=˥PSNERp쳆U}.RIimݿ2\RT*qc]J޷T1J)7)nek~ 9isTj\n-B;[p[%Kp[d{%FA"}OInOVʋڄFv۟R>8Y4< @J=c@ZY"S`iC"%N sj1*ޫw%U`JUVC/v3wfQ7rrs򓕁7sRrw~nXfc+*stsw%Vs tsw%Vs^fs̾BN~W2:9vVNWC7r22:x s?re~aQ@eN~Q^)2est2se~nf0e(n7sn7vs2:q|+~n:Xu{2nu]^Tnuyu{VCٛ"UGFNlu{^}L2Uxu 8<|\ dyr+l EEY)sɹbxw...."n]n{*`]쨋r'{)o$( .. U83]!;Mѓb ;b 9Vf`j2nrrnrrnrv.ӑ2R2|2p4!(" o%7&FWr.\݋cȿs~监rpyMgMTP/web/logo.xcf000064401651440000012000001325161161641352400145620ustar00darranstaff00003030200010gimp xcf file@BBQgMTP#58BABAB`AB`AB`ABA@BA@BAUBbAUBABrABҫABAB UABAB0ABJA@BAUBhABAUBUAЀB(ABbABABA`BBBBBBBBMUBUBB BB BrB B0BZBBBPBBBUBBBBBBBBBBEUBUBZBB BRB!BB#:BHB$PBB%zBUB&`BB'BRB'B«B(BB(BbB(BUB'UBB&BB%BUB%BHB$BHB$BB$BB$BB$BB5BB5BB5UBUB6UBB7 BHB7UBB8BB8BB8BB7jBB6@B-UB5 B*B3JBB0BpB.5UBUB*UBB&@BB!ʫBBJBBBBBABhBBxBBBBBB]UBBBUBB BBBBUBA@BA@BUA BAB:AӠB(ABABbAB AUUBABAѪBpAҕUBABABB BB B:B B]UB BABAB AB AB ABPAހBPAހBPAހBxABxABxABABABABB BB BB BB BB BB BA@BA@BA@BBBBBBBBBBBBBA@BA@BA@BB BB BB BB BB BB BACACABABABABABABACACACACB CB CB C B C B C B C AC AC ACACACACAC)DAC(ƫAC(UAC'$AC&^AUUC%zA5UC$xA C#xA C"<AC AC ACACACACB CB CB CB CB CB CBCBCBC BC BC"YUBC#AUC$A@C%AjC'UAC'A@C(AC)UAUC)tAC)UA̵UC*A`C*AC*AUC)UAUC#A`C#UAРC#JAJC"A`C"uUAʫC!AUC!<A@C AjCUACACACACACACACACACACACUACA C hA`C!AC!AC"<A C"AC#UAC#xA@C#ҫAUC$AʫC$AC$AUC#UA gMTP#4,BABABhABhABhABA BA BMUA BUAUBABA*BpABABeUABʫA B@A`BABA`BABAUBUAuUBA`BAJBA@BBBB@BUBUBB$BhB,0B0B0B`B0BB0B@B/*BPB-BeUB+UBJB)BB&BB&BB(0BB(0BB+BB.UBpB0BB3ZBuUB5UUBB6BB8BpB9BB:BB;BB<B0B<B%UB<BB;UBHB:BB9BUB9BB8BB8BB8BB8BB8BBLBBLB5UBMuUBҫBNEUBBNBUBOUBBPBBPBBPBMUBOJBXBMBhBLuUBUBJ%UB0BFBUUBCUBB?eUB0B9BeUB4:BB-UUBB% BB% BAB BBUBUBBBBBBUBBUB%UBxBpBҫBBB 0BBBAUBUA犫BA BAԵUBABABABUAuUBA`B"A5UB*A`BABABBBBBUB@BBBApBApBApBApBApBhA`BhA`BhA`BApBApBApBApBApBApBB0BB0BB0BB0BB0BB0BABABAB BB BB BBBBBBBBABABABB0BB0BB0BB0BB0BB0BApCApCApCApCApCApCACACAC AC AC AC B0C B0C B0CB0CB0CB0CACACACACACACApC8AC7AC6AC5AC4A`C3A|C2PAwC1ArUC/UApC-ApC-ApC"ApC"ApC"ApC"B0C"B0C"B0C*B0C*B0C*B0C*B C*B C*B C.@B C.@B C0-UB C1UB C3BpC4aUBUC5BzC6AC7^AUC7UA5UC8`A C8ʫAUC9A͊C9AC9A5UC8AC0A׀C0UAUC0JAjC/A@C/uUAUC.AuUC.<AC-AJC,UAC+AC+AC*AC*AC*AC*AC*AC*AC+AC+AC,UAC,A*C-hAC.AC.AUUC/<A@C/AUUC0UA C0xAC0ҫA C1AC1AC1A˪C0UA*gMTP#3+BABABhABhABhABA BA BMUA BUAUBABA*BpABABeUABʫA B@A`BABA`BABAUBUAuUBA`BAJBA@BBBB@BUB UBB(BhB00B0B4B`B4BB4B@B3*BPB1BeUB/UBJB-BB*BB*BB,0BB,0BB/BB2UBpB4BB7ZBuUB9UUBB:BB<BpB=BB>BB?BB@B0B@B%UB@BB?UBHB>BB=BUB=BB<BB<BB<BB<BB<BBPBBPB5UBQuUBҫBREUBBRBUBSUBBTBBTBBTBMUBSJBXBQBhBPuUBUBN%UB0BJBUUBGUBBCeUB0B=BeUB8:BB1UUBB) BB) BAB BBUBUBBB BB BUB BUB%UBxBpBҫBBB0BBBAUBUABA BAܵUBABABABUAuUBA`B"A5UB*A`BABABBBBBUB@BBCACABABABABhA`BhA`BhA`BABABABABABABB4BB4BB4BB4BB4BB4BABABAB BB BB BBBBBBBBABABABB4BB4BB4CB4CB4CB4CAC"AC"ACACACACACACACACACACB4CB4CB4CB4CB4CB4CACACAC"AC"AC"AC"AC<AC;AC:AC9AC8A`C7AuUC6PAC5AJC3UAC1AC1AC&AC&AC&AC&B4C&B4C&B4C.B4C.B4C.B4C.BC.BC.BC2@BC2@BC4-UBC5UBC7B pC8aUB UC9BzC:BC;^AUC;UA5UC<`A C<ʫAUC=AՊC=AC=A5UC<AC4A߀C4UAUC4JAjC3A@C3uUAUC2AuUC2<AC1AJC0UAC/AC/AC.AC.AC.AC.AC.AC.AC/AC/AC0UAC0A*C1hAC2AC2AUUC3<A@C3AUUC4UA C4xAC4ҫA C5AĀC5AC5AӪC4UA*gMTP#2(BABABhABhABhABA BA BMUA B…UAUBAӀBA*BpABABeUABʫA B@A`BAڊBA`BABAUBUAuUBB0BBUBB BBBB%@BUB0UBB8BhB@0B0BDB`BDBBDB@BC*BPBABeUB?UBJB=BB:BB:BB<0BB<0BB?BBBUBpBDBBGZBuUBIUUBàBJBºBLBpBMBBNBBOBBPB0BPB%UBPBBOUBHBNBBMBUBMBBLBBLBBLBBLBBLBB`BB`B5UBauUBҫBbEUBBbBUBcUBBdBBdBBdBMUBcJBXBaBhB`uUBUB^%UB0BZBUUBWUBBSeUB0BMBeUBH:BBAUUBB9 BB9 BAB B.BUB/UBBB0BB0BUB0BUB.%UBxB*pBҫB&BB 0BBBBZBUBUBBBAUBABABABUAuUBA`B"A5UB*A`BABABB*BB*BUB,@BÊB-CACACACACABhB BhB BhB BABABABABABABBDBBDBBDBBDBBDBBDBABABAB B(B B(B B(CB(CB(CB(CACACACBDCBDCBDCBDCBDCBDCAC.AC.ACACACACACACACACACACBDCBDCBDC%BDC%BDC%BDC%AC%AC%AC.AC.AC.AC.ACHACGACFACEACDA`CCAuUCBPACAAJC?UAC=AC=AC2AC2AC2AC2BDC2BDC2BDC:BDC:BDC:BDC:B C:B C:B C>@B C>@B C@-UB CAUBCCBpCDaUBUCEBzCFBCG^BZCGUB CH`BCHʫAUCIACIACIA5UCHAрC@AC@UBjC@JBUC?BC?uUBʫC>B :C><B C=B UCAC>AUUC?<A@C?AUUC@UA C@xAؠC@ҫA CAACAACAAC@UA*gTMP BABABhABhABhABA BA BMUA BUAUBABA*BpABABeUABʫA B@A`BABA`BABAUBUAuUBA`BAJBA@BA@BB @BUBUBBBhB$0B0B(B`B(BB(B@B'*BPB%BeUB#UBJB!BBBBBB 0BB 0BB#BB&UBpB(BB+ZBuUB-UUBB.BB0BpB1BB2BB3BB4B0B4B%UB4BB3UBHB2BB1BUB1BB0BB0BB0BB0BB0BBDBBDB5UBEuUBҫBFEUBBFBUBGUBBHBBHBBHBMUBGJBXBEBhBDuUBUBB%UB0B>BUUB;UBB7eUB0B1BeUB,:BB%UUBB BB BAB BBUBUBBBBBBUBBUB%UBxBpBҫB BB0BABAUBUA׊BA BAĵUBABABABUAuUBA`B"A5UB*A`BABABBBBBUB@BBBAPBAPBAPBAPBAPBABABABABABABB(BB(BB(BB(BB(BB(BABABABABABABAPCAPCAPCAPCAPCAPC A`C A`C A`CAPCAPCAPBAPBAPBAPBB(BB(BB(CB(CB(CB(CACACACB CB CB C B C B C B CACACACB(CB(CB(CB(CB(CB(CAPC7AC6AC5A{C4Ao@C3AdC2A\C1PAWC0ARUC.UAPC,APC,APC!APC!APC!APC!B(C!B(C!B(C)B(C)B(C)B(C)BC)BC)BC-@BC-@BC/-UBC0UBC2BpC3aUA C4AUC5AC6^AUC6UA5UC7`A C7ʫAUC8AC8AC8A5UC7AC/AǀC/UAUC/JAjC.A@C.uUAٕUC-AuUC-<AC,AJC+UAC*AC*AC)AC)AC)AC)AC)AC)AC*AC*AC+UAC+A*C,hAC-AC-AUUC.<A@C.AUUC/UA C/xAC/ҫA C0AC0AC0AC/UA*gMTP#1BABABhABhABhABA BA BMUA BUAUBABA*BpABABeUABʫA B@A`BABA`BABAUBUAuUBA`BAJBA@BA@BB @BUBUBB BhB(0B0B,B`B,BB,B@B+*BPB)BeUB'UBJB%BB"BB"BB$0BB$0BB'BB*UBpB,BB/ZBuUB1UUBB2BB4BpB5BB6BB7BB8B0B8B%UB8BB7UBHB6BB5BUB5BB4BB4BB4BB4BB4BBHBBHB5UBIuUBҫBJEUBBJBUBKUBBLBBLBBLBMUBKJBXBIBhBHuUBUBF%UB0BBBUUB?UBB;eUB0B5BeUB0:BB)UUBB! BB! BAB BBUBUBBBBBBUBBUB%UBxBpBҫBBB0BABAUBUAߊBA BA̵UBABABABUAuUBA`B"A5UB*A`BABABBBBBUB@BBBA`BA`BA`BA`BA`BhA`BhA`BhA`BA`BA`BA`BA`BA`BA`BB,BB,BB,BB,BB,BB,BABABAB BB BB BBBBBBBBABABABB,BB,BB,BB,BB,BB,BA`CA`CA`CA`CA`CA`CACACAC AC AC AC B,C B,C B,CB,CB,CB,CACACACACACACA`C8AC7AC6AC5A@C4AtC3AlC2PAgC1AbUC/UA`C-A`C-A`C"A`C"A`C"A`C"B,C"B,C"B,C*B,C*B,C*B,C*BC*BC*BC.@BC.@BC0-UBC1UBC3BpC4aUBUC5AUC6AC7^AUC7UA5UC8`A C8ʫAUC9AŊC9AC9A5UC8AC0AπC0UAUC0JAjC/A@C/uUAUC.AuUC.<AC-AJC,UAC+AC+AC*AC*AC*AC*AC*AC*AC+AC+AC,UAC,A*C-hAC.AC.AUUC/<A@C/AUUC0UA C0xAC0ҫA C1AC1AC1AêC0UA*gMTPBABABhABhABhABA BA BMUA BUAUBABA*BpABABeUABʫA B@A`BABA`BABAUBUAuUBA`BAJBA@BA@BB @BUBUBB BhB(0B0B,B`B,BB,B@B+*BPB)BeUB'UBJB%BB"BB"BB$0BB$0BB'BB*UBpB,BB/ZBuUB1UUBB2BB4BpB5BB6BB7BB8B0B8B%UB8BB7UBHB6BB5BUB5BB4BB4BB4BB4BB4BBHBBHB5UBIuUBҫBJEUBBJBUBKUBBLBBLBBLBMUBKJBXBIBhBHuUBUBF%UB0BBBUUB?UBB;eUB0B5BeUB0:BB)UUBB! BB! BAB BBUBUBBBBBBUBBUB%UBxBpBҫBBB0BABAUBUAߊBA BA̵UBABABABUAuUBA`B"A5UB*A`BABABBBBBUB@BBBA`BA`BA`BA`BA`BhA`BhA`BhA`BA`BA`BA`BA`BA`BA`BB,BB,BB,BB,BB,BB,BABABAB BB BB BBBBBBBBABABABB,BB,BB,BB,BB,BB,BA`CA`CA`CA`CA`CA`CACACAC AC AC AC B,C B,C B,CB,CB,CB,CACACACACACACA`C8AC7AC6AC5A@C4AtC3AlC2PAgC1AbUC/UA`C-A`C-A`C"A`C"A`C"A`C"B,C"B,C"B,C*B,C*B,C*B,C*BC*BC*BC.@BC.@BC0-UBC1UBC3BpC4aUBUC5AUC6AC7^AUC7UA5UC8`A C8ʫAUC9AŊC9AC9A5UC8AC0AπC0UAUC0JAjC/A@C/uUAUC.AuUC.<AC-AJC,UAC+AC+AC*AC*AC*AC*AC*AC*AC+AC+AC,UAC,A*C-hAC.AC.AUUC/<A@C/AUUC0UA C0xAC0ҫA C1AC1AC1AêC0UA*$gimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) SSla(gMTP     J7 gimp-text-layer(text "gMTP") (font "Verdana Bold") (font-size 32.000000) (font-size-unit pixels) (hinting yes) (antialias yes) (language "c") (base-direction ltr) (color (color-rgba 0.000000 0.000000 0.000000 1.000000)) (justify center) (box-mode dynamic) (box-unit pixels) U0a(UHla(U\eYONOPPLYRTTUWXYXYYPQRPdOUUVVWYXYYZYY[[RSSTR\TWXWWXXZ[[Z[[\\]TUVRQXYXYYZZ\]^^_UVVWXWZVZZ[[\ `_WXWXXYYSV\[\]]^ _^NORTSQM`SUVVWWYZ[Z\YXV]]^^_ ]\^RTVVUVWVTWXXY[\[\]]TV]^`^_``_ \[[aSVWVWWXYZ]\]]^^X^^_ZU`__Y^^] YQXXYZYYZZ[[\\^_`_U`_`_VX\^^]S]]\ WYXYZZ[VUZS\\]]`_L\^^]WS]]\ZR[Z[[ZZ VUR[[\YJ^_^_^]^^T]\[[XZZSYYXYW TY\]^]^R`_]\[VZZYXXXWWV SR\^__`W^][ZYVYXXWTVVUU QPP``__F]\\[YXOVWVVOTTS O^^][ZXWXWVVVUUTTSZRRQ M\\[SYZYYXVUTUPSRROQPO LKY[Z[YZSWVTSVQQUONONN JIUYYXYXSVUTSRRQQ NMLLMK IHHGPXWWVVUPUTQRUSSQPO KLKKJK FZTVUUTTSRQRQONNM IJII EDDNTSSRQPOMLK HGHGG CBMQRQQPPONMKLKJJ FEFEE BAAWMPOOQ[MMLNJIH EDDCDC @ LKJL.MOLT_aSKJJIIH.KJJKIIHIHGGHIm.IHGHHGFGGEFQ/GFEDDG[0GHHGEEDCEFEHoYONOOPOPLYRSTTUVWWXYPQQRQROdOUUVVWVWXYYZ[RSTR]UWWXWXXZ[Z[[\]STTUVRRXXYZ[\\]^VUVVWXWZVZ[[\ _`_WXWXXYXYZRV\[\\]^ _^NORTSPN`TVVWVWYZ[Z\YXV]]^^__ ]]QUUVUWXXYX[\[]\]TV]_`^``_ [Z`SVWVWWXXYYZ\]^Y^^_ZU__Y_^^]] YRXXYZ[\^_T`__VX\^^S]\\[ XWXYYZ[VUZS]]^`_L]^^VS\]\ZS\[[ZZY WVVR[\\YJ^__`_^^]^]U\\ZX[ZZSYYX TSX]\^^R_``_]\[WYZZYWXXWW SRR\^__W_^]^][ZUXYXXWWTVVUUT QPQ``_F]\][[YXYXPWWVVOUTTSSR O__^^]][ZXWWVVTUUTTZRRQ MNML\\SZYXXVUPSRSSOQPP LKYZ[ZZRXWVUTSTSSVQQUON KJIIUYYXRVUTSRRQ MLL IHGGOWWVUPUUQSTTSQPPO LKKJKK GFFEZUVUUTTSRSRQRQON JII EDNTSSRQPONMLLK HG CBCMRQQPQPPONMKJ GFFEFE BAA@WMPOOQ[MMLNIJII DEDD @ KL.MNKS_aSKJIIH/KJIHGHm.IHGHGGFEQ/HGGFEG[0GHHGFEDDEFEHoYOPQKYRSTTUUVWWXXWXYYZPQROdOUUVWXYYZ[\RSRSSTTR]UWWXXYZ[\]STTUUVRRXYYZYZZ[\\]^UVWVWWVZVZZ[\ `WXWXXYSV[\\]^ _^NOQTTQN`TVVWWXYYZZ[ZXV]]^__ ]\^QUUVUWXWWYYZ[\\[\]]TV]^`^``_`_ \[[Z`SVVWXXYZYZZ]^]X^__ZU``_Y_^^] ZYYRXXYZYZZ[[\[\^_U``__VX\^^]S]]\\[ XWWXXYYZ[VUZS\\]^]`_`_L\^^WS]\[YS[[ZZ WVVUR[[\\XJ^_`^]U\\[[X[[ZSZYYXX TSX]]^R`_^]\]\\[WYZZYXXXWWV RSRR\^__W_^^]\[Z[ZZVXXWUVVUT QP``_F]\[\ZYYXPVVUOTTS ON_^^]][Z[ZZWVVTTSZRRQQ NML[]]\[\SZYXXYXVUTUPSSRSOPQPOPP KLKKZ[ZZYRXWWVVTSTTSRVQQPQUONM JIUYYXRVUTRQ NMLL HOWWVWVUPVTQSTTSRQPPO KJ FEYUUTSRQONM IJJIIH EDNTSSRQRQPPONMMLL HGFG DBCBMRRQPPONKJ GFFE AWMQOOQ[NMLLNJIH EDDC @ LKJKL.MNLS_aSKJJIJIIH.KJIJIHHIn.JIHHGFEFQ/HGGFEG[0GHIGFEDDEGEHoYq#  E d  8 Ox  \  w Q/Q $3DL  a  VV   5$$   ^r7     U  e  }3   m.k,,V. . ,/ 0aďFo)YZ[Z[[\]^^_`_][VR[\\]^`_^]ZU]^^_`_^^_^]^]]\\[ZYS__``_`_^]]\\[[\[[ZZYXb`_ \[[ZZeaRUXXWVR]^ ZYZYYTVVUT\[ XWZUTTSTS[Z WVVU[RRQYX UTSfQQPQPPWW SRQPOONNPUT QPQPpPMMLTS POONncLNLLKaRQ MNMMLKJKJJIJPO LKKJKJIIHJNM JIJIHHGFHHML IHHGFGFFGIFmK GF IH ED G DCB EF BAA C @ B @ @ @ @ @)Z[\\[\]^^_`][VS[\\]^`_``_^]ZV]^^_`__^^]\[ZR^__``_^]]\\[Z[ZZYYXXb_` [\[[ZZeaRUXXWR^] ZYZXXTVVUUT\[ XWZUTSTTS[Z VWVUU[SSRRQQYX TfQQPQPOVV SRRQPOONONPUU RQQPpPMMLSS OPOONncLNLLKbRR NMMLKJKKJJIJPP LKJIHJN JIHGFHHML IHHGFGHFmJ GFE IH ED HG CB E A@ D @ B @ @ @ @ @)Z[\\]^_^__``][VS[\\^_`_`_^_^^]]ZV]^]^___^]\[ZS__`_]\]\\[[Z[ZZYWb__ \[[ZeaRVXXWVR^^ ZYYXSVVUUT\\ YWWXWWYTTS[Z VWVVU[SRSRQQYX TUTTSgQQPWW SRQPOONOUU QPOpPNMMLMMTS POONNncMOLLKKaRR NMMLKJIJOP LKLKKJJIJIIHHJN JIJIHHGHL IHHGFFGIFmKJ GFE I EDEDD GF DCCB FE BA C @ B @ @ @ @ @) ΓA2D =  $   @ ~ K y  R l               0@ Background     m@m|@mU 1==<=>>=>=<<:876420-+('$"LZXVUUTTSRPONLJHFDA?;9664G[XUUTTSTSRQPOMLIHFCA>;8544\ZVTPPOPPOONMKJHGFCB?=:74233#%ZWTB% !""#'711#$XUR;ijhgdca`^\ZXVTSPOLJ+-0// "VSR:7xvtrpnljhfca_]ZXVSJ ,/--"TRP:6trpnljhfdb`^\YWTR6 -.++!RPO:5pnmjhgeca_][XVTA .-** PNN:4mkigfdb`][YWUJ$ /,)' ONN:3ihedb`^\ZXVP0 0+(&NMM:2fdb`^\[YWU@0*'LL91a`^\ZXWVR/2*&JJK90^\ZYWVTN#2(& JIJ8/ZXWUTRK 3("HHI8.VUSRPK 4' FGH8,RQPNL$ 4$ EFF8+NMKI. 5 DEF8(JHF< 5  CDD9&FDB 6 BBD:$@?. 7  @BB:"<: 7  @@B: 7*   8  >@@;1   8  =>?;(   8 <<=<  B :;<<:67889:;<<>><;;<?@ABCDEGIE1%" 889:<<=??ABCDFD0(%" 6789:;<=>?A@:0 " 46789::<<95ERONNH4 3556689::3QJ88tG88! H623346688:8/! YYXLA50223446786  .@RYYU; ؒʹ.0013/265    +CPL1 UھȮ-..007;2!  " 5D2ՏI uZvB,,-.*SI* "%''( &< 1<ԎN0t@CA**+,4[H &+.//0- #<& 84:Fv&&(()+AZ= (/57764 <:1BG?^PH:6u|1&Y&84:.I&'(*J_2 %.7>?><;: G]mlZPNUVM?U0*ɟ q GQ J.$&&(P6  )2;CDB@?> #mdB+% !Kxf9**͵T'"[',9>$H#$$&@M) #+4BCDDR%"Bs`G1*' ʡ"S=%*RE, ) ' .3:>@CC0;\L5**)  ʇr&q;PS&P$  (6;8* 6%6,**& ^i4*;&4)*,"-    2 ('  уlM6V G3-   (   f||>D*&01""-1& .06646710@mta5 22 *HW"21 >`zP' Rfh[NKRYP4  "'#  BNOMF6   &   '    '  U 1==<=>>?>=<::97430.,('%"LZXVVUUTUTTRQPMKIGEB@<9665 H\XUUTUTTRQPNNJIGDB><85446\ZVTQQPPQPPONMLJHGDB@=:85234$&ZWTB:DFGHIJKLMNNOPQRRSSTO8112$%XVR;mL0//!$WSR: 򭬬R0-."URP:믰T.++!SPP:򯮭U.** QNO:򱬚W,)'ONN:X,(&NMM:ﵮY*(LL9Z*&KK9[)& JJ9[)"HII8[(FHH8\% EFG8\ DEF9~[  CDE9𗖓~z[  BDD:𒐎}zv[  ABC;|xurZ @AB;튉|zwtqnY >@@<텄|zxuroliY >?@<쁀}|zwurpmjhdX <>>@ACDEGHILNJ6*'#  89:<=>@ABDEHHKI6-*'#  689;<=?@BCDD>3"#&#  4689;;=>?<6ERONNH4 46689:;==3QJ88tG88! H62446789<=;02WkpjX9 YYXLA512255789<=gkE8>Suc/ .@RYYU; ؒʹ/0124048@x Ht4  +CPL1 UھȮ-.0127<5n  6t&" 5D2ՏJ wZvB,,./,SIV  %(*))PY&< 1<O1vADB*+,-5[HA #*/221.{4<& 84ѷ; Hx''()*,AZH"+49;;:86bN<:3DI@_RH:6u|2'Z'96<. J &()*J_O~ (2BFF; _T%(,-3_rZD0*') tT5n];6&!!"<.L`'/6RT(P'(u?)7=9+*E&6,**& ^l7.>)6,..%0# 'nh8 0dL3((  цnN8W$H4. Fk`%(   f~~AF-)33&%/4( .069?<710 @owc8$54$.JZ$25  >`zP' Uij^POV\T7   "'$ EQRPI8    &   '   '  U 1=<<=>>=<<:998530.+''%"LZVUUTSQPONLIGDA>;9665 GZWTTUTSRPPONKIGCA><85445\YUTPQQPPONMLJHFDB?=:85223#%ZVTBG]_`bddfghijklmnnoppf8001$%XTR;]/..!$VRQ:f.,-"TQP:h-+,!RPP:k-** QNN:l,)'OMN:n,(&MLL:o+(LKL9p*&JJ9p)% HHJ8p)!GHI8p(FHH8p% EFH8p DEF9o  CDD9n  BCD:m  @BB:¾l ?@B:¿l =?@<¿j <>@<_ ;<>=ERTUVVXYYZZWVVUQ7 9:<=>@ACEFHIKNNK8,($! 89;;=>@BDEGHJKJ7/,($! 689:<?@=6DRONNH4  34579:<>>3PI88tG88! H6234689:<=;/;z~J  YYXLA50135689:<@٠gR\}: .@RYYU; ؒʹ.0134048D, nE  +CPL1 UھȮ-/0227;5  R0"  5D2Hz qXvB,-./,RHm  &)**x&< 1<ԎN/~p>CB*+,.4ZG_ $*02321/=<& 84ѷ;Er{%%()+,@ZO#,4:<<:86k<:4DJA`SH:6u|0&V&749-F&((*J_` )3;!xH_qp^TQXYO@U0*ɠ"n F~O I.$%&(Pg #-7AIIGDA@ vphH2,& $Myf9**͵R(#Y(,9=%G"$$'@MF&/9AGIHFDClX.03.(2;\tF,** ˫":Y^$d#""#%0G.B&08>DGGFF; nT&)./4`rZD0*') ˥nQ4jZ94% !"<.n'06=BDFGE@T) %(Fs`G1*' ʢ$R>'+Q D."*  'Y.5<@CEC,s= "]L5**)  ʊp)n<OQ(N'7^)6=9+`zP' Vjk_RPW]U7   "'$ ESSQK8    &   '   '  U4Z!s 4                          ~d7 ӐI# h’f .X, { ]2()y6l +8Da-f/ / 0 0 Z12 0O s0N /0 " "  ,pxFF! !.} [&8z .Bd5)!+257 7:CLT]fnȬwJ?5)"$$'08@GNTYXXRH;4,$ "&)(%!4      ˿ź츼įø쵴鋊镌鏏퍎鵑镕 雚 頠 馦 鬬 鱲ﻫı 鷸깴ı ʾ)&''()+*    ˿ýź򾻽ºĿįĹ󜔔銋镌鏏󛐐鵒镕 雛 頠󟕖 馦  鱲򹴰ð 鷷񹴰ðʿ)&''()+*    ˿ļŻĿįĹ걾ꘚ󗒑镋鏐鵒锕 際 頡 馧 鬬 鱱󳶼ı 鸷𳶼ı ʿ)&''()+*d ʶʶͫͫĥĥʜǴ񌍍 d ¿˶˶ͫͫĤĤʜdz d ʶʶͫͫĥĥ˜잟ȳ򕔔 _ /@Selection Mask  @&6B@>B#!%p!!N!1'yN 8]3! I9IZ ?dr)y=y4DDss Zu N   i ,W$ S.M{. $. / 80kD%;p 1l*  O} Y3     8 A >  m  L & & & & & & & _ /įø쵴gMTP/web/libusb.pc000064401651440000012000000003571161625545400147270ustar00darranstaff00003030200010prefix=/usr/sfw exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: libusb Description: libusb (Native Solaris 10 library) Requires: Version: 0.15 Libs: -L${libdir} -R${libdir} -lusb Cflags: -I${includedir} gMTP/web/images/000075501651440000012000000000001205070642700143555ustar00darranstaff00003030200010gMTP/web/images/loading.gif000064401651440000012000000012411161625545300164640ustar00darranstaff00003030200010GIF89a!Created with ajaxload.info! ! NETSCAPE2.0,30Ikc:Nf E1º.`q-[9ݦ9 JkH! ,4N!  DqBQT`1 `LE[|ua C%$*! ,62#+AȐ̔V/cNIBap ̳ƨ+Y2d! ,3b%+2V_ ! 1DaFbR]=08,Ȥr9L! ,2r'+JdL &v`\bThYB)@<&,ȤR! ,3 9tڞ0!.BW1  sa50 m)J! ,2 ٜU]qp`a4AF0` @1Α! ,20IeBԜ) q10ʰPaVڥ ub[;gMTP/web/images/Capture6.png000064401651440000012000001525041167500155000165600ustar00darranstaff00003030200010PNG  IHDRv(q=sRGBbKGD pHYs  tIME ޾- IDATxuxgN`$8Cq/ŭE(~ w n!!$=9ߝ,.y?>Ύ{}g΍9!4`p%C!lƍ9%PtbV==N-,Y!1z(#$222˅ʈD" ,)z-,Yz4A/]rhfHt?N]|sha ^LWu woHcjm6*͖J%hP.w!`YPQ/(!:v(U$py.5k3 84åt>&:6&Z%م@{11}lb^&h^2C Z0ZHSBy12iݒ`s5o` B$ϊ)|BH.ABHpBp:ڣ]{&eO|[m ,/+u$5e.t̕5k\e.cw;@Oy\z&lD0Q|$Ic0-W @^8j߱3B8#mlBPJX) ޽͗JeL.έNMO@g${*I9|[%RqqB#0࢚@_;ﲓ'i!ps1l+""0BH&'lפQlD"J@劗-w|Te2 c[1N_r֔m;t?=;0LBQ!üsmJC^M JVta][lǙI /^Doer'0S~=EA2nܮcV'R-{_O^d4lWӍE.^L!dds0mg$嵪eu32ڜbR"XEa)TBalۡB)KRbh˷M7^"Ƚ#/|hkENcײɌOṳr}cK!.,R*!#?FXgW 4Qr,7i̇mD9Q\FbKf [W˝b& գwyI5Z2.߿6*r(7# !d].ܤ@Q;JiaYDn+ν %"uk@R_ԞumOt72&VcB0)&nF0Lr[BaЍ~zg3$p 0;}Ӽ)#1 "\G@CKRRT:.cB-:iݶWҗyp(rqb"D&*P6;L 6ÛxegI D ̘DRkcab 맳͙9U@Rl c!DPu:9Ȗc! w)jZ?|aW) qU^(LrX"%25fRIҷo޼vu ӋIӪM[7&2?/%%5"αnK68ײOx[LIW <9cRXqq_b?:Lx,BίqӸGNV;O]InW~<{w ]L|J)[`<(n\ӡSb*ʥ iݩ\D>N"BZC傉ĐҔxmk{s0_kZs  O%wCO229(\'$:)8.1O)6]gbp?|H|y/Z \k7WӒ?/mЛJiJHDk~f˄RJ;@*@13(VG-P׎^>.%u7լUG[$|>|&C+3g\,YϢ2e᰹ %)k !0TՑ͛ƽ|~-&fREZ'1g JPQ @a1:(_@b Kr,Dѷ 3bHLr)Q%PTt#tptR^MMINMIV/ziE;8:iL'3[N~wg"f[:q[omSnQRb~e^bꦤbTB%6NB2aĦDm<g9%&(zFV%zEmY3H.}}*jRd_/KmZ0g"EұozGDvK/cuw' r麘T#i:G|2MۨT<, r{`n3^׫\m8&9gb2Jmo߷툹z6̣EۛP.枱ifSi]cL{~tS)7/fcg,c׳QKlsrvQ?%%"F0rDq>KܾmrAsE̛'%*ql1r=u4kّA;s]o ˋcn!bcÑH$+- .+JEBa^LW7ď["*mUPYX4x.0.x1b1tPjx<d@_勾@%f|\,tPjU-B@&+3CUb /@ف}ae@JL@HL{1D"Ubq\8<`H$LV]lT)M&&5}l R }},-X,U,srrYl}Yf heffffffjj*Isr---o#9N<#E]ˏd¹a,A1ئ233|>=HGN33mllұκw Kk;_Ժeof`IbV>"H 07nܑ?"\\\|>S+WD0ҿ+c[sr8PHQ@  b10py|fY/) pB`|#b>#;S$Ea胋)DڤEFUe3`vٲeH$&rÇ[d gH`0 KcU/X$1?)" D04fH-@MbVɑ#GlAB f[ ?}yipѷRi3RFQma0UC#[^DpH4}ׯjJ9Kn*,S3{vNƕ˗ RRCX$*NZ.+ʽ]YLĄw֮UH}A5 JA;>Ls-5*)HX,V~~>877̌onnr1$I|DR9r++Ud#>#ĔfVZWT]YPE$B@ =3В_$I&z1QU FěDg1nm!%=z7*2J Y5@ ,@>.C4+Un!CNX,E)`09_8vZ{ B("D'c$)`0.|)H))IIIF(.Ѯ]夏OzMjb"UyPKmuM gfe]'`LTnnʕ+̙ckkKoYzy󬭭1k֬;w52CcT.M085h.~~O#1F e2TCҪD.p*쌌 K[:ZNBêc;O$P(z|gvde2}>KW/w&H &];ST"׫z9$?~tpGaL BrQU ,/}Y xaTDVVgx<^DDW=<3׌6a\3$F;vҷ^|کcǵkצ"( $)JKcsX(G}B/p mV-+I,Y,$ %XZl*???%% M]0sݺu*/&T_\Bk2# ;uHtݗHqt>|y^|zҔh(vԑ]D" ƋF}se:~{b|bĬrH$dE9D9V#H$bX.D!8JyD_)'G4x1Xq>ޙ=!ʡX,Bx|VwX$xb;W<2I=smiJm4U*6K`h'^5Bmws:ӲU[:>)F;`\_FcUwҍ6"PA$UXX(H|-W"""f OLL\r,~1;4,7>8L\.IvZ#Ziݶ/`C]Ht71lƔ/8:B11E-k9 mmA-7Xty1şgd<5CaaÆW\ݻe{h2ϖL Lљ B~&]!-[?w[[Jz 7J}g>:z5P܀YGjolԠ]̬Bָ*e"؂퀶4tOvmڲyݍ?2ɧ{x1maZ8¾iUm|] S7 CڿiP?/,,011EH?O>A=wGI>֖XYV=Ƶ+tKĊ@)޽{$I&&&ZXX(~i'BDd,D$g/Qw6k n~Q!]aB 6mcF>#v:,ɜۋkz`eם C[}UxEWjЁЫ'&x|`n3?dLs`5BgFU1*B=zYҴiH7z+A֝ 㒘*E2Q)Dyn* (T|UXrRTӷLY,oJLF;- Xkݯ=1FX1aņ'ڱ= EW c|]=e-gAMk;6} MPWO'*1 *C ܻw~'//‚ &D"9pBH?HiwW O7jT~1eQæ)Z8^$ciwc/۸NJ4U׫yې&35 S:M,bߚ.A.vt<[P׮n]wT=yq #߹W87)!S˜Yڒ`h'ʄ7jQV}Y(4AZ*rpss[>//F(ZXXdff|oK$nݺ{5W\qssCj^Lx@ċ%P =B6]Oml/&س^aTWqh[q7RPh=VNfb&A4!r"q=AuSv"~LjK!?Qu6 &P&J!$X>AO ('L*lmºtFm۶-##]eS|>kM4/_<-6 81i󹥢~F - g'&B!BpӉזctȲQoYG6o=hrzm%`r6wX%>k'BM?,[޺ 3w obd?OHMMvptz2yiRS5W`57B7@g#!4zu 0/P=)7}bTnLR͋*MϏcVEdg5jvRg!4,'; F8C7qbݖi#vZO7˓lNw7V@5^(r@ŵ7\.)unY0姲0X06e'T$>zp]Pm,:Q~. ?lG.լ 8Z2 NechxQS=QT%m\ T` L}* w{Ǵ8MaC%Y#BL6ڱ^P 270%Ԕ;Ӏ1] g@b*ҳ Bcm?d/?z#9e:nzMXBǐ-7 ;{Ҝ'Szmc"g4oP˿ϧT"P(fE,'\;-͍٣9x'E=;aiQW#Z;?GI ]ݓSuLouS7_\3y߫D9`>0!ɚ՛ rȧ5 IDAT}1Bg:ba)c9]G $_Ȗ-pJ,3RU< LVtH"r:ST"\{?7hrM! !fŘpNTIvN͇3HLt e`E-'tTy4TRafz/aa68j3-qG{̚z6?v&Ͱm0^%0;)i9ӗoָ.Æ>@bWg<שi*` Fʀq,8893,S+mm|@%G';Hh9`6׵7Bh{ ]mKN ǐIϗ_j@z-ԃXx\Nl6zxDMAb@$fjb7-}rJ,&,&ҞQD: z~m2LAc6 LgR:!=ɗLa;^Y)gI}7uyr'k[P]SmlD=S8:EQ tuw zUbpt"7aVv#2o jTÿْE&)!!6WeUt1n:TS w͑c"plĦ|,? # hUѪn` )}wםKtz2}jjlcS#8c/ }<@u8x]m]r9/4hP3WhР6lM ץraĔ65鎬@L`s`q0|Y?gF4 ѫwlf 3;W GK3/gVt;L;6jyvқl9j8Yz:zl6qvV.\8_[[3{p;Plj.;BSM<]`yGB ٷO#lS%&¸忔Hb&/ u)N$';H0hظ?hbHgᄒoŠtmRQ_;YORMRr҃3O|?/Л'̟ؾԭ6֧t׶搔Da4ʁHg80rcSzlz1]_3&uz\{< %ӧI^g&Ё6.=8ձg ,eII &M`0U4{_P.78Бj&1X.Ӊk{yY:YQicihb] bcݻˎVt=`YG_/pjbն)VEM?MsTMK/u 걟U +6P)"&1OXҊӇ"ɺv^d 2_/o΄AշZNnׯirčMJ q]+PzF~C(|Dgm[nf*'GwBfSdk֎5o=ĩ*b..]ǯ3"oUx Y-۶nWmڦ-$2[eRI薍H|^UӁ##PItQB)n<LphKS6Fݥ#ՓSۼ +xeJzT$UohjjCBuE/}5Y`x<)B"Rzʜű?4bL)"uEMKw #?0#+i_=eO)%|J6[G5$erE̚k"iҝ˱E!T"e0?2Ijj4hCҹ:tt "[VAU #__^Н[2B(/U}SzT(KaC5M<4Ww{ʵw3]a{^*Dy~^>1 9dŤ_iV  J/2\efuC;y{,hkb +o>z[W_ 8bDD?7a@QۯG|7/#zk!pH9=gUEw CʱP#a k͊JoyWm{8V?fAjV]J jQ;WkݔBIyԫzc:k[Z ظ^<Bhx?=N'[k{zL zS[|p2]bOM.:{Ҧ>U${1c9unlyJ2Z999g@{GځȤ#- e'v{1':I7-*q]ћw>J_9T#x|Ǭ:R67県(IPZY/۫ku~}GUݠ SkC%<`nΨxJ3n3?=sJqy^ʛmڗ%Tːfīk[5 h0 :#HN<1{t\.gϘ~ >' fƍv}f۶mWRb ,g)yyY)w2nɑQ>3T1<jFJ%7GpL}lSXyo@J%: S"o]~̽Boaƞk)´[XO}qad gauZгzWU()K(ϸrxet)ٺu37 _Y0{ؐ*b4V^Snؖ^Ga?Qn b~Ŕd*-,mKՉ~7xs#w<9r?>= E FGُ7pҞ/ߢEs+^Ԙm|B+&]NSe;IoڹqR-1gn6\cRwOp&JZ7MjEByU mu`Xib䍇RƩl2 9 pOLm$+dy8X!ReQ3~h3[p߮HdvߚCt2Cc{=&e23nޤ3e]}c0VHӞ.w `9s]ç&4jtxMNq?xåAˆUYcG{_ 괎'7|Mֽ7zdޝa.#f*^ce}VQh|L\n9ZUv* DlL4_ r86*vi1Mܜ~t0IiOߔZy[ &/=%AcΥ5_Mls#_pgȷƓx7#Ռw/f~u~NN}BGO[٧m'_['MJkB+V(*v²4 /kfcY&f.iB+ɛ9XUTiH#9FQoIRP8~N ڑo#d,^LN;er\96%TYct3>Qq]_9U3|ќko2 Az[XXy(ˠߌ!4Ok'Ƙ]?llL$2B:&ΗQ!ěPbPCl{&\I"99\TI7(gLbNo3}ύcnp^m6裝krL9}7 kzwlOSQ ˬAj+1bhKy|1RF> \O\ `/܌^D͔QEbHF \jJ% 𢡊-?UMegg}kY'(އg[ҠӾCU$C_{jg"r?c},ǫŘQ#Y|[ S0~Dɐqhm)#_F\)'EUqo݈Zu%fJ'ysU+Zm8:3?i0n((8Pm-@=M$IڔqٛcW'WUb@q0UiFԢ'149T[*|{[fD-:(8s`/fյ0UiF" oZj  I|Ya0&a')dVQ^痴up/&P&3Pmf Lϰ3gedXYY4͈ZT:32y!@0@b@53 ݦAv aX pFzڣ 5pUiFԢk[7 2t*1 ntd1 9`2<4/7 B&iaiY7 ϫM3U/] YeefJ2rYq/+VB:`W ¬OvqqQ~ LӌEU˥uHI̸ngч'oP_ּ"0A  ,&T7]kOH.Ŏ29 $5͈ZTϫ)1S ⓔ?q< T!9"cjy@o'ml~HLxxŦd[~Sf JT@Mw6}drUT•G٘//SqMs/[zt6<8Z+Ol֧Sۏ"?mLzw 278[=j&1oܸѶm[;\~M6j0[lԳr/_[՚`25H| }a/*ëēGCd2)tE5:ŽdeXv{`Fvl`p3? B(>)K)bq1yD@Ho3G?C/qO .U[@[Xb[EOLR%YaA~E *\VIzk◗ZrMsޖ47YmHG|,;+Vď!O/ f&m8g Ղ }nvQCA~6SULc(򹤾,ǨZ 60)l}RJ~vRϱTm-tٓCItrv~ɇ`+zpm5h>1Tr+Hu7t(sGb2j zvqI\:`fnj9Ʒ$Iez.24SĝwxCHZиcD[(]K-GsI(`orU,Pܙ31[F) 3s EEbaukj4c?kqq h掟0ϕ/܂(th\εfQ xdn4vY=SsUVT>tL#_{#|Z?knܾɍ /?6S.(TE..1-!1{G'g&&rx$?bYt"y"^9"3ooΈĮYdj$dgvqdz _R8xS)ɉ̌̌tN۲ͨ4Ȕd*Ɍu" tEv(T!mQc229/BM#: PDY*m 9k66a#4:nutb_KzӲ᡺nfjam\ѳ[&h :^'g*߶hT>y1?ѫ HFGm,9L]7Ș3?WT !{1ez.ebf6:l",fSPi[|ʛƣ7WrݢLĒ :?^m9ǣxh (+x.o*S(e  1FV:l)}F%groI;F֢,dFm܊E$ EyJ(WYe,C_D!TdC"KL$bz/f5,b̽!KLq[mXml(IZvJ^gmam,y-cZjJZjJRܕۋr9dTJΣwުfs)Փqӧ[B3[wh6з%Tː Ws^Ŧi!?y/$}(ס^mꠕ"T4rSeF _ר3*GтEl?_[LFUt؀M=~oQIfsIw˳LGۊl1BSٷNV< g+WB'|E٦M.ZݾnBBv0†(˨Qħ?_:ß_kTv߹cJngc˸w<ѭt1y⺃tJ2#}`p֍ 6+lV_3zPSuǡzz~ztk/B?¼6Pؘh@pm*[-P_|:vG5jF_emZjb5̖ӽݙֲN[RSI~w{Liľ?sx*ɌHGGE7-Q8:{pm뚰BgVq6.lc){R[EVmt&1Eb),cz;AוkJ8c7HOb\ma`s*T* 0dV2+37~܇W>y6F*f7tO<~;Ge'r2#u-I9xZ:ftrOj FXNR(R9Eak3xH{ h LoX_vvY)m\(04<9TnC ,Z}ĺȧϋMкl8ZHbphȂ`@bǏNB@' 1*C~* p%g=Xu 8v .{TQ@br@w~7-Lשa/*QTQ@b"P~P|RR|Àw" 0xxŦd[~EN[KOגk$%%u*-NG ˖Iia6%H KIo)?{_B +V@ńg*)K'K"%\]]/HBҼ^d]4Q99WݬO݁z)xJHr4O`@ڍێ_a,1I\ddHL+wKjcopӧEiJ0^V/v͓#ڪ3T0w]:|oLYF%\dcyAxւ-aC学]z5~rb tp%\NQr\N>>r!}7k١$eU vQWwP4L*M|ٹsXۅ-Z֩Y̯(znu}uu>z&,eaAk0Hd!*V+Nh;cOmӸw&/]kF~J^oʘݏB)"jW\(iIȲНGtͭ=} R)S]k{6:USD6ظt]stp%LNLϥɿs\P ɎӬ2ϞN_}͋X΀-F麮y#DYu9~̟݃Nǜ4?Goޝ+)E=f}{Nj mnodsvxgF5][r1;qdFR~cAMԯ/ {R=,X};Y5^j}&:mFOi$!;%\Ns#&݋j!GOsh+"b/Li˟pں6i;`U&i޳}ݾyR... mԪt\~]PUem=tT2^׋@^*URwo,Ŏ@U˦/VMɐi Gvp4yO[dW8Ju;eQv#Vs*.I$ikVr ]զE_m6)-BB[Uvkw]J*JFnMXٙ33 r_:yy>9|>9.h{:{+l򼳅BZ)Kr3'- O)BF)`=!Dyw&YgvLKb-H*ֹe]gO2@Z~! 9O  ~ l-mW9m3@?v1LbJr͠kL_]٘`EkiɑyZ[^w%q򝠱j5;[͉~B B7?H =ifLp 0[$vzC KKoڞ/Ҹ]iK*]?XuhtraIÌ|UãJ*K v2'.>07Xeګd2IE!$9L8Z_[K*Kџ+LKb!V$^/*~Q|])S_za+S=*,{Zp9 *rp5knn“lnn OC>n6'trͺB6E#ls^-2{nC3zOigژpfBP|)-™i!bi!< wJb7$Lt1LOoB*+ʕyy M+gFU~*MGb6y4B$4<_p$^f 06i.dY,fi ]pݘM`:p 3\՟w D#A`^%-?G٬` Wf%§nD"11ćdalm+ F) pmbh2-BW} L&G Է K<R{v %?Nswޒvٽ̘ef1P5?q}H]XVx'h_#`]z~ۂ5D^TRnf-qG$}J%/Ң #?q83N~Q[Ot8ʚ-µ՚0~{ ,ٱxgb1~ᔻ /O'lţzgS@f}$֘o$]TG ekh)uw$dY'[vK燧wwv[hfɁ78%Eg(j,Ŕ0ɦ^UOm'>:[`E\d *Č);WYkZ7)h ʓz 1[ğ&=X !ILZzW#sg^ ]NFWsv79i6BRv+” +g<D)*Prz=BF)S]ycwSJ,)ȼb R|mCiǷTJk؍Ť"Y5arv0d|_Yo,'BѨ|/&\.2ee3()ǔDYS0"2r=91L`V&,X#o}V/~q0eSw&ԿP$~xt?v0(bH4t;Tq!r<۾}" Jc6Z"Fͱ)3r$jSUT4mvLKb-%c);%Ah)FY.Q-I?n7?H  [NߡvP՜ǯvC}(q͇}|qp(Bh&YH8 cJ3 `|aф\rR/l0Ub/eC?Sr{y6FIOxڏf A]Թ9U.h{:{+l򼳅BZ)Kr3'- O)BF)`=!Dyw&YgvLKb-o l-mW9msg$掠vο n>ML>6;S,vF g-'P^LI.=y3Plqpq7-9r9]K{kܫ$!A!f!fL#!na3D%6Rmot+Wz1[$vzC KKoڞ/Ҹ]iK*]?XuhtraIÌ|UãJ*K v2'.>07Xeګd2IE!$9L8Z_[K*Kџ+LKb!wĔkJ+˞\~Jz.A,+{x´Oޯzny5WozqQ5y~e.Yssfss/(oxAw9#og\.2a2%ls#1Hђ0D"<ֲ uncpٓ&VFy3cWI %*+ʕyy M+gFUo1cш $ixIkm}əc.i8fѿbl!K/X-& t쵦!fk]]ݬro;OHĢ[cc |Y׿egnN]<G+0H]L&#9:W\̮Wҁf170 OogJK/\th׽-.dB庴F7sĜw Ƨ8W&]m#t崃W_w.{V/ol0%Y3]&.vJg{8h'M43M{W;Lo`b"\ s'{Yb9w]dԟDeIBIi\湋JZ~4JBF) pmbh2-B㷘šT,1^RBSļqut&V3a@u1 &IB9mB IDATOԞHsɏӜC(xv/3&xiEr@Y T p9~h߸;o ZaXAWpYTYu-HQsAmJA^*VaFwpJׯT_P"-znP*NmUN~:_`<ĮK7s"\[I+[3بꗿWϳgQoK"Ӓ"i%t'W&Y[︻ xvw2nWg7K_LKG*ۙ5oЙ8]LFLG,fqdywiRdt\.2O;xbte-ɳ<& E!>$ ck{^QD"zz0X'q/f Hj JN١^xrxTOoz o($$JS#ȩ,?\Y^CY^CEASEަͫKD> @^'w8v::Xn1FӜw5ItFMY ,w$T ʬV;Llzk"cRn?[ctMR}z..#/ӖQ墥a Ӓx-Az"jk*/j<=CEAeOTca77&SE)blʏ_9 8пŔ0ɦ^UOm'>:[`E\d9E|2<9髽})ᙆKyS^~% O{i4cQsw(H(O'WيG%7)! ~˸r=߳~7Ⱥyv;wrrrͻwwaUdfT\Y 곎̡y)tW`;:U^yjTvNJGN~6gd S&쯜m̪;8liK}2rMes'WeIAD:Duv._vȉAt1zRu2~&p>q_YY+#m"hTEViY3:\,uyo\~,eւc Exs,65\"/3'1z B^ BMA,9к{>B gcLP0 'qW]5b Tf)ѳFz;3JJ~$™j1RN^^EY߇E7zo7nU5Z7B8eSw&\a+rŹN`N6l.Ә+HRgݵcs;E6}DШ96 qMURf%1-Y( |'hl9}՜iIeGfr9i5.+?%BeKN & 9П]L+K"+_:H/yGAq!O\ΉGM ߜ|!4|tg8MAdp`rnzέ1ݍvhB t9s8L;zvWpc"%J]MdHb2L&b^RJFFZF?lZ3)FY9)ǝqU8sey}m׬xگ6 !],nیNYh!t7I#o!z\d?&0'@D}(tG!Q f!B(gS\%.™i!bi-[ w=X]N*0S/ܹ V*ټfǏ͚5Ko+ :Nv̘1zCi#UYQD (khjlrԈb5u-fzl#Y\HDџ竾,ٸ~ j#F5mCӪUrD&ڀ[pלL76:fXLb1X,6wwc69ĦFkok{sW18z~,&zy%cf7773f:;@w Wf韰X,!|XNNdNV!ġO |,> CCI2gTeuٌ/9,-# q)ObVeYBuͣ;l*ed3#6:0'EE_̞3D83g"##w//wȑŸ2SJB&2iE6/p٬3\Utx|ph U{u AOoߖ۷LZk3A6KIuuu55#GTdPH5{H$-icy\ /}-& t쵦!fk]]ݬro;OHĢB Tfkmolf7ȵ7^tppu떖֭[d2 :+.&h,Ca PB4@5j۷o,X(6$D Bft"G'tGrP.gx`ô-?ߘk$C_W7>پ2l$6ݫ9;oIQ^fLҊⱇbLMMV޾}#55ӿD1,`$ܫJUfKٍӬ /v} jSΟ?1!JJJJJJ:::ǎ[|y]]>(^[Ar}\5Zn޿d8!wgo1g9םo{ LwI~֌gIݶdibp-N!5<"I ̭CS+ޕiXWx A*"n}cy.Y"=aYPrA1~_ p3;yѤ0W4JalCnEB: _" &IB9mBOԞ5 Y T p9~h߸;o ZaX@(r1l0}}Fww/ pI,N-d)ʪF /3]먫5yMAo{~tiT "l}/T rW) 3#S~iыw{ɟ θ[wPApj{'uUv'v]QsejMZR~FUzu]<]:e͝?/\PL.G4yz뼽w-b%AW,$x~OS+6/ރؽtwjg}i> @^'w8v::Xn1FӜw5ʶwQz09m 7Uã-2kS'}Z"m>$bԘo$]TG ekh)uw$vWPL+_x{Lx<=oM .2 _vf1ߋ)aMwvN}tx\((XhI_kN I#/9NyG7M3F 5w^xrxTOoxްa04ZMN$)22b"'KvWjoV`#4+v㔇ߪM"V%!rBTΧg㇛KmqZSJztw5i\הM>B{6ӫc_՜MewN넍jx]'kyJFa0e6Ϭ*6QJ7T^r#no/T`?G%WA=2GB3.nwzR3O-ObrL**ҿD}{/j-e99FUd?.PPnu%͒o.ItL~u(Edڣ˱;*2 &q t6[>l!K--׭[NIIdbt}eذaÆ)\㔿gUx =9 6J@~ 9.=0 bNͺLJ7}9XɩL9ǩmg;icd/KEۑ3MXڪ"ü&;[(/՘$;sҢ`D"t\i #B;͝7ZQpgxfǴ$fzdZG$L [hoejm8%1ٟ52R<"lfجGFJRR2-&jJGQY}Bg0ߋ)Ʌr6'13}uyfc 3.PPXw?LЃ!ni'g,JΩm2:ȋbXLi.deebJ///AKw~lA'>fK9t|XcRVwY]sOL)1A0iw/}6]3#ޖU+>qIŽ} D ’?G[cT^-dO\.|yanHWdBHVs2q2&/T>ƣ?W,C}IiEt| OC>n6't/G0߬!iC̸m3";e|NVcq_}5kŅF"|G&8:/.|zMhpnBP{/ K\/ - C,cʒ\?*L{hY:HwrcM<$(vz:{+)1gF3i;[1f+~tkn(ޭږ_~騤7FL:Z]ETq}l<ҟS[x J6d>w}\:#%(|cychׯ㴬v -|`6B!?aTeE /DzS#ꪋE_з鱍h{}!B$4<_p$H$,AScg:Z r222›_*#%Ϻ4Z7 e2c->wӢEjÅp 4Fg,bX4.PQn&G"8Ko7 `Y95I R$.%Y9cr˚x)\/rsa=^y)D#A(AB`, @b#VǫҊBBSo"_/@ҟL*0%HCC4bЛ&|:nl[4{< .hnRUR%m)>0'TeY XHKKS22CgL&KÚb JB&)}&\(S %?#G A763|Ԁ*7aWB:8STi9|&k` "#-ca1)Z#xY,v?'Z[+״(uo@iR)nh:l 7Ƃg ~o+#=jE)3c7222j-kij**)LZk3x)~Nyw:D"f%"Ю!Dc{PCZpg_+(>UUvs+)[h-mUe?˙wJD1ؑWTRc.&d{rUɠ H!tB_8!*ץUwۃ}^zC[~18n^[yuSy\0px]{@Ⲣl:JR K_rY9850,CxE%I=~>h* =_[WcစcIYƪ=>؃s1oK'zT7S1v7(eYˇTã-BȞ4\׬tFweZڰ7T9/\.^}ǕG?@zT=D ,1f#}8I_,󞒰,_([uǝ42ff_tۍH$w6}YHb*BF)wOD˹QUyo@B9)N6!_a'3JUj.qycMϋ? {VrhEEOTj}Qx$ )!7FFQjÇ!,E*WaVx9[ڟ; *B(}J%ϓyriWhR;3pqeHgiJ 丘pj{'!VCĎؗ`Z+Gk5ieKyQ߫OZvv}'HLaOƶ#^s)[Pm N}w;b1OG&z{&Ec &.vJg{8 LwI~qhaܧ '(kNy4ӑQ fv (Єo(]1h"h=IO@r{Vfv=&70rW_68T5&/:rCA'M43MHl`Q άx L&W~̀/Xo_E|}'stTN]G>˭;H_t<";Ψ ]=K}厄іR}j@oxm>$֘o$]TG ekh)uw$dY'[v IDATK燧wwv[hfɁ78%Eg(j,0 ]L:d9n4c]媧ﶓ|}]?q83ƣ J_E/\ u#9>yz뼽vƔ޾W|!:Rׯ_Ij!`'v]Qse~M״n1RШ;$N~Qѿd2gJ qZo~9G !#Wݔ^橮<类Xd^)hs]6xۿl*%5SBbR0 cYL烿j-e99FUd{A5r(-AH?$zJ4{`awcqw)TdU2d.`(N#~u}#1/pV31dž'ż3N`Cj,6p$McN#Jg/.[)۠4&h%BhҘ=#M6UEJEfǴ$f؂H.Wr Z>vPSԌrm[0Vw?L)񝠱j5;[͉~jw0Lq)QrQLqǔg, 7jn*^B[a$s4֝~Ϝ'WL~P9)ǝV8cl;m_f A]Թ9U.h{:{+l򼳅BZ)Kr3'- O)BF)`=!Dyw&YgvLKb-o l-mW9m3:X9Zo(L71\"NɲS6&6F&YʯvRJ󸀉'L |fO"MS{8le֧BS_SukH9 b8ٰZ*ز)S\a+ryQTMKX+Z^'D,b+meW%``P{#?(Ux+o/b+i jůw<] ;3 үXLZ ]a~"]kN`l\JbX,&b?,DCE1bA=̿ܫ*se15u͚lxAw3ǑB7~yڀ3. IzBBB̘RhI"fvkY:rIFH#-,:RR ~Bz덱`gFAb:Ikmf1/ygd&}-icy| /}-& t쵦!fk]]ݬro;O3ܫoT.&y{$'\^Ac|(L?ke|sWW Ҫ{O?LFb;P[yuSy+Jb<}mBO~* ``4Χ@ Ҭ@/7[ks}c39nG@&f6g͖+\Y_OWԄ?AfmWמ;h7oĹ+=-(P|x8i\H^8`Kf<('Lz]축$K;πnq !dOhgneZ!hL w4?D}Ǖ=@O:P!Twsλv?) ˒ *L20M,po|g@(nHIg0L:ӫQ ~K~%F{1K+=Y T p9~h߸;o ZaX L}PŲ ¶B /r0#n;8%W*y/(}xW()댻i?x T w*_Wel?/0zbץ9WLaUI+[3بꗿWϳgQoK|"Ӓ b]媧ﶓ|}-#;CH`87bv^LQ`Wd;>]g8AYKw,ϣIaܡl" MH!HL !Y[;"$$ӛ $`5Klv,/lp"i)(jL_t8.f˓zU`Q?'$^Co\Lы, _E|v9:cSqJ]`?yG[c:DgԄ>LNrGMAhKھav_mjh;,"<&sp5I$շQ碩2?mZ.ZJp0-ǼQS!hTEVdW鑝! 8ҁ^LQ \.2;WCHϮNRhI_kN .#/9Nyf8_ӺiHAPP OJ oRar#e'vHTPudK1jS;'uFHVJt5<ڮw<{%T2algV}a(E_N/GBU7巗yk0yn%W5}#1/pV31dž']ēos0!AyW@m1ۑtU3ikamPl!4jMi̞&W"AlcZP3`ITrNm#yA} wJbQ$LҸ^TN?1MCWeqE3#ޖU+>qIŽ}cvtawhtraIÌ|UãJ*K v2'.>07Xeګd2IE!$9L8Z_[K*Kџ+LKb!!4:${#w(X ]ѫ7Hkԭ,f@\d7&0'9eJB{+Sk;F8t> lLlgL8TH!B(gf.fExC*ֹe]gO2@Z~!>f;Wي_%(&`(W"54٩u!ݙ;&q6PH,\Kb1RN^^FFFx ]t35'06i.dY,VjEj:3YlpݘM`:\՟w 0^tr! v8?y,ei q:&Ci0A?ru`4Ȃ )R/q;k3\O[lƗ\(YUea:FZZ !f5ﰓ}FAfG0"PW&7qI+*y~LfM᪢cP|zUn\`Ee 5?R^&X@.l!?W'QjEkB~0 -}(n߾ߐ`Z)Ckm30[Ctppu떖֭[d293{$<0P .&LMMV޾}#55ӿD1,`^憆\'Jlf,cRv#`4+3=Kjw5/k2E!:dq~$PN}S!?-g\4}-i7 ˌ ^Z?PjU\N.7nۅewV=5/.&w^%bq~$bG&z{&Eە bb$Yy\w !h/߳zxcI.Ϛ&v<|1}pYG8_(nM MfQhB DbbWNa&ӛ $jXNyeZä&V!J{'DESbv(hzzV]# V<*4CW6bvy!!!"KbS,&z.W=}l \\huτ"ׯT_P"-znP&jk;.(-],!'לy)qwׯ$!`'v]Qse~M״n1RШ;$N~Qѿd2gJ!=ET*uذa"kr٭YZ6r|4"+4RìQs.z 2ܑp!t,eւc!dib#)rS.P!/Au lh=o޳ұG&k(^wXDx2Ǚ_}};aϙvx:J:Sgu v:瘭DGhCD/ѿOS'Sk(οw3b_զoN_>I>3A,̘T$S swnwnϷ+FboԞX8˙a ]ѳt4=)0Uj]n'Mg0ߋ)Ʌr6'1sI<1oLH`Pޕ.@OޙScCՓ.M}Ӓ#޵6-̽J|;Ac;jv·"n|Ǒ:%ZYr<۾} %O XSHety^ LӘHRřgjNUR JM `кfMxnlnn OC>n67$G0߬!)%֦_ mwAyy6FIOq69A/S[Z=7!ղS6&6F&Y*!3tJcsq8%"EK1˘$;sҢ`DonXv9u$ Uwb_z:{+l򼳅]5c;ͬWI!*RJV&Wmenhh% ⻩4+\nۑkoIYƪYF iVfzm;վ5)k^ &I|Ր6!_aB~[jϪ\4}-i7 ˌ ^Z?P9Xbj ~E-ݑ~N FCd0X,V$@oӤboALz]축$K;πn!{V/ol0%m~_ p3;yѤ0W4JalCnEB"C0wsXDb;f'==9X)L w4?_3~1s_DVSPR՘hp]מ4PO>4bD"يG%&xj݆.&6tXbrwIXŅ_g-NL(B(}J%/Ң orfm҂uB B|2<9髽})N ёw~JRkxm=ҍ+k&kruӌFݡ w*_Wel?M&9xTiMv xOsG oZ6r|4"+4RìQs.z 2ܑp!t,eւc2E4a*_ԘrqL%y *D[4!d@}+D=b̡`{a1g~!젅?guS(2@O541bV"U#y4~Py<_:: :C|A53b_՜\Q͛J&GB3B:Y1HV h  f +g<]1{˙O알J́`T-$aleO^cgƼ=N`Cj,6pBĜowomaUW+ [NߡvP՜'!Tw?LЩ(ђ0Dkc(yb}fF*A` ƜnGU*I%R Nvn{JoPYW3δlT@ Ld(.NEԔⳳjpdن 5onmk9!d.5C~uub|} sEy8eaEBȖȴsBaqt h( Ọ̑:8]f WY"!d %;U0m.Vu֭0ʫk*`NޠJsϭ :}AZFH%lLNbqwi܄#[H$z<+.چMPCLg֤/R@?VD/ާ8Jc~Q 9-WMܯ z>Y[VvSW/m|BT1lo- 攅q +={Էol]zGXK)lHyUZ{qq{llDG8#6l_oU)0V-9n-ؕ*QNWʍrz٤N:QΓrq5u2_~{;47k`q赝c:OO:EGtK$> \d4!djK㹻֮LZMWȞ&3?65%2m{1ڶ X~m3 XnTOuo=TtNJ=GӌJ7trpA&;;Vk۬sNgD"=U*h\Z6?*CL+ze3g_g5+I:Nst3P;xcvH_BȰ]',u*߶Ä =UV_.W$o/Y5*tqץ \BFH$Ɠl>a$)eV ->u- _k)tŦ<}Wc۸ٌŊo^+tcy3=G͒ 7x0]THb MsXL3ZrѶ:l~+i>361S޽rl]ڋ2s:w6_`&~%YUJxn&B/+3CwI.:<^qL ,)9zsk˦ W 8q >;ػWnHL34Mib!ELc͛QfYPxܹ;j\7Gv)??D(,*,}ch|bHYW1n_)(suj.Nj- C2WGnp1?>s\_;y9?9vzF6r P0 Sӯ_5kVxx8!fywι5"0 yrgNrPvG fٹsMxrOTΧ\3/!EjV4.n9wd`P#yf֬Y |> ^\.EQgS3?YdH/%uY,2e0fNi&]]] I٦MΓ(A stL-: yu&*/+D"H B=rg,:ս[jaHV>Νhnn^[9).hJN׸mH$Og&;WluT"J,,Ȧ)Q/aA&? СCC̴Sn)zR5$qy]tmw|YN0!Acaqji5 q$OWz&brJ$mI%/-h;={r`'[%WY))/@fjG89iV|nYٚuuى#cn|uqט9SJ(P@)HY^*;qdyϠoJLMWʐ!{$X?-|z|zm]z`Q4sjt@U((gּF_&QC4GVɛ<ʦٰ9[הVvKcjI-=yn׳o0+o)+".4r~`D<(3`w?pn?. V^ߛrMBaq­ e,(TO44ߦh’l~*k]okjo@q-_|c1Ǝuna^[aϟFU"J,ϬauoC#!oX;>֬]ZW8>jߞqngV6pݒ. XO"CB4 1kQk"X,ӛ>}zlcǎڤ NLXZi$bwY-d+s# +TA~2Μ]Z_B3}?6ݝr{5'?ݫߪ3 D^U((ZW1w 8ѿ /{$-xXfqB%0 ]|<Ȗ/:mCC;ׯkjs)an͚41?.W"Q .e_r?vAU{GvKXOֺ]3ס~gچ^[h`׫gܪˀGnPvhɵcO=&$ K4I:E[P]4MSeɒŭ[uuu_TCzx`Ӣ6>Z*cÀ.ezwKW)[SSgQz=xqIQA33[~1(fn$um}NJX rGM;ȏ6%n'BLb?Sëhũ7K^zjH'`HL߳>cc=94dJ"a)K0ײG1KzMxDE"!uJĭ{;6X0a$EY_ZvKkA۶k''ʑoɾٝ*TVϯ]{vjjdAa)&&dV.XX,cƌ_>rRhZpH50gܰ[!moԓumc:1QSbTf"ԫghbnmk7XJV%M}t}$?6Ԇz4xF^_қ\-nЖ׿0sC?,7T3("%,ӈ!v. C.BD%뷅BKHL!,%!ІnbUg :%[iVz +M>*@i">*IqL "B9#--4irYubr7"Y}b#MDitAڢB,ܲaDɥs!*PfK os8fKM7.#,6IZ_E /IQ3`@B$)W!ccee_(W.&7*g^Hn)H:y\J|M*]Bp@?B_CPގ u#6. ml~<$4j~ILZMSH%*pfyǙSVjGLm fxڛ|eQXkc'z{9N>PQJIK st4#E"!pe 0?̌te{7ʫ>4rRg"fvvU<<9N^Iwc7f/ּ7+ l}!v~K-g1svdQ6wSY7ɪQDto!{FH$ҷYwöY&[trdPf%_;GkXk^=AmzIso[;MY)&.R..XKcLR;+i>361S޽rl]ڋ2s:w6_`/>z%헽^黣ݢ̏?E+ D?p ’GO:Xk;vXFUqgs[( MbiXH4S:؀fԆx@XRra#Fq8lX>~p~䇝v}!igUػ^t b ,QFzY3̌YUsqWVn1&BL6ٹz35kfj4>Vt+77j}o*-,,[8;A\pnajbS#}wNF@u|/2b<;;;@@QZglӫR!sͭ%Loa'βwaToddUJȨj.WGkO1#/!Ejgof:!sA8`:kDj٤(- 1E][׸̜Y"C\nL6vMТ3'17M٭I}Pɴp"'v-E! )5I&B@?We)둒HEBL@ [윴 ĔEQ4M |QgԤ\+ S~UNZ- 3'A]m1mT~LXbQbXKoC#C~[m=q!|zdGM9jkճS+~[+\~96!1@ 1))/ͯbƯ0qJEiz44ԍK(q[8صs䁂ɣ6zm-!}Īe} NE?,@Uuk7g?0sC7 *47ܪwedDWX&Ǒ2-@r3kRW)AV+S1p?(Oz\_闶_y6usF6fLOjV!m]K[m8(v~7[K9eaJgxO~ޥ&[R $-R^.Trbjrry6⋤ȥ3~ׄ/r%#k8X#_digt 򐬚x%WB3i5ME"!$uÙMg~lLYUkBKeHnþ_0M6#dS%(ێJ_ ;yXcş#΀wiRM*]\*\ֆ&VYL2g_g5+I:NsWݚwfe!dخoݡ@v_cpȢģwqm0nUBw5Xs CTHY;om:m I)jMhIX'1`ԵkM]/J8vP-= X7c +ݽzO<˓ vRM*]\*\b%?Mx&\wӻWK{_fNs[ eGڣ2*w4$EY?~ҹpT tj܀ǜೃ{4CӴfh)R4͔N16߼aJ¢7'U9S`6za1 ]PMZ!&BL@ 1bBL@ 1A9@N@YA$B̊HWMi_7qBꋡ oo>,oŽySkx)zkϪ)̍ܣYf= %ڦbaJq"ZncNuua|Zk4bV#W7/>ݿ|\R}o K?g}֠ T<2a{e1bH8$qdC"q%歖K ;$j%kg钽uYabmHW-דBM*M|O~7ܯmXz#9we=ـ:%03h!V=Ss#K #uH`!s.0/4xˤ8q:Ӗ^]}ҷpSe([Y CKvqэSQSƶ. i#F7B9XuWY 1QWĪ.CƹJHN((HyǤ8՛ ]KĮ~sRrrԖsw_/ɿIiD,zb6!-stIv&r b_1{ZZ,~ӻ =`@d|.eȡ}܎ugr&hq_eyF=nTWHHIn}/7 !k<4=Xu]Vno3bVwv\&'55U~YPϮObU9y(&7ՋJ~:BKqbBLb 5R־ľ(fuZ@Y`qD!.b*. mbB䥕չB0FfCe6> AX Pr u uկ336EF~|y'^3n355U hVZkMv2u]6v]u?';NҤO(MK}~ь(bz9[1k[O!%MA`ӼOQF^cCVL8Hעm{G &&y|IDATҶ.:_Zӯ'!ƹ2i-!GԴl]c,f0K>vuܺ4)h>2UO&.>CS~nͻG^=AmzUTu~NNL]Zf st3#7]}WoKlpptX+IV2"""{{KaÆ_x _H[7(JDDb"mJ~MLk\ڣ2jh"+ߝ;/XD,4-~~}>}Lv֯_"i?m t0ɘCff5h[-[}ƘYEo [O I;޺eKǎn־}_lbZ8=ONnha57E;q>ܲys !4/e::9ZZY!wt6DGK(AC|&BLe{QQQbbc p17C.@ jշÇP00޿{y}]1|m,-,;tt#)3p8:YZXc ,uh![[[[9PUpb@Vb|sBUfa aSNoTg`X\n=q `˜1+Ûo"۴m߶WsU{TPӉ"[*m_h EEQQQQ:|c _s!wfWvM0 +p6@Ka?;wldmsUA]p@eed[5j$)3a4A"p8tE5%,22Wp@G?Xaihai}IG*v{xxxDDĖ-[d~mfy 0`B'`́T(Rb8.·斖 !bi:9lZpJON:(ӳpx\1Dy ߏu/1%{3ܷD~ =46443<̙[6ops\1bV6JL %巩))^~ܱ_" {iKƗ< !uk۶<ڎ9/9 >J?50OMymaifs}hUL>o OcBHvvgF=tPܫ€q @Y5J鿇vM!qw8ΆtAU/b*>v€0B/²CN/Q޼SA}17 fǎm*ֲX,.׳gOO?0/9K0i21`d*Z@ 1!&BL@ 1bBL@ bBL@ pPCb1%%bq8l6g1FD))gggׂaن 5onmkbqwi܄ԂWpkkv #PPb񽸸=z6kR;HGGǾi3GȆ שѻ}b,kZ[V9E"QM\iԚg}$+ӵ`p>w.aZvULOpB2= ͚v[u9!&{vxYZb̮5㟄J3U!&@M}j=gz L4k\_eܚjRsBLN$1΍ L;OB&t472h5,.}K\b1EQTCW7zDBk%{ }wG72dO?!5*ssGIOw'] k=o841i eC+--YNuyO髽iz!{Z3o[$_~p믄BX=ކCPsU|8:KNV6R@!)Y ywYZVB1FZf^^n^^nn;D 5<銽f~& ?=%^NUV&3]+̽eLIK_ux·%)s:4n煴Iw6M^ F9e?6o;jyi"+V}}/ݚda#f7]bժOؑ1D{v+ֵ$e%tn5oy4 !&S~m1 4!$;+Su+BW|;,ObTl򢱉UYWfJgd+[1@{|V=,!&@U`26MQ1UfPpg7n\oԬͪ{DLF[ѭx<>BL-,,[8;A\p8FF-["l,ӱEՎaX\^-PSq:\Cu bBL q.;ʉٚ;58|1k{mz6)G[נt +i>Ǔ&e=7t_Gn@g>7$nᵕbff@^ptx6O6I|G) :$/û|w 8ҨUMop3I Gz+2*KM}<򁮥ˢ !bs4l FHHWX*zYǯ'ݫ˙'BvjɴֈJBL)y{ 98v>ks7'ڢm}g.|JY#G>#0rہ]~Qڣ?H/}Sa?&~> Bempy>ϐy*W1Z_{!O4V$X9זMKEJצKLZgSء"NJ9^B:[[Lz݈ϑR:TV$4U].QrWt6g軅!E\~xM.{訯"QkÖɐ[܄su*}kEe{bCS%){gi HUWfߪN,IxpmWv҅¬'OitedVإ @C7+u~HU-FE=l>*)\}&3#=3#]6Oe)Eac.YxF6`ad_Nԩrq]Ǘ223ҟ ԉN `ǫՑiW>o// a䊔5XB]#~WgEϥƝ/y\*3#??OVJ BsO9\K.*|s)$f_icvw_yS,n~D.+OPm֋ҡxR";|8'毝 +*5sLLy+;sgM ?:;h(Ok\C *z2ƅKG"F?**3,oų/6hht/nPXBPQR?wRtXv:p8e_͒6CIENDB`nPvhɵcO=&$ K4I:E[P]4MSeɒŭ[uuu_TCzx`Ӣ6>Z*cÀ.ezwKW)[SSgQz=xqIQA33[~1(fn$um}NJX rGM;ȏgMTP/web/images/Capture1.png000064401651440000012000002425011167500046700165560ustar00darranstaff00003030200010PNG  IHDRx(+sBITOtEXtSoftwaregnome-screenshot> IDATxwxEww_z' ! =!t,& (*"U ( X { C ʖذ\\r{ٛݙٝm+&P@B!PNQb!2@oO!B!=M(>v1B!PU￁3; 3B!!O5M ?B!@QԗWUzǾ(!BUNc" uhJ%M|^jEF>ڳ N,B!Ѓ1wde@ 2MBqN]L: A À\.zgznXLȓ7\BPhЬƤBHHK;O G4BC~8ASҦGYh<6mݥ9am7Ql+J zuoFEL-J$]9bBJNY;Z")\8 w5lظfѺ>n\j|D799h" GpRBH)0ux0lքm2KW` <,vm+ ͈^J-MW٩PҕV[%wʡڅ@7>'hDzNwkӂ~k;_!j:^@GzgҩCbx[ + 0 c5 sYg툚*LC XfsW-Pʏ׷w^c*G+)Qk4zޝ6hu7)+w> mwGK^ Wж«7-}eњ)iug]:"i;.<5+ oߎ,mG_SooQ|桷0Ay&[J(mЩKiON]sׇ-{li;u~L4M[.ܡ3tm9BeϭtOOګv5kG;U)@"8r<|q H4f݅:0/c^-gjW>݊k.o-or]n))YL?rl65" :mqp^w/_[ v,_!ZT*ZoLʼnN[|gFyK=7ΐBnh4'au5LJI~xk%^9b+>>>PTTf C3ڂtlxMǝ[O- 7(}e^8{oYQǷª>m[mw5edcINzl/޻f ';Z q"b?`0t}Ѽ.[mwC>`yk4z`|:֜0=QZyғKכ}]}[R>-'Di_iUYw <4 fBlW.N\?! mjpt7Rohu;X~ħ'.`B6=(nIڵt8R??w ?zevGY)&"(=yF|vj&(q~O%@U=]7|lﹴidB@ESziS̚;ʽYuw,#}ل wB(_q  7dLl7‚|y+B:lVxY=@quǏ2vHLtdDSt-ۜ<~V4eG4z0|ggy76c&lhr@l3i#fhuغf/+]zsVxՓ4ؿ>OjӲwM Ecآ"j޲'ªhJ=@7$+ˌh;./ӧ[E^I n+¿CE h+EScw=z|g?D!BWSV~ŨVT47I|7OdӴkſ؜ $V^ojA?R딞֑Q_<㰡Ju4M(5t/ l \T<7JB!b?unRz+4rG`Д]G҄@%7jזâs&)Hcu][Nyـݪ^Fl Aw!B#8f'3_uԪs:X>VN')~U61_~gfX(Z:wiS{ospJ$mṶVI'8+QBj͡ooSv5jd%jEFIwRo>;r|)~AM'WPBڈ1Ɇzi>D]yCbP !7vJ"[:kD֦&sRn|ہ_#!Ư׵n^( +"n7B!P T* FhFE6 I}˹yL:RP5Ĥq!Bǡ] sA.NȉI1!B̛B!Tf B!WyB!2͎:/!BȋdyfII3B! B!v4B!P(M!B9I+!BsB!T-B!^B!(`0pg(ɔJ[r``YBr=o߾q7rVS/\^#U$Ajn s@2yq\^^;2rH^^R ryQQQaaaaaaqqh>>>>,ˊ?)yAws+܅s M ;sss/\h0aeU EQE9B<_vvZRqyԩS~W;yAhZP8I c Fczzjj혘:uň?^u+Wp_nݤ'Nj5R))ھ}{p9}@Xg~{!!!⬼ŋkAAAR;3cƌ MB8# 4%SBhwПo4@X1r޴)\h ^%S~AjsGA4q} P4mz}*Su~W_@ ܶQ(7'?wg?5)]EtBd2Yqq鳧ՉU|:f34rRP*ڤQҾCԊj5{0m[i6yb(~-,4CtmhRuرgMٳǏ矗,Yҭ[WwA rY5Ii@xAZm^A8# !lj!bG\Zn M" fZ_R4)GFo&rT(e>*Vn}.P3b(3Y}^+5kj]sݸyCyA(iiZRQ-.*ڼ6 @Ąiz׊_5tOpBB`Ȑ!G=[ zuѢm۶nDx!'NNg p+gA`Fi׷o߾Ҩ'q4%sia h5z4+jۤt4ܑѿ <>Ixڵ? }}}e2Y֭y&9w(e^MGǟxܭ?!ar&eR$0%GMDQԸ/?>hwHHHUׇO(iiAr%97U|xRfušA3Fjl?_w|7Ϙo+MSEK Ӻi,E3@mqw+-[h 4jԐexz7Ǎ>3_* 7yR<lje){&!e켼<1$'''00A”/k׮=`Bȍ7aYV.ڬ"b Ⱦ_/?e47fY8>1^V]ꫯ!:uJMM@={Pbxin-iJyA@Kd2*of1$\Q Xw< KyIEŖyhKML dgg) OѪ z _Re.@nz圑cy^ D&Sdeqw '1'P02(T+Jf2 lLF;Pƚ@U͚5n엞  999mתW{-!bSّbHvv8iv###μ{ gZVM,Q%&w0}_iFYW!C3t-fs} "מ?a+VX0aD?_s\geejXއ@ $kܸ1Dd\$ *Z;[.FjB 18]:Q6'c={x Z2eJFFFXXXLL J2""O>pmesrrQϜqMAX# @ @t(u}9" YOij {g6QMrrr8RΝ;S;.)KLK˵OܨQcƌ1yoPR{tdɒ,R!g? 2S (5̓UǶԡ=#n#-Wxp_SG|rJJQ}|}G|Z߳+U@Q2)dZ-"J C'jAm1PbA)sMkZ^T*EQ2J&-|4gA߿;w;*j?ُ"N:RQj/#'6_lj(SO,ۺuVZ] llRi:ꙝ]z? F4 4:vJ//M^<<<:Mi2EiiidrCr0==(k bw옲t|ohҷqӦϐfS) RTJQϟD\Y cPVd8?=)sh֨4E+&["X  <Ԉ^ZR ~>Z_wkпCfg}FT6/Dy%KwC!~Rեk7=䟿 qM4MK3ʒNgee5o޼E}~(yӗ,!5 L^*yb4^/o`Mbeo{']My^_ѣGWb`Qm7䫠e A+.uY2MH LA ׹4%cSUfaw1ZM+SO{9 r% sZ!,4<h@?y^0+Γkwg6QMQ]v\R)k{|d2ymGɯN>zyhH7J2}0!=?]&kݺjH~~~l\!d;6l]8(M|YB2BVVfMcri[#{4B ''; h4ez<ϛkBtnn!]Y ybJ|ޭߎ"@Hn`P~W54Y}c6mZiNBr5jبQCw5kTTTL>hp{,**4@!BBht!B t]!B[=u~^5<^买>hp{2#B#c֣3"Wtqg^)aUޱ}2T?2+7Nu_ 5pzП͎fGGԊifu-DNI‰#ܝ+F4isQyv@欌U(Q'CӫuŸi%U_΍9"*գo?FٓzktNߛ(MW\;_cw>gGvk3xt4 3#=۾ܜ~.?GoV@ѝTÆ~zǃ/(BU+̌g'5W2w]:Sh?9VQU5ֽYryuLظekObݯmwgPIH%n2⥷l=ٯYaĕwj޸vCxC{SlR?y˿ 10ࡺuw0xAZæMмA\ f'+<> 49VS?*WU6|816ji)+0ukǶzI)Vkğ:`u|>k`f>_;[k/4 Ș6,S*յ!Z5nq'^J>{cT: ZTR)4ѭk~Ɨ;OezMڼ醅Gآ^]bmEvBtww4~ {qI֙e޲%?>UmOeL+G\C-o5RXU|jiV[/_ݲq|R~[D+VN-W;؉cO-SdϮ_=e=QkJ%c`q⟣6;Nc~fç~4w_m~Eu%ϟ-3{=yύ(-{1ڵz'b'p'g?T2 3=M;o۝yCy{;h˸_zo);l_>~58YcM~l6cuufl +{āɖV)yװܛuZuæ?[Zm9VHǷU&ܽ w5*y!bG~(wg4Ư׵n^( *Z&7zk8pDn}QK@.^V0҂ɱ|=J1 HCDT|zZq/J@1kVS1˭)/&w蜝urAyWn;RuTXԶ@_VW%3ͳN_ڊ"X'>tj¯g{%~}3S>c*յ\m8xLzVvhUt ͬ]Wf vt)*ʝ%<ȕM ]$?i*Vw[|q)~xӍ5e֙frn5 M۱jrtX#lS[o&u*Z{MY^i.w-r#\ZS˯T߶ kGqfeifxCRoڱU)d2hԕ=t(g][.L9gmZI`''tT(@w94s|}CO6rXEbk/^}̪e͌Toݵ?\K&3g2dZ[ؾcf|nZ1b+wzhI)Yi2GJqrp<]v<錨L홍mWH˶4VV?s7IhV^)lHW IMdY!+Pnͭ#)'>JRW!.h"~9&|8]N;V Ǚh6w¡%_5݆Θ9$rEO K ~{>|L[ 6˿3tYx[Srבrկ0nf^@9{ww##NFڝ|VBh.Rֈ?u~L][57f9+ۼ>&$qjdV2&햑ԩmm_8NviO^f;VN K/:eZi>xU‰V Xm {lL3o9Om׆-,1heƷR ;n{MF]ޯ+^4MdV!fU:RkWSJo {z얣 En;reY17>Ÿ͎f׊oԱӛϑݯ,BwHho_!>.bu(va&'nT>`¢mR$ɉ~ 5MD?c-]=qn+gVk[u~Xk/"Xw"[9յY6h\C E+נA[_ڸ-u)][B[ݍ\<βmi-VFy̅+z@YRT߰6|2@ qdYnV֑8^+,R=Dz;:ĺ;{1i7 ^^bQ}OTMuw~Ba޿3#Vdx 1z__N wwFL.~f"Cpo®&7@-5dhRfiwdFw!By0B!hz!BaG!BU F!BUl'D!B΁B!P&B!eN_B!@DiLGcn Bdx0(ދB!TLo\~ѻwZ0LPppƚgg Bݝ;} A  B 322+W*"kEJ w4sT`D!۵K߰!< !`) }QϏ4h^G5 VHBXkն 49}iEGyCǎJrVH  5;#»K3j&(J/02(h +$B}xwb[CCGÎ&ѰB"Gw!oE9s7bbډ霛>E"T1UHa}C^f#J9B5 !9 '޾ublE.gDcQaT D^o@`YQn,*,`}C5\ ˊ=[9SzG{?!O),3\l z*GPO+,{OkEF<~Ƶ:/,ȷ3,ksXx12{/ŋYxY3+T+*T!Re.aʚuRΝ9G F ZٮNVK]\Z_!g ٬UkAiiFQTu::'eYЫ}dXtrGfؒ>%24IA&?HFz?-E?Fw9Yf.^ܹXeYv޼/5s欙3-۩͖1B|f٪o.вaLDݤyJlR?,88A9__b;zQ [N^mɤ'Fժ~QC2a:i6tD _?O V*}ZzRp݈:鄸Z Z1%BGȋj)s0R4`|DeY?q";.|VЧ ܖgM'N$~~$bV?OiyΏyJq_5fޏϯT[&<ݴS^Xtگ3O6 i~};}ug>8rܜO.oo}ܺOl;xqu~LZ~aĸ7o~QCr֚9O m֮C&v~lxhYd8snp;ang4}|ݍ_JUl>iˆa?0BGGmz]vR jQHhXLӐ6#V፷Iu4Z:Qk.ݨv+=)Fu75#tٲKg̘n+ ;t5 wBJ!7Qnf:\}I*Աfb }73t#T!:4rs,g.mퟳgsgOHl}tmYom҇%Uŧ4w5'6\FnG0fFHO=bf_͟~Ysx囁 ֏hԕ=tk4-CaUR|PY GCrŦOӧUnw;R!+V(!?sW rri)[Gq:4FP7Tbta4,3X/{CeT VsRdE^Xy1efJ_7B͂'՘VO i1@}&wwG>΍R3;96zc²BgڴP}]#Me.r=m qаӲ93vh$+g'o&PNvpV rMNjco?E:h~fvPfNJ;M![,~KU9bGNKz'5y/֛nc/9bliR_~{eҌG?gpD9MG8@?zlMZ?BIJp֙EU;y5-l@Fzz[15͂MRnvmX7sh;O""<"Bx~8xphMBt%咭k4Bxhxn)_FsZiw\ W IDAT~/J 82oMzx2"B!װw׹|衇l]ixހ#N6Nd!+j+r##/B!(Fݺu+**jn;)5/u"ok4Ϟ=k9B!*92ޭZYܝgl4/>ܪB!qYwBro~3g|XUl|-.Nbb4}{}sn5mtOG[&?WUY"zcV_J^ꯟEli.ַkݸz`MYaj1hQRL11[>W'Z{o-G73@\͒hdu~˭K;dS+w{߿8~^>@x7h2lF4SGO|;^h2ͣb-~tʇ;:nҮL2wMT\7}X/Vfٸuu/KU)1M%(O90yGT!O(}Vd+ ~G ssG3:qSкY:u{h;㘁kԪCX1sF uX٪J27m7Gz! -פR$&&‡ꩱ 6~x4/0[xˆNmZ=olm^m٢W{vMN~ow}K=kץ_m Oa *A0j9H ,N}Wno(z罾<™)OO'm0d=4+@_iH,[dѓ_vc9R>/~|.m<[ =<:WsQ=r޽}_GhvM ~cwn|2c٪twwO[jZfoqQ<1r_^Rx'3^=xg'~祜mЅ[YhVym\7%YK^~ɖKY5Zs>l7jCv9IlvDNq+2WrMv|h5pG2C'pckG2n>e`;y/&}t<{,]GwPnjhl-h&-rS"U7iO/)Ee0ci5t_5l4|sΙ{Ϸv1x^ʍap$++,Vr^8#!t=bk}<8g+ )6AWcGweTP[Gc#ј5*q:*"vڧGZ{R6I7IKKKF}h(bZnk #_5̬,AIJ%a\ttm ] a`{0 Kf+}遞[ۥ_o'1l6S˓Mѣ!w:eAiy908vy<zv]g;LG 0WFC&_}+Yi^8J`S:dn{K?Gv]uO*,=AXCs,/cRu` {/3evg^JQs" QY7v42EAu(+jcלҔ_;DMQǗM9_CEX_r̗U5 :-&t]B^I p?}λ)8V ¢*{~3GaP"v*:K][OXSOe?}(zu{Խk7{'ItE==}izǨʴ(5%} LUPVu]]WxиDW \ϥB'j jʤeƪ.>U6xɭKwS٢\8Mtw{ܐU+>/_SYob-0obwf*S$:\KwhYjqRb5>-~gF=Ȭg3vֳon+Cj8%?W<?ޡ?1cWsT{#nl#"'40KE#`J:5v{5` ca!V$o~Tu_Vգd7="5ccCÊ:jeصS/B0\4ZGC}[ oY:V޺:\݋LM6DwɔKD% .q)K]"|Mű |2\GӾBEa&a{o7D4DdF;:FgD5FdG MvwÞ#gWl<)'̾Q+xϑ> kڄbW{J{J_2]'N׿?X{j_ڏ*rk=Tޑq]Vfѧ:wWCY{5:;hȌ=Ǚ\#T ,v`㜁Oz;5?o}дs`ۈG-m% ?s"+mF΅N~k h5Nz-U\y䔍냳+ Iq6K* o  Kk%eeyyun| SbB,\ߤ-ӧʊd^pjjjҖ@ ~S救Mv"72XlfQL6㵨(x,@F55ժ*g5F#jjjD"QNNT``[2@ ?Rv4m]$z8Qn aaa!fݻwI@ $Aʎ٬g񓣩,m@ )OB @ G@ C99_u{hxk=ijj*//ojjSII@ ())͉@ Yu{a<?e&i g`>mT" D$x<.//'H @ h֗@ wefj; cNO4EEpA&t؜ @ $H,JsܨYpԆꢴ8P`XBiا:VzgjJvQP#9@H);ZFR9e:ג^m"6Z/قײbmVO6i z2AHdujcC ~78aюH{s&Na3b?tJ )&XŢ|mF/{r!uI:,VQoѭė?I_!w_y&!8byn9+F Q&%ז8zHk9hCGO;i9ŲGWN꽆 3f'^k}NO`1*;8íip&7*f"He5i[aC*,tEfV;qmsee0q;C|GZZtڛ?qŒIPH7y_,,;l+PEf |K`/۬lJfZ'㰦2լ1O"-cmoc@W %h{abX-M&9lnt@H;dX ?Λνf R{1wnGK}eR#wܽ7 nd}~7I{WmiAW5˹ҟ,,zOOv-Yܐ]'R/;;ǯ忦I|~#8mw4r lhMdb@Ayk{u{I{%lX,Vr^8#!t=EΔ6)K*RSWb%a lΦI7#/fߥR1ULN~M+WMsN綯l~ꃤp6p3\ 3Eʑ'w 1kTോtTD:bF6"? NSUkOʆ;i&iiii5#ԨE[+bvma ƙ21(I)u]$rt ! L@3z.LCJ3B役wzv"@L&͔$:H=^D8#Q>rK-5Xp^$ D30Uor<~s=GX.zMe`0|||s'/ Jѐűo vxõ1MY/ !h2gOVe :&qOBNs#yH\p>V0ˠX?xw߃.@s|NՑCVeluԵ"xӉ檂ɪ]wwjkJkyܢ6>Xј[[CQPqK㋊n{}݈Awa%|;X.T/{W bG̣ nO%Wt~! );"#V@9lu)\dpe= 3JR͝؃^tD ޭ:xf'gܣSV@'07"z;ÍhR40mCðէ?)5zH4T{fc!oNFay^x  ϲ8ĜL=? IHd~\5A/Ù 4-bq5p~7Cbfen@NDX'`4z QF3c IDAT>?RpI8=vߢSl]W־DW \ϥ=^!] "=⾨?f[Աa߰4cQ#lLpeyj39."ldkp-+T)*詂adUUra;sm app/qZw7az:L~*>DٻnrFE<ɵs9Is7|LQ5y}SzD\ϢXqjkR=!V }F_ f!~[$:\KwED,O\sWPFZS퍸ժE-}o6=D7*40KE#`J:5Cb7/|R9gn_S+;ޤV9d@^f hSա60w|30А(h'[LȠ}،ydDɫ#3Xg*`[d#~o#fMrzrEGta.ĄC=EMr껛{"9{Pk^RWLspW 鏿挵v*GT C1irw4[*¸ZѣnbYgCHZ^5\Wf@0V4R~7@NO>8 4ώgCt~Е$ΞLVRVVWǬ05l*a%~j}Ke 11q鲲'f2.\D7/% yeiȍ c,6fcT:x-* O/wM3Ky2QMM@FeggD99QA2n@jw ,{mm۵4 t+}hK"&b1xV=*jӧ{F0[$]z~X,Vf=oAo^ 7ءgAE07; U""CMR}f=p-~^^ ^' 3r/t \`mVRkgnziD"04?fҵOri8%ߵGc'i[h$>e(̜>7,Y`@t$Rh2l6.dp )֔Skw(~H}d^g;~zm3ezY ݞGHgUD?,UBsz-z>QdQNr6)ZE-;kPѓ<ί&9275;c!( Y4mwo0f)B4\2za6bKGYPlfmsee0q;C|GZZtڛAX~%(Ӽ/Lb (*˩0rI]o޲) kÚVܝV/y io{2X`,D+ r$l?l*5دȩgsk;BM&Œ8)nVLζrvRn[le]JBΧy[$h7lHf~#XY䔍yl*{q{Nߪm1G76>l߄DXyO>ê܋.>{\m/%̇rh5q#7FXpSo477WD(A`gj^󷇪RӮo˥R1E{LN~8q&U^e7^9_'Y9S{'nX5 h_xco:8~2vS>|PNJL#2{;SGLAϱ~Ra8nu/@R&2*w;uUo[틿*a *4;v{0[<*7](妮0 7s$%m;mQEuO̷͂76.Ak&E% 'wQOOi12-x8irԵ=E xĊRS__TtF $ս +cYrzqػ ];tVVp{b4-Eh"m'hJMP&~RߎhCE|/_.OjUҁhL[{3aӟAGGPIu#Ӵ1_Nν|zk-60)gY/+`ߝQ[ j],LD4Q:|ǯ*ab4 5oӎLzks\cX?̴("? p_Bpb hٟ{(poFjkj8fE D<;pI88s_\ j9 [X8혔 y^()#$\IKkP;$cCc01H-,1TF!tjk?嵫u,%(`_vX-X&іiE˕Pd{T0l*AڰNOќkK;VG%Ň{@.]-N$S߿:eie˻wJt:*^6Q/oK4jYjqR\vVQUǞw^|iSE& vw{ܐU+>/_Sஈ~x9bЩF5Zr'F(K[#@@Vi`λ0Z}uj׳y hz1 L+ c7?UX99%m&3r!aEeک!X,!GY":FM'lI*ᵏ5W[wö3q][aS_h@iWNVڰS0= v7HErGshHNfctNdPљۀ&?c~ Fn9cǸG#:6ԃOG`;vzG;ADA;+(Pv9 l_bW{J{J_2]_[vSNвC#/na]r;mmkр!GrtN$hiB'5ʄYLqĖ37 )l]]nOgG!N^??',,5up1+LMJ9+;kɾ!EZpxHLL>}h&yOOtc@RI[MޚWFޚ6ى`16bcl6F3lעIzrG<㱔6VUi=r}6wDMM-;;H$ɉN1\tKF AʎK]4-$!~6,,,222$yu{ @ $H20t5 ~r455@ 7#@@ _h"@ ~R:;ns{ /;.JHSSSyyySS؜JJJAIIIlN@ ۃ 80x)3apL{pl6JE!BH$MMMNj rd``a@@ʎf}Yx!+*`-V0A#\T4 Gd2ؘNͩQPP@H͢8'ύ,Gm.J3q U j)}c~d?5@ YKeD.+SVs-)$!~68[PZ]m 2VOr3݂ё;hiiq~A 툴:g6!C0bUZG›$ ]lv]a0XK4]lŕu4n]Z8m^` El )BD3Y$&,Ul?3Z-lzPuջG/Zcg}u__#A-yqunm qI^gmӺv>f_W\ TSE{}tcb%q47-C%Cb ? FEI>މWI-$waXz2uk&vQ֯p? -2]|=AϹ\~l(y댊|F_C?ZB CxX$7kϗ~1շ!ƪx[j- M/[XRhOt `zrh8%ߵG͗DR 32>7kBC 틔M&f%0l* &c}g3453b3*o~i\- lFÇ504J1ՅvFt طÙ h)vlakK㮾 PNןF,~:3DBhַod+1kb2uK*RSWb%,KwR%<,Vr^8#!t=bk^&T'0ؠaΗ Y :u{I{%lV0nYG;:uC*M w'n?[̦ʫ 9'\yp~sW6 p?qARۉm_rAf֋#+Nn[-BۯNƎ>wSs)LNr i0o쪰zŨ(bPa>T^0-')'U?^!/ys8YսzzNQiQ/G irԵmN+'}+sKuvk(*N}i|QmO1T.yg9˅a6toBl@!:Di(~!-Rv4LKbGSˆ@'pl cJ5S9 >QhPʡ"N>y҉/g'I!3mʂԆ:aOzSu[MC%׍Lr|9eC$eg)0pI'CH* #>?10,k#Sb?.+U2|+OcG*}}ؠr8RT:wÙk g՞Ew1:NN[O,>;ԺXHб_N]->?,´PcRre;5GJN}6̾-%t}q*(ٺ[&n~cɯj \cR *T}()#$\IKy@;$cCc01H-,1TF!H6g2yGU;բ*rwQmkdv&?Й36ZpcEKI9 Vv.C[.WCRTSð&j9=%:kK;VG%Ň{q,,ePoQ3Dc'Sh@A_xYr\EdjW!B'rYup˗6Ud6"0]*z,:_-RD֤eƪqsjlOɡr[-wㆬZ0})Uu9,K+s-\-J{fh"_]NKi38^mSݽ=2piaԢw/ >,a+1  3y @1y b%aLGU K8/Aުz2|.VQ+_Ʈ=}A ~G:se7*vsણ+mGN[Q'/]?*N *}|5*4wl?3z۩+pځsF?u< ֻ}fE {wt5_GwϮ~/X4(pp80HΉmPk4\Wf@ IDAT0V4R~!唍냳+ Iq6K8D "=`ai /ѭYaj:TYYKeP=)R]@bbee=3 .xz%O>{I[MޚWFޚ6ى`16bcl6F3lעIzrG<㱔6VU?W\_6:QSS&rrW #77ݒJj#@ ?Rv4m]$z8Qn aaa!ɺݻwI\.m e`6kj@hjj:;;K[@ oFʓ@ *D @߿ 2,:d)5 `ֿ3~UM[iۮa0 % d 6nJGź d7`E- fmn6/unǔ1C̬"='f6N }hh %Tm[%4/VVW%߸<_'&:[dz }sYW?O{'̌'M$l>q0GvkLn<{>kXi+j*o[zZIk|ftWG6Xy-*KI_>׺ /o2v1X*6m4`Pr74M"HKR i-0VX;o5Hj0sBFgg$K6Figg[RR@/L6Id%ӂ5d0煰G:Xn+ezY ,V27A|{ZyM;boٷwo`6n3HOϼF `b;!gLǁz6}uaQ?]=63t*{<ءe~#*u[_x1%{kH c79,dqF1"Ml XԢ5=ZNf"He59 =DjR0@aMei t!;_'9275;cB.%W"nne7wu=dr t\vV".hH7z LGEk/8zHktyI4RZOk1_#Dbv?QVVs0qH)$lnư! ^:"d3L̝$\$a\XsiCt @fzd[߀Sge?Sx)۬lJfZ'㰦2,њ%aSH~ۘPvcI$%^a#a\fSfI~}-FN=+1◡=lok2dX ?bΛ&Άi-7B9+~wHۢ/p>'ܹ=.>y랹bӂO46,S w̷12j싅u M,KwR%<,Vr^8#!t=3dee7Px$h|n5X[2^Yqrj[+\&$Qg`ޘ]>ysgl}r7*8*Z3]MgZBEةwn9ptz=+~nʦ'>H w;M\z(SYgٴRyaV.L@RH o605q>Z{dPK-rd)0M:\ J 'AZHNSUkOʆ;i&iiii5#ԨE[+bvma ƙ21(I)uձ* c\-䢣;mjP? ӐdҌgyo.蹵]@d ~^&4G4%HlXa`: oH9 JG_x^Hw:TʅIH1n] rxz-GǎLfA?B~^\3)*AXs8YսzzNQiQgX49yonZ%AQ}'1Tg<ԗt0IuJw\^nA&ĎGܞiKF@L(@gs|M{{{ae`X;F4Kܜi5i'r7/3r:}«cbZ\:928>h}}gt8OraWPʡ"N>y҉/g'IT/ |RegjCðէ?):-Fig9fc!8F2Fc}iI(!$gPY\|_I_ۣy'{-'2'{|wgVZ 3UJO;2%lrr8Y\]3,P  `\O_$_ ?"INX!6.zW>%os\cX?̴("?p +#%C:u!/+7xBmh )׷~ ( ʲ(L 9&%W0%*`$N/Te5[u-z,UBvLJAVJ2B%!۠Aja2ڠh~^vvv bL1hu&&د->&39ՂsX-X&we٨q_.] "=\a /䵫u,%(`Zg|(2U=z`6DUܠCm?Csm app/q(Cw(tj]Δ\WU+i[̵x[t =^IV"tۃm#rsar|u9Feie˻w[+K/m#n̳c)w@1zD\ϢXqj:EkVS:*RRBi"dEXsܷ :i=nȪxCїY_! &fu)K?/;lt*A͹ܣ=?zܫm׉eikuw* y@Q˯ĬS3\B̼o4Xj=ǘo&xX1ɛU*,))Uh3)d\$>V=6?V];{@ ~ :u$ۏuǧY/b)pK ]k:((m8eU9=uZqS^b}e%W-?=EX+sG}t8 1X'kۺs |mk֯`mg⾵_iWNVڰS0= v7HErGshHNfctNdP-9F&XP?3Wsȫ]u}M^1~Ǻ8dh ^ƪ\G!֓f.zXBa?3z۩rp yNucזh ;{nb Q*>+ϊ3!PGQ;vzG;NR\Ğjv5sՃ![,bSj/Q)tboPQY9$G00h K3lMM9fpA}c}0+eSw!H ?s"iE*G΅N~k h5Nz-U\goRNO>8 4ώgCt~E! ',,5up1+LMJ9+;kɾ%غn&'.QZpxHLL>}a&yOts򧭰/j jjjҖ@ ~S救Mv"72XlfQL6㵨(x,坁4jjUUOy!khR XUG+u1yEM$D{ Fnn㖌 }ya[a_Ԍ@ oDʎK]4-$u^l IV޽!<|(ˑҩ/jF 7"eGSl3Հtvv @ ̏ @ A&@  6sqmaocDe MMMMMMbs*))%%%9@tUG8 >x<0 LX7/j/RQh/H$&@܄fH$ӆ@ BIG,JH0@`uzҠ.*ԺHd2ؘNͩQPP@H,JsܨYnԆꢴ8PAX,VM4S 35%q@ Ѭ%w2")й g{b$m!?/-(y-.6idjPO'SnA +} 4vXB8W`ю'g3,:d)5 \`,2"=\G[[D } 9yUӶ~`04i*ظ+6i$!6$pۼjoRsnPE8hРu4?7QWb34Ҫ8;pMҴ$rγjUv>f_W\ Tskkk߮XoXI6. tE K_$t_.d2l&]nsG!C?ܜcqF?{Y 244kw_w!'u*{ c˃F*]^JD_ȿc*qc/ (pCaG:Y~XԢAÇXw~0vm=bFXX41j3K'q[F:NtΟF،tڕ[}Qf&H*d!eAVW~\w? ldf S r5qXs}oۆqa25t7@ݗsL$1)':$$ `QBt=GͅZr.*gTI&&BK: &bH: 0r*iRmQc8( Y~_ϕ=*,z/^YwNmٻw k,)g若 vqϢ$OGf>a$ J JQv|p\2@W?Y3Դ8 ,4x8\n꘣IwGJ2k+(MIψ5p0v䦩XLeD{+f䔭 }\<>'>H:s;Wn=x"SWJ2 DhTXH #Uw^a%Ռ&N{R瑫Wqb'eK{$iI4=fbP|񃿳E!&%e>ֽě1"ww%]d?u!itE'y/.B ~Ydd%NKL}!GEGFʋnOnڞ{p$Mn~qVAYE039}QrB}>,8Zvo/'@?EMN>3A 'b @ 'q[ ,RPWjչO#d5?]j Q3M59~\$LUGhQY7vvf0dy!ȩO `1ѴSPrׁgYյ j8o՟&Jq&4C2Q5O/];ࠊ<&RoRܔm  .R 2 rZ?Q^TF L]3в,\=:ߟZ^p{~N_ B#/.h2_3GS0F?.z'fSm'[23 oo`tqεSB_#m媂F:a+ϔ):NkM#EWywY&%_VɈ+X׌j rƫ:87|R_)y 聇'2b_o.}ο?{aPy[+>;89n;+|m۷|Z+ 3IF`z}P7)%{β֚:q8CJjsVz*9x1+3"tÂD= }ps9&5mjV%$gԙ*2~"tĝٍ0BG_f%5Ht%^wtƄhSWo]ʛ  P5f);Ł9o &0ZCŕkԌf`茱̋ ךf%$IT}D"H$o! IDAT[ՏL7Ehzae=8)2%MΪs ?:o']+Cy154c/G >WvZ&^6%MOZ]hm~N;dc {4{y{ikbq"_ՑŨ @Y7H\TUBcxAe ZBC=)i%4>*  v8w2qV2Ȣ(ij99&VUvD!0I0!" x>ey(qҢRCf 'RHx;;w*NQII^NN]g-\jnn* $̅{2=v-'% 22y0̋/BMM5搽'9) NZ9ueǏbӌ.9GS`\[[,VРA@wmԲDEd@ ~9mŃSj嵢LB|?222$yy^lll:$@ %Mڳ?ڮBߛ@ ⇥K.B @ G@ ]蒩sslyqmioc얒F_ innhnn(V@ DUۃtpaYRҭ6X Z)" %%r/%6]QQA" D!0P%$mQ1/60A#ܔ0d2ĄN(((@HBt4R)t(53B|'X,4U^)!=X&@ NK:u|U1L|:R~$!͈I$RgФVp̻TH5dZcL*݂c @t:xg_v \3:k0 K4}eyB8 4nZ6kVX Qwy`qg}V-s1{L=#v$5Wj#h@')$~&݇%c?f#?]dXL\ISߘǹ̔k'u(!>'^> I~k.jh6{jkPu[$6udcP* xfGO cMQ#߃ʧ:qXVi9iK_!֧'*;)u4`4ϲ-"HJR!0D TSo5ْ1.C~|FFa]xh/0dL&-u[ENVA|ĸYܢVf`qnTxCPB!_S lF͑s1708J16^CH6x0[Vk(߳Hࡣ[:j!^K\ l-$ވ&B̲ w91gq uߛկ{yqq>-:WIh)6u~aypUEUw#l`U{ge[M׼cw9&y~Q""|(lH>f?/R#4[\`e#,rlf-Nlm'l:sq#mlF:J~ϭ>q(Ksaw$K]"~.Qt=bMQOk7mX y[Di]Ú˵l}6~  k794c"I9 &!Ks$?l. Hjs_B ~`X,U`Hݬ9Ϲ״YEos@$l 2!L+)2JLN{xܵEqW_Pr(N#Wiq]b1 Rvo9m{l?b蛢7/pI>{/M8x~紋{|ԥ-(_e{Z;7{ԗC7wa'ydNJO xVճ{bF*))u=fC| DId<}笈\[k%W7O7=U{su2꘣IwGhϡPx$xlMS*K"n9W̦)[sK_ypa-!|N^}ur^z,E򑥧6=pOC2uq?}g^+61ͺl7SFSZ J\}a.9)讷yէdbEx<[w.mY+jz]n&?;[XbbP\cEjƈZMO=t5 hxlHZ8];IK<6˸_.h2lI&hr>b#WL1Qվu~seðcGwRr0륥vb7iv& đ"}QJB#=ǀ#ݴ=HZ)l}GXb=t)sJޗrREd8?V +'~A7+nݬ0OluMoE]p;0)gC6£W9Y[ g9єJ5UwcnWs!Z%T/Qd݌fk oy E9o9@w/P<%k0}qHp^\`W,I8Y0]tW_3iҋ.<4%@P(&q4˨ {H0l8E䎦+ |Y%#pF1= + \Y_x[na? I/sqnTf4)#2\3}"3f’m袎f4?fQ =ZfQ8 WW,uņ{}hP9OK#_oے6љVL;$NLKřa)uc3Q:T~sX"#MǷ IPWo}ǒeSz$]a$3r1vaZR1Z9 \AOS_k:ndQ蔢%znԙv =cxDba-qHqMJ b9,2.i ~fmޛtjr8^'ེ{8V՚>Ł9o 5T\9FͨX͋Yg tXńkMk$?XRxQ8)$}.@[+UYVO .hsk]. O9\89#?\] %/+[UY8c3lxgg-.9jg yF-'v@8:yƫb֍0>ZʋAE{~4~sc,qq+vK WԊݣM֜{a:n>r:w++_YGm0,hf6ÌGEDsH>gLoƖ$ڣ G،Xsb .!bu'W )4L r&e rmSU [޻?fu:GV*mGm9`в-WqѶDn'a6yрGrN& x]Cжw_Y%-_Z3'֪ʎh sf`I0!" x>ey(qҢRC.D ;oec$/'!齳s9\B̅{2="$0?ԍV头 -'% 227`2/^n K?6UQS&搽'9) c,6fcT:x%,Gӛ)]2A0$Y[[A@3qσcSjjjt: Nn돨G~:@ :.0z.>N?h׊0yZ,0unee!Ɏz8WC gK:x ߵg; D]i9ε]]];,@ mK?+S@ (D@L#t~αřG[JWD`꼹YluEEE---EEE@ :w43o2ҕgkKIg`2k]X0>4~"9hRD"ikkkiiIʈfWTTH$@ (:l(R LJAk4_%᦬U3#0uN&MLLt8@ _I';EqN~sXSgV!: sr{'2 +g~o.jH2@ dG7&Ք^G9g J^yɴ2Lkl5\[Pv.D"Qv \c~(sy v}V߿'Wd0YZ6!Coe0o=Ҵ@  VeK_|{9h P#9P~|~#-b0`QKfyYy +hOWNlmi1gc!UϬg89 5>gDB$?GںqheaI7|Kk.MU.J@W{þzUs~;}{Dh%pOβvʹ*zY8Fx^4s ,;ptrzbMgү}h_ȹD +h߻wgϞkp,$;Ir> qYѝl1sy)a'=uSad}wdze0pa돽|HF9"4[a[9YE$CIQJ309&VjÏ8ϖĤƏqS3>6 C{@ nѴOLL\޿+11Q L&ͤKa2٢m8^XUd͇Mő5G6>T~+dDzaM޽sBxE(4ǯ˩/#lF6Fz} - W!y&)8bSPUT9&*u`Ce9Zrlg^* jD;GnpXԢAÇXw~$xD"1PDG;N}6e>AGO/ HbRNtIH@R { 3 '9\n @ [bL`0Y,CmnVrKN{xܵEqW_Pr(ZSnf PT&trZ|%EFi`IJW=L1\|Mћiw$!!S͆;,*3(y|}-IlY<[M ^[@umJzF|Ԥ;xp[U|:hҽĝ-67W-wѣml صtrx%78v/^,5ʃ ;=o isC Wn=x"SW ra G23شMS*K"n9W̦)[=~®]rS3RU~QYRhjIj81n5gVvڣ<\Lz2֥m3kEBzZM+Ġg CL J|{7 cD^-[獵J7bPI +zG={}iGߦv΋fPPPllh/Z"^F4$oyoߟ*-v& "vniJ EBюN &mRµeӵRg-S" \uο'iI҇z&H$_ š5AKZQ#"gʍJr|Ï+ޟvpRޖB|FXd4r$GWFow tg۔{_{fζ%SZ^lt#,f0Z$>ʯeP  /~e<*Ǝ^Fw,2[. H<7 jֈ3gڎQ>:vdҾk q獎4[Q5O/];ࠊ<3RoRܔ>@uJJn 9ӟ(/*{y#a&տ.h]fzqP]͘O-/=a?/i]>.◥Ws|Ma2ya0Y,MM7.L^F)K :ҋK2Y \`js~}&, ."z՗ >R+l_-6N09)i:GFq`,O 5jFm՗; IDATX͋Yg tXńkMk}E7 cU*,|9%B" =ZUuho ^MTf gչm]ua+x7:!fqnqO2_zOע2KTp=.t~ut3.:w+v.OðA[MJOo*$ ǽ?zk[OY|b-uyI;7cw'loB"9E{,^}d4opc#OmFlĢa0.%+ MVhwg]r#BMX a[ԄQmھ ڌa+/-jڸ)qGP*ӍtG콴Op\[Yf^'nt ^K=ZU `J&쪝_=.çކq'?t,oJB ;w*NQII^NN]gwp@23 :*EL&ŋ~~]ƀ1KX&Z%PSSl/JMu d-INd l6d1^i1Kf iFz' A0QQ| A6::WSS&L1ܮ~KvqZVE7]Ӊ@ DɎ0z.>N?h׊0 щL[YYeddHN^lllߛkw:@ ڍNv4kv smmmWW6 @ W]V#mC`@ D#/S@ (]Ѽslyqmioc얒FL777WTT477(V@ DUۃtpaYRҭ`8K^:BD:'HZZZRRfz슊 d``}D !]l(n+R @`UZnH]s2lbbBV(((".h9S.~;jcMQjgLXvY,M4U^)!=X&@ NK:u|U1L|:R~hDuΫ6R]#@6i 2U-(<2{@ ڑ.z#Eoe3b?ZSWy<`/ԒY^vv^J~0Gڎv]tJ;X"DbGv{}@It7;pϪWLɛ=:˶5cĉ5:O>Wj#h@')$~&݇ȹD +]dXL檋?Q*񫛹˸~DV#8BeTZw9G6mătu>ԍX,V+GL3UsՄ^ӆ["sV.!U 6Ơ"STvs/xp!C3`M>+zϓݑӗ+R$oʶxsJ M,[(%~4 c~aP*O˩?slILj!?[>#c.~ͫ+YtIGdt ?L&[lc ľ[z|6s^]`,5]BHh)6u˿#TU./qZ';٨uWR1~%kCaG:#,wᖶ3WFsD1q% !!{ڜ:Y"9oo ^6*fVY:q4q¦3w 7fӮǍ470qGRHxSBQs]xXnT;"jMb\_e^v*S3Ln~s)}i0DrLB%DH#(\?ؿ)rke8K: &bH uΎ:[ݴC +>%3郘k7O0w\Y-ιsfCMS&J d̠+Z:9h;1G%t Hl8W}shf$N[RW9HIKY6%scpqT,R"轈[ier҆>W\q~KHW$xUrGڼR :RSYAb)SPr?bUd1.ޫWDH$Rɻ]^JB3f?hVI~68]tܥU;\ sg}tu.SPw1*M\/ӛ0E'ʞSz~ވAIoKzZvU^V\]ٰ?| vk(I2.◥K: |4?69Edc@VɈ)P7GZ|{kGBt<:@7t V)}Sh(i(&%_1ȫ1}Yhd#qR8Mă<bҎLvJ c5U> j+'vK aaU89+|˘M~Yn=Hy$oRZVIѫ [t&|USF ciu{ ,WR湲Dna? I/sqnTfW 4)#2\!)2m01H),8߶u :8GcSSH8ԅbT8R)l B~9ʠf%msH?󏕒QYwG = gu?9Sz$]a$3r1v7:3iEKSh Wz*}9rn'ʙbٱ.ZWGePɷZiӲaz&ZSZBƠK]-wzh7[/ 0 }9⚚r Y6xe\޽VAt Q^2T'.ʹ`a P>+l_ўb5yQ6@A0ZCŕkԌyd-X͋Yg tXńkMk$?XRxQ8)$}.@[+UYVO .MΪsɕEnz3NEUTM5[M5\b.pr,ed&::[NIi-A)fgx(m;U/P#h:}vF[aXx͠񚜊]̀Oo*$ ǽ}=ni6Nϑo 1gޤ9 ko yN%v}̠ >8ĺq[OxvW`;\T>lu4MUE٘ϟ1v-6]|lщv`݌ \*LPO_{0Y)p.S=N&$!nt ^K=ZU `dkU2 &4DdW%"?^O Y;O^YBߔ@ $wTFζG".5775P93;sj]ˤP8G3!!CFFCɼx_׸1D?jjjmE>ɉLaf16ҙl6+QW8-f8L`SB.DAy8u3-lvZvSwW`1%/j] UcV n ӫl u,#NQQ+5T`u4 k?~LC!trDbb<^e܋vdXV2uϜ޼Թ`+ \~xXW戢 ##ߏ_=Ov,);HIu?=uSoN*h]e ,O:9pzҚ9&VLtգTSQ ʿՌgKbRǸ p!c\@w#&f3~Lvza3jl;|W.A@ cx<@Mxy݆h+y&)pŦsssEBIH>6zk-:hk!'_'I#ܠ Hl{86\iȢmYck9dO I (wᖶ3WF 7dwu_ |_E+,u[*{[dXV9ML6҉[%nK([[ 32pHNsO7lIBM횐H$~_*Mw EߋH7.Lv֧@ށQkZr-[28ϰPavOK9&`(!:Gp}FN9[-A :g~DEp'=<ڢ/nn(|qcW(p n 1;hkD#lhپQs4^NPڊkS3&m 1 A'9@/0ETjav%lOgcM`Ñ/=;2;W9HIKY6%%7U,/e88W覩XLeD{+f䔭 }\<>'>H:9pփg/,=yf8`2'n9E*#Uw^a%Ռv풳(,#W>%D-uio$ML /~w(?Ġ$Ǻx0Fղnz ?ENWz.0M2.◥SL&-q͝I%|rh߽qԒ׸)={}w1gOfQӢLub `9ˑ~\TywN~ a>CAOH7`o=n?lOhT˶Ps%4U,+ZyT֍/(6Y^et41SGX`46u YV~eu->RynnUKR+Li#d@Ox@1n+dTv r8"㷄F7)uWn6uJJn 9ӟ(/*{y#a&տ.h]fzqP] O-/=v}_Ξ9mhV^^W{' 6 <":#m媂F:a+ϔ):N1)'զUm&-&GLX"b "Ҁ`}[iG&7!w, L-Gcd\=V}~Jqwnж}7i0n=ڸpx U||\g, Hx`cUha 8uORy%a+8 [OXZ\`rAʬV? &eD&KJ=!2#4~\,`bRXb6C ځ_bz{ؖ,lYqyqp=I #7& Wco:O&jMe &3t:+uOUϨ3iUdjͻ#.} b[#{ ]DEO*1i Xvqᮋ,j|_j]jqˆ 3LO7њ4Kn*n'ʙٍ̝0BG_f%Ht%^'U)|Ju\95f Q6@AFkrpQE0}b,21y1ZZ/7 cU*,ጨVp2-@[+UYVLMtvDV-.cA[75 V!xúeV&sS4{stSyG\eKh:}vF[aXx͠#KLL@apgH#du} ×K^%y *&#&yt3b# i>w)Y IFD(8粞*A@vc#OߝqGX7M%|Ɯ%Jk^ (:}57(iTkJ2"ػ3ƮFϑʏ1j͸Y +ǃ44{EVkN=N&.m6@ލN^d |i[*;l_RV$]uې5αΚį ܩx+;E%%y99u*gfg.ܓ]At"5U$8)iHHHPd2/^n %l{|뭃QSSl/ $'2bcl6lJgD]Y㴘%htz34##eQk94h_55l"(++jEd8-jꛮۿmA ON';V`S^B:D"Hdjj}6k-k&,9W=-ˑyv_'77c n8O_׍p::?5+,:|߻SCB? ZcQ+|zkv|x»{'drcjXgl7yQg4ϲ$F"0,?y 29&VLtգTS<.gc*JSTL(rTRQn(DJpr$Ǣ"!7mV#׏EKEcj{?ƎifC<</6CU/TʄTPe|c嬶-evgR3hB!|PNSd ]O}v]iA3|VQVQ'Wrdìl,{HyRo]pΥf4%¥󬬬SJ\Xo55a-m~LEF^{9 %LT3fHS@ l=[~#bdujD[|7ůU#"cU8gq2+RI}YH/ąy=m%2I~hbAή6%]?4|@ oY;f2Bg[7%lxL~dߟϩY̿2~+f!ȘN܏׏l2{Yw+7VyG>k?nͪ7Y̴V& !ӧ/Lؑd5gu.=Uڐ \ )sp]nK¨{'$l֬2'q6J]+s_^!*O9wob;;RDB^rv N9zFd7Sn^՗^.\z?->b֠HQG>wbR\ ζzރv-lDѐj3<ۮO_%4^ꒁ-<_?h3Wc s?GN8%73މW+9qFo؂m[Nr>hVqHz; GܼxMyn+Uev{G]'73q~{ԧD>󘲗ۻI]yIh7SM4ʹ WLeצkWO ]ۻ^ku4:b8>HC9[pyIqW2qǍR[QΤ?.\c7N~r=2 |M?sji ߏ.>}+lL8#bNWڝevDR׋?xzj$M5hNUQY+>.-䠄wjiqQ__P[Hvrh4͎W-ZBy݂kbE}iDɯME3:A{_M%-RGFP!hY߅\!Z*Fgȣ&"N4_|8_]Y%bY$0 7<$ǗԖeQF/`l {vGkx緫wo ѲFF DR 2 gISܔAi~Ox}v>{+;U%C=K7S[9?-Bx~zI%8uIWvhtdg;lxMFkr^4>q2RtthD4gY$q/M?̐“yi/3i#>y2 ~M+cOYaE+uoF~+n ے1i2`P[~.h粰a#Fu&^:_kLZZFmu mǨȮжKCߵ[i:SYtxxnSDߧ;HW`x~{йYERtԍ8fcƞ{Z r6Z}NQ9Ӹc2Psh6]"G/ht{ugʪ\'I;ts]v+*oc1{rC#po~/"t餑~D߄ F#DuєYI̓ieg-g=`́_n}͚p#K4:*dӦ)!$xG>::MMMEvv6vJV~d'4ԌJ>Kj0auu?|ٳgĀ+T*C@GO!1WǏvcTBB( PH|,1ХWVT0ؕ NeVNyۗwEh4oB5]<,y 2Ky|yUfh4kghjJ3x>4mBhN2h#dpt nZ! 5dpěx<<یk{H<9sh{G~n8t:nck=+eRR]=ns[yj]GH"#acu܎2uT6 YxYvtZ姗{|Yɚ2 9.A4g;Xڻn<^)%^+q߼D;w=5&}S<|de9[ A< 0z Hvi'_ҋ n "<5%@ 5Y`^D\AҹmOǚIvk~zBpkObu'>6=fNFcwnRr2++ r.-[{[d|šu\s/^_ʴ@5k,fsjP} ;HCcޯximpNgǪ5pبUEPf(RCi>󡏕ڶF)5&/J 4~U]zW#IL"=;#}:[ҢoSkd}n}o"0/3 3R07n=ҹԌWVtU3J s2,7U۾d -; D<'`2١`{ <\;q}C1ί iV׼>ܿ{m</!db LsNmDIf9x ϭX8y\ cy Rb Ɖ k&X2 gڻ|RқIyrP}v@4U n/O6+:У7D_ ,Lv _Cll x{`amйڞRG){.%ȓ'r$'B=lz͔Wŗ]BS.i¯S5"]dsw.OQ^3qFo؂m[Nr>ho0Os[BX|/>흸yBO_8e 137幭q'eoB4Z[i['us'գL%7h4f|":p^1]_Y.>oξ t/wo6C"x?ly.jBFBYͼt J/˟y6pw_|bMWu/);o6sWwG*^t%w<X:y)0 lSވ]j $kw| f̧/nLwš[E3|AM胢[iY?C|8L1]:yOT- O?2?hiW&"ޫ: >9?r<-57<}۵7~pkC`-+øPz.`x@3F"VԾ3O>؍$5{쉗 #^MS֮w2OϨ N`9(*CǝZE?~z\:1RyZj82xUjV5P8{ ڨXQ_jZQ BY}&.h*ѤZH^_'i{ެD"[SIBA^++v)Sވ]ĉ拏s>++A,Kz$FJ4Ijn1Bd$YYYg^*1濾/SNHoJȪJnemHF]d 8IWmy#h;^8Vt_˼Tcw~z1q&"i~Ox}v>{+;U.H_4݅N^!x۞mĨsy%mdn?cu5,\QuC絬|+@#$y%?+"X3!Ho?!'3h_MVG|Rs,uu5MhaN*Hd,R'*xh!Iu svp[ؖN[(Z'SQ[] F1*;+m9vz޵Uצ9Q,teB&[uG}9ԼD^l>ڼf4N~k[G$+fK~v<\mCQ2B 8EW6E ֝0%ӯc؃=/*:/~[\B r~%sN4]sE+̶Jveq[ںnxw3_=В[T_I7=pt2폏N>Dnj34~çl\[s-}wn`}9]HY;净a}z:]c[_ڶy;N;0?P>Ț#kur$cFd8.Y}1)Mwsop⌍б^lZ1x>ޗ>z~$7B7ҩN}ut(lو=x2oojF%O $$$$L0A]]/?{~ރc\\*@jcZnmgBkelDGHG߸IK2fM  = !$߽ټ ߁&Ru<iDؤEkav&ZikhҮ]FL-Fhxq4%n0??CE)Αjd:?/e 2|G !C id&Sƍ_ -iuk90s^nIOwo!׉:Ê: 3luGƒk;ߋ \vww?`&&9qUh)n\# IDATr:}OkFHK/>~CGU=$7??,szDmAѡ3|>ζ7XqI;r^P~eC6=i5;[0 Wu]jVaUu؂5SDrϩmË]]'Im cGh췎U|Kh?v2jE38fi^^Zs֖]AqSY6B0D`dd]Pʪ*VjijY܏LPD-B^цG~]]sSoB4Z 137幭2^\!kVO3i4i{A>V8J]Roy:$BHҹ W&H| )'F*6%;O4 AŶ>鮭| *uժSޗvk~Ț6D%-4N<-M~Ja+8Լ/[˲k+uVڅV.+vv2n(&Ny.j1r!*2r!sa&{߭~8HF9n3aq[ںo ar. Q%#n^d)(rl%rz=SϹCi%'Ȍ}&F2OӨ%g-h;%9ndܽ!=ll545|@+&H|cC742-(/,^nnfeԵmW=dw䇹;؏3RTe]̅cd%u@aV`ۡm/Ԩݝ )1k ?S]823{cYw~?M!kHK7m痯5'<) ؙ}O}DRu|ƶ;\&I;̢JUϕȩֺ碛'Zaֿ Yf)އO Yw6Ŭik!$xG>::MM*\,UB"@bbĉuuueTMEEũSI|ߝ_MNx> @PRFGR;Ԝ38\.Q}DN__?==NkhȻeee7Y`jd}WC1fMlLB{&RuO?Q޽ ZtJ=! MLLLƌQ B!BD!B}tT*,,dX kjkkkkk7AT!RMMLLdyB0??¢bC!h"U`0\š999MB!&Ru@ x_.x~̚!jFx3Ru|>noTpӭҿ?T7׹t:]|gq\2^3j.RoD}Sh"UJLp2\JG)~)]z盦Mu q5R68zq܊GSf;y8rݖ~EEiICXY8 :j9?/HIҪF9.A4g,F`i .=wvsJTx46wԠ<٠k!9Ru ɒ {% ,3 )¯ L ߚ#!++K w\|rўU|VU?S7F*,lmv'zx#+tyс3_$׬ϵSG{O)ٝͩe>C)}0uϜjBa؋=I]/jv[&멑$79U$GxzFeFStx%&UK4߿}6cPt{ ڨXQjZQ BH Mĉ拏s>++A,Kz$$;~xN'nGz>Nݐ)!}pᓮuUs^_>,wscӐƥ"^JtMnemHF]dE姏+DFj~BҬ9LBPIR[F6h#_h]f&:-HnMHUF,}&̍駗ӣh.}C]enPy-+_ 8kKIj!s]~#~MT ?ޫ3/1ro23"wWm &Pꩅ̟0eJFR=V3(pCZ[ez~.h;E7OV_#!0DNhtҠudW7!"xQζi U2,^nnԵmW=}'KYGqBcwӵk61_f0Xe|qQ܊3l @ȋSexkmÔiJlxY.cQ1!}wQX U3w Ȍzܼ6<ܦk?Ɯ[֎o׸~pԭˑ̎g50ڶҦ߮ۆGOϯ_#އO:dĺIBI@;@:u2ѩESusǃJ>Kj0auus|>ٳx@gQ)J6w(9%6jjjwyseUU=J3HQԌ .Kr322B:GGǴ4efڴi!!Lg"п&Hՙxzz6w!3\:G!B&!BsX,Vaa!RXS[[X[[ B!Bh"UobbbllL&˛ MB!D:ammr444iB! L4@!ޗ |~dgefa%&B!&HՉ^A)Y2\Y%S`W28 [\A+(Q#jw7&zۻBD:'hV28fV{jm(nk+x0r~9g>Z侄!B1A7xdJc9vǪvm[:XG° rol3K>G:ҹJG+?c]g D*! `</!d*t*q;;`~%TV[d5ִ㦭>օg9sؙFTdby)1cwp%y%DmAѡ3|9C[ rvv)E]xdw [I;r^0%.ʌBvDI jdLQtuG0Y!5[/feWRVm~]xo}J$U^`<èo-c}Mg 9m)Ӿ'Y=]:9{*@ID:fEM C>|Q eC$:WsU["ŻǞKs =I:L5%`5®^KMz; GܼLTR?;'(WfKNS3:O:sΉ)'#EYYYRuY/GoZTR1Z4&xKJXѪ5 s?GN8%73މNa p"[/Hܱ}9)@t}&[9/v15+ ԧD>W=kOإsu*uйpߠU2k['us'գL%7h4f|":p^1\<9=UzqlBӎ'E8~diy~><5!SÇåsj $kw| t3P\I_YQA^ݚtHd Ox+w 6y23 $2(nE;4s)ͫ_}۵7~pvxl&'7>ԿY/|;a*WTR1tU/_fGJrmX%\O`c(ٝ`=؎]: H\4ŭi[1QǎsiIN_xUƤ3,tu|M?sj55efyS\glHzQ7_OTswNUQY+erzh]^Io_?k.-ȹ6J4x@TD:q#g✏J.A˒޿$ɸfM-LzyXd#QstՖoD]*䯣Of[EnM;L}C=K7S[9֯\{w7egb$uRyc%3@$/!ΔQטkDC!2ǜbyԄΣܞ},{"i~BҬSw[#$5j{Q hDsſ23nywD{4{HHU\AnGL0oˈzͥO}̭j?e+]'єcm)#Ib5dc[bgQ}B9RuTeV ѶzzʎʽmrHP`%! L}]z:"^1]趺߁%*qU17a=E\MumPlɱ{_8UL}_-X~۫r9p6&GPQ{h/8ԼfuY|2f[y|m،k9}l`t|Fz=soR`ֆUؙ˥K> zp/Vk tezD<㷸et=S -9(Lٿj9ƦT ܐ*|h_ r6Z}NQ9ӸcBH&DNfh@J{2ݑ: ~_^vʜa3oP|G(rӜ#ײ)&e,z%sWl`gnPȩk'_3r;CGmm$Z=W^AI ym~YFvQym)#&_ef#~k޽j泷 s[ט#uhUI ZnS[1ȬӵI~>w~YCq|9(nexfQ]wo>X>+w;zz~>|UJ)f}MW%BHN;:ѡhjnxWbY &LG?{',s{J'm cnMy*@!ħ_zjh~ IDATjaq.UU(>.#UGR322\.!hS2!!w=T&o!PsҔ7jӦSNb({qd417f>B8L4311l(B!TgtB! L4B!7KHձXBvDB!0D.??ؘL7/ -,,,6BɁ&Ru ښ*ihh!!BHh"U'd C/~x]q?JfMB5#L4RlJdp*+8 v^A(0JRQ!h4mo:GI1%ܻi 78>׬ox ,9n.}\Kr*K+\KSt@Cqy6["39>ݣchGUaa{lO?=!8xEѣ~q%I?^ٱdFaEc؅6濺S57S|Sx̬Oމ7 }X#Yvy^> 0m=` .ȽQA,jF8Th\jFS+?+\:qi3Qt>\m_o ܿ_)L?ğAbvN_ p{ۮKMmkoDmAѡ3|qk,v:jSA:9 tuc ;~.;6̚c5T%bg YYat:=izz]Ϋą9YQ@aێQ#:[k2k N־=)jjBUcgڻ|RқIyrR5Z[*%B?r*KڏS}.ЃޥJ:L4hV?sMBj{J1zkP$>gz4E%W !jј Ww,)cGV{Ƨ9r-!noNܼZ\!'/YtrچYs,"\Bg%EEnM: $2@M <^QE;R3Xl*/f%9ܶphӒI.v~ы'1c=VԾ3O>صkKY+Tw{u"@ki%b#ro@[wLz_2V~׻e;HW`xб7%5?eB&[uG/ a~>xe|فKz^Kr"E&z،k9ޞa߈_g|S kKj0auu?|ٳgUqs?23^W܏Y!BMį}`+ N+(QˁwF^)vsx<Q]%cKw np|Y%#Fӿ](YWߙ,!L`mWW׀Ñ3'vwtw6(aݨ̻JNTgZfk+Vc9?/WTq 9k7muǍ0*z4j5zAhlA-yI}+A%BM g4R3 %Kw<++N3+,MyoLvڐݛWң=姟<_Z/ئ:5s+s܋w{wc;SI[(W@T6$V%JJ%mTrȱΕʵ~ʠf|~e]1^/k~)pq> ;7~[ xl]` Vixvfx8prI͢ǼWmOb)( ަH{7.vco\g ߺx.năčŨױh$%$~q*#׼Mǃ ZԐ3I9YR*z:a$Y_c߮H'GjϱkU7𧷎l9DYZ=)mi_|,]iѿڎN M$h\\,7?wE !Hdev"J/S&BWZ{Ie|ϒJn^^:󻌬QR+矙`[`6E4:|D\y*Ȇ,ˠSWH ֌hieV<[ʨ@6^x"?x 4~KTJ0 8MMs[R3tMV]zQ$W &^2d|k0k/-Q71Xtp|EE .+,@7۽cw| H4AW'h>2hSD&u$<:߾b7ƒxbьKq9Jo ۡC]՗d[usyC$>˗>)B(1ݕ0 lƝz()h)b gCOwdda rl59A;#\Iý fɜ,@53\iv-{绯BdRb1~kCN`Mzw$yzSBɵjFhW9VシwhVmQ__a##[%o_e8m2hcJ^EJתjiDtuDyA1 [~m1I^$!L,0|ColU Cg?S'e6Hk2=FmÈ:Rx$e +bXK +DbE5\зk+v͏?V):$<,za6\N_^ǬQ9 [}R6=yY`Dxxc5V^صJ}pHPF ^wy9Yj]o}o*+H_9S砫s?+5JELԎ;¹JX̝R[O_Ұ廍ek}/+es͊)RCfDzSl0>q?_Pt$BƵAs~!1η%J[5r wd׎(/ꌭSS&ɦɫ>-okcN?ysmF Dxyߩ A8O|uv_Pa:e$xjKQasԢNi|27/+x]ojqr5˨UnƎgvG9((*(߮*,G=sL` |NtuuwrwJ{M]]k|4_rEAANNNzwxzMa̦woÎ& -,,(bJGrpwq|WլmcO0bD"U9G@ ptuL&d,F|FգGi]]]^^D77hF6|~ee%F355bc5999l9ճg:_KKKH4UCC9͖YRCC fuv8ҥ?ERaH4AWxèu2:sudЁ;͛2l eYzMA,8d2$I8;/h ?E ˼t~h%nVTUMS|.'_Ntq7"cC#贽#c[ػq{{ǯdg=\PFw u#cgf,FG |srW xG64Ų :u`LኖVi%)ɳ۴" a0Z.j12h>/ם%&.}P|Kc|̌X/FRzDqӖ  H,:>aR PMv=ORKK%}bV]8AW'~?cڛz."u lGA!YFH!CUU?oHRfn gPN!daf4pAIâ;f](s vI[w<.xNPŬM ~Q_yD|%R$EնFL ٥&^T{/+k;5weLMJGO;rU+M&K)y%W<ɓ)[^>O-5999 Qz6öv** ϟZB,>"f65={Վ׫跖[%oBՕ{btuky?=Cӯ=H$1Ѭ,dc1@Ϩ1xԮ44< n{ꪏF44t9,/Yd5#\޺WCO})kWgpxm >7/>ov\9u.$y.k n[fӝ_ TW*>U]xp{Q]uG>y:I}K~A/h6H!ߕU1,-t}Ay_jKC&{w4lzzzTiڍ˧TU0ؠhψGl{sH cqͪ?BOw.-G^ވj,|ZTAyr@or?qYkcf2,'KIykćoh//BCN\ꪏbۃm!i?!uu+jhjձZ"%? ANoEͷ\op,`ևqO^N :.8'i"SXU~H\Xtq܂EV97wfeģ/s PIcf䞢ze:%ubKzpħgɧ$+lٽXgj8TKAVPQNKK]U(ɋ&lp Q+QFae/ ERG}DW{,RNc;KJH9kzfG8#oHX:U҃kǢ#˩ \Tslܽ:7Eo)ܝHׄKJ@%(x~xG3PO56G='a|)A.(E >ɓD+ z!Rźe$<,r;6-lp:d Vg">HG" M?Ԯ|c^Ńex$N o82BoCǕT{z]\' (} \1oǥòæl?qɨ~<¬4Igѯ^9,Y訽buU,tzWK/Rq\fq@O?cʾ x'r Y1-eb!jJ]n6]].i{R_1'tNhj _vlL/ Z͋)˱}-cstfl^<|?GƒSһiɨ]"734T%hrm棷oV 1kqf}"qyLgT>D!H~k @M#j3flqBGmD~x- ;V;xDRѓnJl>UpIۻBp\0hae>^*@6-0GK< rż!~\sAj \e䤶 il6fKxWs.秓?ԈDd2]&~k4?:"_R0jQ6Wy-! CAqLdsfyQ^GH%y|j7U9yy6!A]AIENDB`O}̭j?e+]'єcm)#Ib5dc[bgQ}B9RuTeV ѶzzʎʽmrHP`%! L}]z:"^1]趺߁%*qU17a=E\MumPlɱ{_8UL}_-X~gMTP/web/images/Capture4.png000064401651440000012000002437771167500143400165740ustar00darranstaff00003030200010PNG  IHDRwdzsRGBbKGD pHYs  tIME H2 IDATxw|ǟݽ~\z@B""+*_l 6 "HHR -cq\.KHB.yޯ򚛝ٙyRkW0ɀ   @ٿ"{DAAQO#@h>Ǹ^AA;gIG"HBJAAA U*\.JAAAl0vl,!DRAA;*,%@?AON')lCGզ.f Ҝ KD0-ﬔTؽw'?qS̭u"ߨ+Jyߛ`7U-vHbFgdOwU=+݊h\צrHJz4dܪulLXcֱLBAN=(ʃIsU X.-䂅 duW@HZ"2l6uJ6QcWwwZe]g4eBKy5OaDFϳ)G\I+v Gg H=Rq9=Mco"Ʒk#YG%@*G3dc߃ w H R~Prf% 2l^GyTx$|їټN`d!A2fӆ 0h# ,W)NY3ޭwTK5vaTYRVa\Tuu)B^<-Rr]b䜺3Skt}YJE&@%{kxʑ)e%JEor˲ Qsi筛ifdbC&u<6تW-~xWT 뀢*MpdNxU& 2EhKvQB*o=v&1+ʱ™j֨u >3e3)&!@m|BTA YAuE4@m?)g>єI FqS9k'ϓJ8bx]6BJJ[jruB!5:;W.W( dZdrMl4̸*Aآ#s )1U9l-9**8ciIŔM}=] *U}X ħGrX|6⬓E,dR9ʹ f:XAZvo2tƅXJKDG6T*P@xPzA2Fee"zBU# -Ne4  RDAA5  ȝ@Q>c  Hc  Hc  HÂ*AApAA8dhh:F+  Hpi,30(g^nN^n5} V͢j1#kցA9Ǥ{w|{Wv-Qaw5?.# Tsj@#da&yv*)+4;  4t>rf5(8$7&ΘxW'k{*UO3^Z8?y& #3޾mhyo7= XK CZG~JIK2aWKxn;V  HuA!_u rқ'._9q#oTYXs03>r)e/%an΍ZHLH^4Oܻ+y(Pu_4ysL۳^J;=ؙ/f}K4YAAWgksMgԇ@1 =Y|/G{+$Ѿ m5/͡;_DҺROoC'my>yTmBƪϸ%#" N*V31Ӿvg&,8𠩪Y!T틼xT[e%^d >c:K>}Ww^FUg h AA܂z{+旙 3NҽrGc4 ~9n0`8S.m?okbA4,+/e:{|^/fMe?x>L4YAA܂z˼.Z0SLÐ[pH5|#; ~3ڽEzOQ5Wgs6Zeln =O'{ _UAD/2w}eVWui#t18  [@]NOSTrl6cu4ABBD͍  đfh0;|аp{BUh\˨ǬqAAXd@AABeX  H?  RqAA3 Tf>jAA:S\T@e zAA^AAT  ;pksAAU&jLAAsAAU&  LAAAU]l6sgZ\.A&lfYVHRYYYyyU7cbR ZWwe$["Ǖxzzb !HL.K2P*t ^' /Fh<<oc<<_UL&S֟i`, ߮>O7glfocǵ{)7RbyAX**JF4Iez)L4#ɔ*\! k3 22n*_8y-w;f̙3?A5KRնm<\tQ Xt颛vmAx^0Ǐ'dgg!0 #vZŴm qiFey4(B' 2&~}[B x[W$byeY"w!#ĭ)*>R*l?4)J~ UudҪISܕǀ2;"^I (R9TPRI FS~PjOԜXXd2䌒5󥥥Mw^* E4C4-W((J%ܟ ]jqm̘/y7'Noyʕ%KvCxcyPHh1\Lnt%q\qqqTTS\\,N 0uiF3j(x'q4%i6ͬt'tЩsHe6}Ա=R௚ʼ ~y5RH$]|S0o,BvS5rmK q*Az}W^* e o-𖑢3pxyym4x$$$|٧ys*,,)g45m6TT̐[|M&MiFNɨ7FlV%Si=5MW3囷liJ|rhPZ^( tJNv%ͬw zATm۶aYM 3L&}<KR+y)qb DzEIp,²lQQQiiUez{{nY|:r>vXBիWaYV*ab@X:fְ'إkRcPbһwl޽ D!+6۪9.%;ݮtZgYr3ySmu _`bU^.sIii||<_@FEEEc@#YRR)Z&2P+m^lfrgDh駟_ >8q *F.5fgztǎڵmcǎڦtD)H$ q<3 5l0///|qqx'qBzWyy 4hZXjs.aFBRt&!rlVn~=+7b$R}^ݺCv{$w֍繻S WdA$AI@DJh xu21H9Z%i[8QN4#a$@S?()CiAR?-I h<?Lv䉲Ҳ -fL&j4ʺʅԄ@bb;1\Lnt+.T*g A.9X-..wGDDT>\e ZXNZ%ƧcRMsDAˬy#G'O*GG'}HZw{ǧL6m͗*߉:tȐe˖5_2~Γg(y~R}ǖ>{~BKen,4~GRth&O~D;!h0I^@Q$juAnaN!r fᓻ5jE0+-FPJ$ X')x)Vռ ( $4LN)'OBhb1cܸq+&0p@;"N>+fua8N XaYuƜeٮ]vecƌd<Yer΢p2,9USU AApƼ~UuF1Ersssrr8Hr7R"77(G_bl|򲲲2{=륉γnz孷ySgw>"c$Nyi MBZ~bO?ԩjy??̴>Z_L*WʕJ\!g Rx"LP"1%Mr4qA~120`()a hRMS\HnaohG#vw {>rztxwEUx7^e*RR2TQ+k99:\E^ȍNm(}㣩ԭrFBQ4h<<=?nLBB   gBjգv=\.t/7?4yܹe  ׷ %P*=ŀ{vۦtNjX8ii{ ㅅ>>>;vԩӯ:zhdl,5[ L{Z6ݥ0~53'>lzPJ>b4kyATǎtO|ߝ旁뗁42Z"c* 7zF-یBlb q0%7ߐ2߳EP#y;vlZz;s9UbVJR ŚYB5Zt^,'"j_~^;4r%Cqlxy͒۵KxcZ>[MSԷ߮xbJjJLtJz%Ic:qWʀݙXSZiX!Eَe=ԪU+q8CL#x1 3#c *5˗2_?Fkxx yL+&.`C:uھ}7ˎ/돊*S*g_,ar,~4wSeLi.Kfk L/sZoʤ/ q]YԠivJTڣ= C[,,kayA󔚓Y `앢I%F7?40@Iu\ cSV}Cd߾}CC)nnP^v-""X1k׮LdA|zIٱcG\6bD7'9B@(^0}z4FzRXp2Se1.p%99yǎ&D.[_@旁dhz ?\ MK&پH+[_ƺv|8.RVVMmڇgXР%B v>*:wDJ:xf|HL<x^h4ϛ'rYJuR2lذʗ˲Ucjifo';*4,vݶ6-SiwǪ_]DZL[†..K.9v>ѩT„qSv>agz~zaJ9ڐǧbψŃ??S)iya.9#]ƢQ_ڸruO|&c)۾^hM0 bkJխvm|)C|(Hnoktޜ%rnNv|`]hjی'}Yt~I߽<͐ئgw|5*e?hm̆c_nɵ*̋kW\aiN'lPXc/zg[u@pbCִ-Huig0/ rم۴ꅻJ]Zhx2֗`} y, jwT2qu;D`PpM܈1Wn׻6Ce Rf{jf-Yq3vp]]dTce(F0uBZ} J>Yzp,F+}!Bb{#NlaX;Z]F.C<ڀ37f}]5^y?:v6yszCkր懷}YUM sf8ZZ[nzsTiw?^)as`BcgURlOֆg1>-N:vtYmlz+޺Ҫkjź/+uA])N ilڴ%^JW6_W oΌ}x~I]jDbXñ%~ox迳kwmiVPƀ"8 r-A%^].TiË, zrmW~ehhfM[p.\5[m|)C f$sI\2ZGG{jS>QaaUAOI*?MSYyuHn}29 n9B/}t7Ȫnm rsN0uVY0RTc +:EtVvd>UٺRشiKjC!BɛğQNpɏ51vۺLެϺ˧G<-M߸͛?א{PyOВ?LlY΅LO}=:Sgu|J[aG$fO~O掭.{{UdgN l])VQZ/n%cRf]?w̺Ŵ5?ϓKyi͘ F]Ӿ_QW`#ʤy56]m9߽q2C噪&F6zANHgWrqxo>q]}}RoKҲwH_+F~MsV oUh6wy+y}:ۓj;&W~KqTw}YĈ6>8gc]"yWƳu8[EKXHzOlgI ~|_+~Iiwf]&bu Tǵ>mU*!ya%1<6rGM Īh $Rui'Aj!ܫw?CtͪU Z/2ZX&k`u   uF*Y2iAAA  ?.AAiHi#AA38c  DAA܁[3/au   u&>!сէ?V r(.V 4KǏ8L0o]"l%?,3)t@DA΍4hhTL 0D!rB]EE^^ށrYHpc#M 4HAlݼG6mx9NAf(* ::zSXe#MKdA" ].{ЮݙgmcnWXHs}T&ZtK3!D.<|/PD.hҼ{qqΛ}d2!, gϜʺv5"*::*n[=4z]+R[Cvސ P:$'K'dg]ȨhTmVx2Yt)x.;7e(A{Ceרjئ#<چguЁቓv y7;B/Ajř+ |pHȩǯf\Nh0c^Q^jD4<,Yyyҥ,YtZ$,F}'o|[a*<ЄwPjvsO5A]/M3;tX,2d4VMLu`[ƘJ>9O+5%c ^o`m"/͝4(2(mkNO}:coA翴`KyE,˲,h%K.?U;97H:&"UҢWwnuR){am:b'% n՟/;9>4\רO}|rohcW=j4Z_JWn^㡞 ^~R~փ# q]VAj4W1 #-fsc,mzGr:5?foM:ʉFKq]v>:m ?JvN[U3Eܝxa嫿JJJ͛sj5mv^;bƳ<}Mb䡓L]t.1դ?\G_f+_.|N@]oNCz%Os7?zp~qqX:wsf^G9ɧKY7;LGkg>Gcn u9=MRe2_?k(,ȷ0[B[M̽~6YoSRHl#{E+;3( r9<2X˗ Ν7onu.]<ܳOYi]+iqPdA~.o]CGVY G{9<>4\(-)\]8h?y$Rqv[ VW:w՟Jں->y>A%M#XCfn U/͟K a#eؓ%窻 ͘/s_[sb7JFH8#dk+#BXf@jݫ#Hu dq/>SKo> 5.vs燒ڿrq/D,WJDAک|bwӺyNz1y?6b|y_^tq/: o[/8Nں*{£}zy‰-_UwS5\F4X8艹|$k?>hg?>3V> ajk|ۺL׋cwo%~z6̖~9TDzo7|L~1ЎC^cڦY1ھ 'CO3Rl$9kX&[hZ ĩ7qj[ui=պȞ-zG\'žM̆wFW5;Յ  JeV^b8Z!'v/.X۾xMPV= ԪTPv2ohc/+ AAʼa*<|jLi_e[UB\7? {+ AA,;P[`%r싮p*|z:m[KLrѴtyiXc Dm޽ݻwW= }5Y%,ǻxrgYj򊛇@  Hڵo׮]vw xXXU)?`8ܻ IDATZlBA1߿ڵkᄉkZݻb8A,.fq+LEʽ,biBA.̪BF ,r3_?iit}k-Ya|QOO;~k)ǽþQv 0(@i4{:k$ fˬ}`vkfѠ`6ZZOwhޑ1QѭfǒW38/M -.qY\ !6&*U&~rOnݶK[9Om:\ݡ ?|{Dxha~2΃&}BCCCCC&u+թmێ=mVyGݒZ'ƌϳ"Θۢ+7Bj1_o Sx)?9'wj1rgvO3P[˾HZ3K~S_kKřO<g+*G;چ8Â~ke^=}U?NEv 3^˛o<6llO98e6SuӚZcJQTtLk] Su3ؘ}\ qWr<ϳ.8cnD&8ptڕG6zhOS'XڇHt.^E«U4Dyߗ]ýB&,޴nYE='n׾̗Nn]v9Uy YQwySI@J` l7 lR?߬{)}:ĸu>B>oz_?!nO]k;7^mV D TC~janXOK{$u7{WѧS[oȁz(JBiMo)6K\hxbN{څFw:P7|429S.fWyܿ@4>?ļ`I ׈zQNz腔?VL|uĭ2Y\̶V_}!}{y`uxM Oқ2*lwW;U]^"~Q=|f0gTrbl9*S|e]S"fr]er/3/VvQ81|kXl%{=zN~jK4Z[@.~4.ӷo23hGٻ%;-Mz;}KSK)ֆ؜îu`{fM0׾R~rNߤOyQYNۏXs2iSF*^ZNKUg4M:MZ :j2׈zGr?zjT먨{7G <ɱ.8|?m' \Ϟ9~7J Vn)|T\fgk7̺~٥l* OھY!sJ`*3^)d3\ojL깓Gvncձ5 k?9݇K792iZnkiCG ^}hNGy~53_+B\u/|_gZ<-{peUgxK@dka7 Ku>cNWCo!=-^*Az,Qe^1p0g*_xVۿqUΪT >w|ǟݽJB4ŎTEi"R""V@KA6ԟ@hPBPRߖ\QyΖ33ϳ 9RiZٹ|aj(vQ2_\睡213jFPJohs?1bE.s#: Ҵk}vƾ9m]qӾmww}3v~燲dGގoWc0CC?rU7YY{ (}'u=55hm%dw&V6.3dOVeRJ)2Dq\5tjl\&W96 󔯯Y0])wrZ:I֖\-z8^5sX0Sĸ+wlըA&4޺O}=<:hҤE=-xnDޟ~J'韒&O\u*kASXe[I)p͓6Nިs+eށE=;>@ۨ MY<$/Ϣy5[ns][M|b퉸zԭϦl?nqe_ȸ{~6̈ȨԒv`VY0[2;YVoiv BSRRnh6Ї?Yp;@aaU])_Jw_OJW[^֨cJsWUJ;rN2>TY~ Uftlױ:`T٫W{g8]2ʼ^͛3 2 oM‚Ĥ[~գ#H ױy_qT&qĹNA~^\ Xy2nYrK^ Su3=40'a8lYmNa44 %3;Ih^ 0[&Tf1gR77kRcx`MD V[w!f9222""%^EBfsll,+4`0vi:gcŰAO˄ߵY̙]bEڜaaa'Od%`0 SӞoCgE6EQJ}"eT+=-úݦd0 qBszptSK֥R_hN^Ltt4ooG)/m,YVfq۬nPmH||/^< ƭLttz0;DeJ" |)\J/9wx(M˘ڵϫok$I*2mwL2 k9'$I4#&nIXyK8rŜm[s"3(g&vt5Y8ע/ Nl3of[i:x{FߎUC)*HBƍ!aõB]ٻ̔%qLEQdU?zU/ZIu->|ʼQʴZ\_SFx2W}}Ge${ ?H;|sa{j|wԨaPP^eJf|܏;gY E_O<5}2qRR S:vI 5y ?k)2~h&=QWݷCbE"G֍‘jNfIQٵt{x~*|ԗr.eI|9ب2l>HMK~m?-}s7[rHP\Oy }Dѳn\f/P *ƖlIuhM{k[ێdFwf[^r %ž>]'urn~6rWwS3q1CT󉟬z*>6q/7ܒ(Ru65ȧl蒭=kGorkjcCNuw̿US~~޹c>۱V?7zdصЩ{7}tY#i'w[ٿVO0`r^g~ه5T76#xȩVe;88潩Yė>9h߯m>kfLQS~0k)k}sm\ +csNزk˸.:[^.[m{xy-dx z]׎hg[}bTVTvF $ń<0RP/p֊=N^ɖE PnP7 į8ßC9V2S>_/ɀL +)Ç߾`H';7^{~փ%b5eŞ۟Y>ʪyἕ3>y`\/"ށ8ԤU/{Ogrq2חڌ -ykVq%eJ@d:':jƚc*x{'3]6H)(JTfp!' #]0RHs˄c 66Va=xU- :A lH&9g% (g?HPrl3ׁcqږ11S(R_? 1_=jud ż;`-\OW1!@-P7YayݾY9V2)o{ 5( l|pLj٫v,;Wύ4HM||Ԭ'nWk Zvhbwڕkq;zFr Srdeڃi_־l6–\KsYyٮ;r\g-$ 3-:1G’?6.5=0E6ԍO,Ԅ _lgG=ixvgCJwlu>3OcLn>7Z5sX'5wzߜ2yG QuSllzݣ]:lU}l]6GHJ騇kzf]W[f Of,i&>?ţ-[t[k^ycƀㄺm>SAwX?ygɱIQ>UzERGmafT\׹{F^ͩ zGZVWc_~0dH2Z@=e4:]hX|%&$4O1sjZTr6MڵkտɲaÆ;穰MI՟vm:?߸mP*q>]vRFbz_#_7O1F[2`\Gk ~xOCR%(B]LN ٳk=ěcViAA"K.aY󻑐4Qir ^fpAz ;[< z+/g07Ǽc u`Șfj.$111%%wIIIwI<ݨd>;Ge=fT\5{۲3/mBK%Phs>TWsU@s(,,xG5o>ob;fԓ?æ_JLm5p:V^0"#{ۥeK/ن򿙢B9Û-z~ lYB]=gۥBGR\|S] 4z֯hn'b\r4-d'AۋCn6a㚮0 ˧Nh\/cZntyiդA+3~`½gg棛 0}{ #{_u9u6m/''/62$u/Wx'bxFcp9cF+㶃gE`NP3߯C]1UEW f䶩KN5pvU5ԤS,/O|l諳:3ױez/\(eTxbS|WrӇq}Om!9$);gQ?G{3m/L{egOZspuoҀcTx'bx<`0ʼ>l]5Lf:M;?<1w әt:}#""F] <J@ag>pZGj Oh@U*h2;oy'C^T&! )5/Xy~auޜP&˚]cNޓ L>J_春ϨmggPD:N՟ {yj$|ln̙_r IDAT͚p5wW:mHoں 1͑W5m6ccc=3j^̦CZ="DDHG)jhhxlD}.>='p"^S Km'(%U߳IL'@^a՝qTǏϜ=&M|O=AT&qq3eZYs~ƅ yg.={8jf݆b EU%(Y,;1,Mّyo1\sy#:RS %-wEgz\&@YZ؜c(Lֳ@%)Pݩ2e,ޛF9m9L|ī8[n5⏁A_xI:myo b_qkr3m;?.[~O[*nCQt'^,R\Oe2mZa;i:S:7Ss$hAu3_zxΈw\?:hu -q?#[Rk>.XC _XI4ÞD MV~}z섇4d 5ī;&DyUGP`^?p@?Ye1Le^視K_޿Мj.aK mY6jqa:KxwJ@c.c>S)IORc Cs@{ΙsI9scG'< O^+%^ʼn72źf CT$+\ J7 CX ;J,=90ŋWqvǹ$ʴY14:P'įaC QU\g%b=f/c(|齦|[^8Ǘ?U2HJXIRR`Ppע)?fvg0\(,֭}z&s]ةM%^xEG{['~8sJkF^þWM&S=ߨA}O~}݄k&+{U?58O-Q ;!*+{ð 3lwgvb L]&q)$`j*9<9v>wop~xyޯLZepc9[7;9S~ `ݟ8׷x fxR5Q WPEwfBwl̨È[wк<ǬRe2!>btu[ WvrXF0vp}>k2"|ܖI[De}R3죿0_(#U&?|vՊk]Mמ`ɾXMY'gV: *Peej_?YPzN^ "0D=z2n 3 QOM *^mY'7?Hs2SeJ˔}rca'\tT(cxYܳw_ǴeU 1o<}L&RJ'~u2A((w HLMC{yXqMPظ/V;Å߉ Jl֯0kRܷeҬq]Ǭ2n>y~˾~=*h~?HPrǷ?]U:.r]Fawwz`-ըeJ+o</';6̜yU[?snfTGG2wj'Z\=7h(gY=Ckծ|iZGm-foqy}Gsyb`0Za~L 7 5ϐ4@ wjNYcٳpo5|Re^9U0fjGzvwzg`im{7L^QrébhT=Y)*kHI)pQT2XXoz6G7`I~/;ƌ+ u|f;.?0ۢ^8G3 衮m%1wF Bcb$&m};-wN+=r_~0dH2+j~=e4:]hX|%&$4O1sjZTr6MڵkտɲaÆ;穰MI՟'k! /EpO=`  u{4>8$PBB(!%ʄP=yK̰M iZj7e}/U $ȑ#!!! E*c u`Șfj.$111%%ŗIII `*c[ ^zr`0 㶃a0 `0`0 o鷮&ucxq;\v:4F`0Le™͚p5w\=(#j.l6GFFFDD`\ݝm6cccY1 qLk18kJ=(Rr|f{D2gFw%..NvIVb `0 ;??ڜ.[~O[*nCQt'^,R\Oe2mZaN`0w,4G7d]*tV[w!^XhYVۗ&:&&&YlV-)#+BaBFB! D! *ՏcGEyr*{œ0538&E"BQOJgkld%ާ@a,_5 ::l6{0;DeJ" |)T{˂A.IQml$TmchtN_p}ϩ<$Ih]ݼ%c {)>>>WS4n8...V-/B`9,+ cFªDS5*=Y D6ehO(J% -+Lե(P @KG BaY X QSz4`ܹ*SQY=//..pO|QjqN'sG7}TF¸_?iwQo9?Uw 6iK J2qs'`aa<#M(#&65 Y*UFeSby0[l)Ѡ%L_B,nPd !$IR~~k!>ž ƍf˔eBdEOE8Hpz_5Ҡ&Ƕ֓;.5A:@Ӈuj<&i^tƢ,<^]6{o]Y#F",2j{W;s1˰Yin׺]A[Eٿ0E3R)/u$WvjӺcߥV{䳛<ѽSv]Oj&ˆ#* "DVL@(D]n-nQtE[t[rD{qZwNKMtn(Stvas:NaWWlN;vtX՜a:6b["8ڊiq:$mҋEU9.e;"KDƊ\-e ;OMX|=f$f0c(}G?f#o4vΡ^QS~Y:ϴ7cgоR⚿Һ7 c-캨L]q)$`j*9y[h uxkVqWv}g|11j I>:^0IH᩹M0bLn5g&yG69w>VNvs@Ο/Uˀ0s@$+D(FHTb>D@"EQ@(DQ4 m @匘)v"jwh.tv !Th@Jw6#89Y|r JVVvas|h]w xe0nO)˄c-3N k?Z~;\Vx?"pp?n<س7F0vp}>kήTfGaP$FL U[OOjjἕ3>y`Ŀ6i[glH5=9?BUXPPoႥ}tۇ9^xAdJ(cD!TIo QS@Bj @"kb"64.wi=+Zoֵ R5KTFJ #6D$fUw@]4%6|zE;Nn~tZȜLT|%2}eV})U11BRhkltߥlu.iy_ٻ/cZFzή]etO>df)x E\lb;Av &ޛL54~S>\Ks}9M]hIMT(lܗmw?];W>t;cX(U8s2AxN ZP'*֌F`!cLebAr"Lj"Tg{KFS|IPve u\U<"XKjو#ak!eaFʔ5^=–SgX:znˬ7H^78?~ІϷ໌‚s{]Ebzʔ~jz`-\ÕuPҷ~=h Q[Zl\G>.spLj٫v,;Wύ4HM5fΡS$uB 媞 n۾kaJv9^=d'ehbwڕkq;zFrGjONF9jX]8:^0K  BU+!!J BշGP J Pu% RSMݗ89xyxEK89s< <.:/ ' E(F#WNfc"PRY)jNY#ȕ{IOd0;my^͹)}pU7uvX f}=_G33L_ώhdJdkԶS&O;:'1yQcU&tm ؜i@#)9e,Dժ63񟞫ͺ-XF|o z,\69rϢy03*Ž=bgYe1^ycƀㄺm>3#{>M#S]Ao-8`0+BB\"m>8TQ/,/LpSFIӅո^jBB3*N%?/l]o߾dYްaCrTĤϓaod6tbC+DdEQ'iaeh8lYmNa4Yݑ a@:iN%˪L\2H< 1Pc,a3cq!!%2UhPR !(EA:~%RT Q̀` 8n\.06 V$Le2 2s&us&u1F@)x``ϗ֏tQG@BHvvlevG1FH0Vb'xYb /d9[tSBx^@QBA0L&qsB F`IPB%DQ(D!"Q ^uEQdEQ0sZmR;1ݢb3mvG```l J%QmS~/ kA=usT7! IDAT>@iN%^$5_^ԙ:TfjFIt 4mro`@(PawH( %r\N-ϫ*B<<c;<|LڞG7ثKF1cmݡ+kPcXlj{W;s1˰YVs߬BbgnaRNB@qe|0j`6?]z9>>ޥ8xلf66?mJ1UVbjZ)㪷H-7F@rv{aaVep8Va7M~&Z7:~Q5CkLFA9Π~&Q' @o4ơ!!5k "n^ЦGTGZǻJ=WJvoX|=VD Ƶpez*+ Ɋl˔}A[>Z] [Ss~3`W}{l,4vΡ[#^^zPeyv6ioϱzC+^9%}6hPiu:wgV}bֽak*;fuI}\fC3 Jcje#?xÁq.XܧQP;͏9qBҥLjRxjv/{2IޑGuES 0tdWx֧VtVžBi E1KOݖA]NYI[+({OTdNn5t,;ya@ADB rs/P~LHHN06ˢ$`2CBB9%Y@ZPhZlnvBCC@d9?( (/h0""dؽ2cUœRBؼr JVVfas|h]wcV WBsԨQ/Zb2}U>2k~諦,xgor>Hn}'Zg&IW>m@sz!`_nm0APSϭp_F0vp}>k@˫LuOj} E2`ʤ܃K;~ذ>-cW&{zM?&ؓsz3+Th*sBt^ϝ7j7&fU#-$ExϧfHf:Agy Tsgegg Ng 0 JNWvfv?DQBapSt: ,=zp֫W7,$(b;w1++a]{ig4:DA8!pa[WuZ͓W0.7[=}qd+ a-؝q)tOCPadKV6 @gܾZ>sޗ{Cq3ƕ޲*x{'3]6H)(JTfp!' #]05Q auuQ)}|0Zi2B5MjBaƪ~V?;ez1,+5dOi @ۊwt`28 _ɒtIW#,lp9D!srs6 bɽ|cS!S̼d޻'%??nݺ!A:ܒ1`\ds;ﱣGϟ;y W;J v DC x5(RJaүKXe2*)Sgt;=aRV2)o{ 5( l}\W,rYviVG|]tO~m#+ MWr-sGWHlǞS:.7˔U\Kߪ_|@\rFT\2jH;Er*t؋ ssr2333228o߁κEE'!UXXh].! #0$n[Vө(DrYm'OWʞ=EEEyyyVS BHd-ݲ($I%E!@RJ)PPHجY)jNY#ȕ{IOd0n- ~~~dSH>;O&_Owlި{_)i򄚭o=G c$4ӻݺԾϘ=Q^|}lCLJgyP׿^ޱUӼ{>e[evm s l#)y)unl3cazv|pсz Q; n:^bH;{IyjebOJZ]9Nϼ% 2šE_iP[h%(Floo5Xksj [E@iAL{@@y/κldddd*%xg6U/_lM_qf9>] C>|Y+8Y 4/pSk֚Tcc[nQ([\`.5rM6&態AsT>_$/ pȑbqX0 N̥R}^*@! 9̅aYgt[[[`豱q----ij 1(TR׿6S@ {M;6dϤ386tIkKwOsW'3ߪ=?W3c˪K|r>0چJ)[l>qM)0`Tcs\Ooo+45 $bTà}D Qk򹖖QBDia0);::3`# " $S bb Q8lxdddd*{fKyC[{\`e(2I,ջeS)'ղVL v'N\1J3uxE$ bPh( -mR>CA@,A Z ۇ o%L1+GԼ-1 %%А[o#Fz$B+1N F0a=KΞ=mDh<:B'"^*yi}u)S,3Naw=~ۤzPѢoseoΝ=\sɧ^~a֤M8W~[NeVf&Uڰa{~o?7!qg<ћquWЩ\ϊfZ ^SmO޼&,{UuZs.D/Ȳ},",⼫E'z{\4&)r佈J&1 0ΗJB>,޳"LqԐ5T!XZ#9"644cr >BP*58KK{z(.Wb/*ZBX/!xa$9G9Qa2p""iU#fN]fcceglTe~stI>OGA!0}3v_uѶ\x!_=lkp^dr]Hʬ+2]cÐ{Iv1ǟ/nYmW=xŭmr듋gLܰ nے){vKkdL_ʀzAo4\MJDP<(, b羍q 756E B( B>! Rl- rR9EQ>jU)hjj, QxR! sJQP//Ylj䉌#A4r\Yo-) JFG̬5 ";c3y6fd'wu=y;.;s77V.;(;NbqO|؉_:v[|7uW΀޼qgseŮS/u9WWf؂إri&wUy?;ҕBK~?`1Kzfg?aϮE}eiG/Sdo*EoxcOJc \ݘd=Rȿt4.Fj{ B JrbCCP(K,ioo9rdbR(ZZZBAZm޼y==K;;Fvl\ U*UTV݋I`/ʳXaI+!!QZiav95dEG_~)wx0j3~OlͮRFr40fIgbUgSHz̉w^͉f_w|jprߥg=t)3ۃm3~V{sGw4v&tgwU;<~Wo f*2+k2gFnB)4E/덢.}{?ooIS_7}?x֟xeBsYk-{t??ijN!OS<ಇtwxe;sl.{tE"Kg*4DP0pmmm " Xk Ƙ\.DD8tZƆ#Z8T*666FZdIEa:ϟJ\.Oڒ EUkQdzU\8baúFloj< *k16T<ϒ@圏8l,? ^C',~=0kGԖVt[tscv8r ?ݥ%?N.4tr-7>ܬy >_pU'|8iV:M SupggN~ W_e •4s?}u+}к;3ZG.͒m v)pkWdo|r~F@_qWH` ƒ v?M͙,X,vzbQDlܙ 4*.(J ºR8r$ IDAT×,YR)u^AT*$IRgSS7Z ޳w JA C s|g(XgdT@R'cLbd0`^DL"֘jl,%5njr f=kҏ.d*3#cyfg]R۔:vrW8i8$]VI^!1 !?CyPe<|KfOU+"g6,Avi1|9C K`(tUݐ#o/"lMUuʋ֯n\*l.y!a]`  047A*L VJkm"g1556XkXQ}{~sssbP(F(jZb\.P,8\.;FXT*JymɋAEGuP(Ǭ;V@B(ߐGλZ-1ZQ"D`=S((U+J9!qŧdx76-e22V{GPv0{g]"lx׎`v`-qo^N(O\z\ew5P9ID>EssnRd!U;u\7_ޱ=usfu[+㓛rFsӳ'{6cI9y_7OMdx*H* Z1ԢeBqX J92IXȋy $,U0HTԁNcT00h1cQMMÆ5 kmk>|xG{skKSksSbcCKzTk 'h-޳ ,Ly$(Fr,)5~#ϺEɒ9]eW2#!1hϽkC~vӦ| O⻄ϯx쿾uӗ~j/jW_{۫cv4pI^\eiV?{fO%M[n9`Ckڴ[3!']tSl-sJkbb@@iEIm|P0PZ+)BtZ^]ىJU%< +T|~à\/ZBԢٳ~(Ǝ'N Z&{[[#k-,ȌiI4AY4)\/z" SK&k!@% OS=xa쉜"b3u%Nl!15xjGI[f.תa*W󹰔/J_yyI \T ŢgvD&Iժ޾r_?efdd|̝4;;1*D=jzFFFZ2munDŽ8 N#șEA$U$DB:Y  M 5(ޡ2S&HJ# yJcbBp3 <&xODY422PX'|2Xj՚x A@g#$###Sk2Suu@(Q 0u{O "J#z#!A%HZ1p}٨R ( PQ?a&Q: hCD-, P,)PB8΋0o0+& /"tJK 0 |XFHFFF23222LMTr4[ 3y hPP#ד m +#̩L8 76DJ!*zw !93DNO3 0ǎ4yJbÞ( 6qGICqbQ*5o]AܙȑGo7NH6B2222N샃˞_B,ZH .Ŏb]LzVvD;r @#SXgM, &qǑJjSZ33cA$O9oYľLe~Py67>or+p8PY-.\=nܸ좭! !{I}(,Y%g<-)]S:Y zM@H*DE#"*dV TN x`L B@1{Tw 9 0yٱ3^XȱGSԬuau 伵ƙ;kEHpjFFFF2/VXQ;w3l:dFa C>|Y+Lf<@Z E$.Β5EKZH  :B 䃆́ZLΒ DPT  QXhPHIY8g=3Yҥ:D$;9jtQbĵןGU Bv^:Tokمƶ!{&՞ُ߰!gKZK]~ؾ:?Vٕ'.Ҟk&f E951ZD$I6BxL''k~߿O_X &m#/:Wh_w؄6N[NeVf&Uڰa{~o?7! SƹǞsGo%ם^A@K=sҶ1em=| ko,u{}";à`DyAy4 C'Q)_r{J#33 ýxV8Z0MdC;3fNٹc33=l'v? :䫇ͼ>- hA*ʯYeÐ{Iv1ǟ/]r5ۏm5v]}.c_ݹ{ulO)s q@eVLκ8J|@1 JbbO>.Hi]JT:D;43ckIZJB) ޾E Mm!Ws!yb$NTs&1q_Bzu:VF1"$Ţ+zH,QתZ**򎳵".#cmE}{foW*v}09& <7Ҏhpv=GK_8kJ;]]]gܣwfƍQͩgoNL^U݂+&mq7.:q6xmtwuu=u ƌyCJ{.7Vd/ݛu^}q/7f`h}bw@owS-e"2|.-Ŗ)G7e+qˈo8y[ WbE8 q[}5SK'Nsmm?x{ ϙOPa]Yqχn"Gdj-kpo &""\_I'gs;O$Eq'1Ƙĸ::r=g[γ'(-:Ka4_c8]iĦ׻o[o/^w-Z0bD$"kM9k߻wɒr_/93IGQ ,x7̟o 0 Bf1&19Ml8kMSZ([,gݬϐ_oFLx7D/<| 1?j8O×O;VO?vߥsg=yG@ۄs6)Ѣ[([[EQP9 8}6t'0?y-g=t)3i*y'9{]Wv͚k!=7?iGw<vW?̜^~f#kvg#RòOV2+k2gFnB)4E/?WNcn Wޥowa3/״m6+nГ9szgw$$&$6xZg#30I=ܧ"ȑ<4ܛ='4o$yX$6qlLlqJ0CV]E59'D3Iz{/\hޞZuXsR&ULr :Q-1,օB!˥JQJY/.z_z|δ+{ϼ!m'wO{}ó__S3@~pO>7k…ٗ?LDp=oi4- bC'sC.4tr- N6\&G'm _i#W8_ºuHۮ rL㧗tۜB8iqC9y~=E>ήpk~X_r o?;tw^N9S[j.i~\r%rϮpg^qwx).'c?߼M6+fIl'R!M@Q 5AA<4:" K=D0BT E!ϗd%H"\_iYqE5TD5qT0sPPI5*  BSScXX#0Rs0 Oyf:sy.yqVlc~ f=kϹ.03[wҖ68ky~CmT{Mw?4KԿ7\IiBs~.Q }x۵棅s뀏fU""z4BՖJuH߶o а¶?'Gvoo}\;E(-dY[y?s^VqW5wjZ}[fM |n+no[{W$;4nz39iJ~ѭ/u'mI9slVZS:g9gb"r9ksy"OVybϔf${ffb&!Ow{!/ޱZΓ'" @#2zذ榆B!υ jj$13֚$jJ/UM9cMaZaCCeذB,"T>754575AJ)̲h2zٕxv]}/Ď"1}v`-ۿaԗm+V޾+QJۿ=ekϝYLNۏ7 f>q-\;:?s^;}gֽܺo[7M~s.͟/gy-8e)'Neo=}yR7?v~sʵce >3?|ɂ[YiPj^PT׭ޓ'&wiyjѬ 9b$ @O_gAƦF P+.YԪޙ~}C6HXGO=q@OO}vr䈅YeٖOθO~?z?Gin:\ 1jO, k<>EQ-}' >'NLC"Vs_lmmp],.A6l}8 qZKDJd@ee!UZP\ 30c< |=:R8h)Z+ܠLU*/(I$;k6RZ[TwJRTM<剼j$Q>46QX#(E΀ :15ON),$l.efdd|̝%RyFƀ罱B¨PYbD$iU4%xou. ;DžIXg/i",,"P@kt 9b R"P a '08fs;LxLggާ"U&$& 3ldddd*syis}p1w+W(f7cJ=q/\0!7/b1kd1o$S!U("T9B5^|9֐֩d;"0T|*REPiJRba"d"&AP P(VDA!&IK3F#:E Hd$Θ);#r$QBL*<ؔw샛m:V)xs3J/WmSJc̻;;;;::{+b vww7.k%YgcƳ"ti4: =# {ob!dŽȲ8k@{嘉@U@̈ BDJ3*Lp "H> AMSx*P09 ҚJY@@D;k9 ޘr:`&@MB J;@TfFFF2%xg6UrUEwnˁMYZz Cn>|Yfeq학B^G{ kN0">dLaL;C3io%fžC&DBI" @ wA @=WPN'J04:  1R žV(D W@P(T>zb$QE9bԊK^k\PRy  BLNJ:kHXD<+f4Z!jY󌌌Le-] }ƾZY<0lW-J9Mb*dÐ&o8q$X)5Q101 H5%|l50k|6oJkG&&Ohb*F@ԃ*R"ZJ#DV@ޱw{uzo3 a!@At%* (iy(BYDA!BN`BD@@?eddd|T棏>뮻wGydʔ)5:Oֻ:oa}C|ӿǟLvqG^4J=ιTflkQ1 ssqg<ћquW`z2sј-5lT+n_/gf&Oc=b꾫&}IKezMb㈈ df`b&&&50q6f!`$UaLd8{P$ `F 3Qh!"{ǩU;'ffL䘴S (J) ",xPb-R1Lf|lb)S<#SN;>J뚀[wqC9C/箚s:e玙̸>MCV1_NeVɕ_.1aH=~S{n?=`F^{Iyw_nޙ?K9z8 ^|Ǔ M \/EVZHMXp6dɚbZ9P1J1&ZNDG.`|Xk{AK=wVӕLBQ,BiQp4cTăwQt30{LS8edyFƇf)SΩS>裫)1{foWA7csamNsi;{l޸S;9]]]wn1cpVWW[zVwuu{[p7ݤM]ܖ;oXiVc,ղYTe 1'KKeѧ-i =Xq_vn䨽noZ'~@]&/8qbo'^~'os[mY6icWZVjqTNr??&ZSf1Ug#!⬍jLؙوȈ8`xĵVIj8*'q%Iq\JUjj?*&Y;Ucb" L$5wV؃BVB9"LB̄-ybIM3K`s4ߑ#9+0,g/7q &sw؛]l]Le'նe1?jgy'i=?7kί^O4c'O=mxp银 lREwkxs&,t_7vǏ!Xrocj,ײ2gFnB)4E/;43x[Š[>v#W?~Lrӥ/}q/划#ReL"P"JF)8*Qp@!eTlTb@%@)eH@^GbT)0sr_MJ6Bۄ0x80F) $F%I+cnJyKƈk=1>;{SɪJM*%0 G8WP8 cJ(DIy/8{'Oܹ; CJ:L&*_fc:gYP#vl! {M8<lY9x5r?Fm>!%xNom?! NZcs>hüc=c|| S0elyMBڎ(U;?>Wɽ>|j֧ 6Xr4?^6zSr9ýO͔0D6JZ%%j$$11&$ @&2&Ɋe "۔I $`cc&' Vժ NVzhT<x{{x{{yyi=Jq$IT% GbJ%1@” F9A*Zը*y$' %֨=<||===ż$~m_uB̳d_:>I|$1Z 4$qނQﺯ &oh۸A/'Lķ SƓ&ogW3maRr@yy:sB5dq|(c,kpBW{_Ic篪=?]6uѝVihTp}^dt{4')bEYvQ zҌ7b^1yn;?I&NHl乡2C1dQ+ W񦄣q9blF#>!:PeLy(x "c"0^+!QQyyJ%J%)x,#9 NOJ"7T(rޞ^6CjRRJy'P*l6p%Mu?IcGGO"HE >PT5LnҤq7_7Q%Vv-[lׯBuHŝ;wW/xǾظVKSMx{{KrONs eOB PQzgox 1G8JR*U 8X-(Ѩ%I2͂ ( __)AHhX-3c"O1mȂW$WndPP $(B$Z VUҨ*| `!f.(=!(F^P HQJ%(eH)+j V;vd٤Ύ_\'0ann˔101)?555**JtMJ4*nZ_˶G4z%oT^}I@ %ƀ  G !1y؅,M)c(c(,6+e @(5r J$J"8NTX>AY9K)k6lVmJm17͙fZ6$$Dj*!1#v8B8z gh#<'ͩc0c@<&H)$IpʁRJ%F%ƀqrr E>yK{l;ygB#f_2x^aQ J//((( ɤR%*B bH˳X-|A&ATĥ jqQq%7rX#ͺ0qUs^qVfffH# GG8fo|m,0Ӯ=^H10TQn6N$A#0R aTB8bog>g@(M`A?R,nXf3EQ*f fbh=ױ *0 .닮iw8`=ytޡ8RՠsШQ#rtt )8+8OxQ'KG#r{ >ʜR"2qW=RWޙ*3eoy$P=0* HJ]?Q@%@0L $cT93#`S<_؍h46QZmn(J`,?@Eo/Vyyy2bYa AUf%p.34^.SZL9 ossI}2XYFt)vԋƣ y$ (q'77 rߙҮAc-f#]*"-$ qGܶ]7y:wi"3b2f_ڧ衔RJ#B䠡<1!c0GLG\a|89O9cb䌕j*x>eYNя6!/M?.h2 O`ܽh&ՁR[A(6LY7He)GY8A,3wsO;kFhmZ:BZn۾M\K?Okb+*KW᪞ʔ$IhB5g, >ۙmvL22$Pn8{(,Vy32i+n;e"TxE7@hx1?݃hQĢ\<mHbpW wxߘ ~/ע_[X;[̩=k@*J]U"?Q.3[SH!ޕcV}|AF>W33_Sl!-^J݄׮ecGFDf=I^*S_Xww1cN[;=) &@ѠdFSw3B~*Rqb~暥ׇOd1Ʀm8G>!aT ʳ"gJ۽߰קŒ'158k]=_f1: j>UIrM{ho{KΜ+'wf=F>7^ 7K^4^w o>4VzԨx$TrZJXJM4nfԔ+R6?ʖbܲeK~ ׯC(ܹ3>j<Jv/}{cZ?O,!V\P{сCP "H%<ĕ?г@(JQ,6RV>vȲIĪ o2h E̫ %/W A!6}|ٓ#L{\U(<66699eźsEeA%I$`c!<޸Wo *E6vs1 ݻ7:ǁ i>Esd""J f7eyATrR[̑PIʺy;vx]]$•KA^תUz荲 LA QNtzԭ׫T*#?ʣ Տ@o8 2~xʥ5{nrĎl4]jCBBZrssjՎVe V Eo7 2R8cp%7ۖh ͺ0V5Dׇp(z>22vyƪ"A Bh@o A4 .s8[f1zzDШQ#2e```zz:z AAPexLi1\<.2a Ip2XYFt)vԋƣ AAj2on\s}j3x!&ud j2Z+LV",/#/ڎeި8 HW(ݚ& 伦(^ZC A=5Mk7Ӆij{ Rwyʑ@\,p/ CGLTƢ* EE{wGsa-3ŞzzytU$I~b':j Ԉ$SFeE-.[cuXRVV!P* ZQmthCjxRS㱦m.> [)mn"-QaGD6彗 `VxNr ?cN^kMWӕEo!ݘ ֢1R*T뵳ztj.˿.s-MҶygzy;8bQQQ%3, V1< g | `λ)θJQqoȗ_`I=Xf.* aJ=Չ}ώ\LA$Ipy~uN\9wS,tz >ߧ{vI|mok.<*X~Y=?˖o~ {;aI3(5Fn}kۨO>qVQd6V76ߵ ZzD6PW*Vfٌ'}ڄy?ި')nyz5j7f;zGto}z,0߼vWQ@LVE>ee._ }~j_}b#ſ\?m CK釲}-̚rglNPq?e=&fI>cO֏MECb—x ã2cs>hüc=c|| Sb^bk,v$`ɹrz86Sy@J&źH~Ō@􅓷wh1t0̋#_f934 ~^ٲ}Fy#~'_QI;O}KX||#^02b6k?U_ ^OCg^7 ޺ħ Wi7vb(kT@䨹yRV>> nѩwҡτi\ƯLJk3J}#fs<23iwEG|3/Tu077eJWGSSSJgݤAHKKRbp>v]y3byCwG}[o?9mlc]j4Ips7~3 ]g@.VfQKoرKř4G9ysKm=NҖ@*J>/m2X`5Főc^*QQQ%,&B F<䧀cA||DɖKE-? Y''tg?N|2K;dATEA(l`f?Ii\`u|%.,j\;' mѬE#O21***qV.ޞzPn5 Hkʢ`Am:$]Wvn1{N;#:+$IbFeE-.[c-i\'}w f~'$HaӦ  TQ EJE3; ~9kI Z^{_`%w2Eû7[MS!g1m7lء{/魗u@ltLZt*aD,,Q3Ƙ󴴴ŽkghX[6M;j1 n\|7aH͞۲ޕi۬e˖زi.:ACɖ.|~Q>gGZ.ADI7@0Fɼu}t*f ?KuN\9wS,{& ZH5?_+M K̃WL'P!+>M,ۨO|y|;u[&o^ Kd9uCeԄ3 F~w9f HE/LY՞ôܮf/@O"HEx-H-cfϥ8A86!%Ӭp`)9 S;`R&lJXh,j\̟UOF-림9kbK9!G@'-6û&c7/2x=N Z?j:yvмbO.~tw*5A* y#}K]>|#}O,Eg"HTx/2]2 &輒ξ㥛`JipZԧ<1O1{*uӯ[L&clڶsHHU&m6P,@a^I 1u</9#7Z˱} .~̋/>Wv RAOsy_. Ƚ˔$ܼlHWjA0xo61L1"eGc'ay *16-&8̛13`m1% `_89kb 1>1TRlێd,9WNoo=qDX{Sf-c RAn: g -o:t3ϒ}7'"<"cej o5KW,?4O4ﺯ &oh۸A/+i>I| KQбP YK5خ܎e:r;${R'+5>h}N]1וZ PT5LnҤq7_7Q%Vve˖~)_DQܹsg|Tؿool\+%Aj ~O>:p DGs^P HQJ%(eH)+j V;vd٤Ύ_⃌e7v`4"Ԩ(Y7)Amo 'xGRh0 !Re;Ξt n?`*ޭjHlllrr;=hٺ )ImAPeލ.鐷=@`RA AAT  HS.w|ٰss,#Oe;rj~Eх KM  H5Ugt 7>Kmdt:x m2X`5F&+('y:AN'׽ A*9\%[~.\At<6Z5D˥:A_Gte,mYJX ݍ`f?Ii\`u|%$r5u5D IDAT~~=5sغRbTTT㘦]^XH#[5>_xДjoT c$I~b'DMؗw3 W3₿< *X8ź~"IRX`Y1fQ#bK^0k֪'ӟ_B˜KIKK%ƌx5~Om=եkMsNoewxCA'2)67D:J9%l҅u9. Y;z)H^o'bZ/ 9iۼqDd=^{^~[̋2~U󴴴Ƚkg5Pt/4y~aq&%wj٬yA MG;|vVNZQ⥲^gz}WZ/ڽ})LvB~uN\9wS,tbCWGi,tBtLWUXlՒ_}-bw"=qϟ/yaP^ h2 G+_=Ӹq&G <_Xy=i.<׹]K͊_D [ER[̝2?w>eoX_ ')'n'G*eϟNeT[9<v㱛=!4)'9?:vdd֜Qg}mۏ e鑾%.[Wj `):A{*h!ߞ[3Cd娺lץIg ! ᴈipZj{%̳׭oNn1li2 !2 ~(̴??mJ`M((HYs;{;l8]7GGrg~͝S08pr\A*G_&(=<7 |_ 4 jcIs-}{#hXIf~pˑ&NZE;&Jsl5ko<z-6n˹]MT&t4VzԨx,cR_텿k=<*U@`|54i))W {Tɹ]hr<lٲ_~ (;wpO&^>񠹷}?Rۿool\+%)[}Bsd""26@DJ{4'ᅞ] DTRfocGM%>Xf`xoo) Fc`8.b^OMMr\iii^nu^RYIWpHHt.鐷=@` a,77Vjuj6W-A KPJygP($VKF* .@AA*]<̘7nX,)5MhhV-i *A\qڵswfff^vnݺ4 G~@U&3aÆ6eϣ EEoEU&<é?qV٘яzר0ڪb>mBo[JSPY"cC(J,Od~ʳㄤec^澻W˖ſ\`h~ ;MV*Ѽ%|~Q>gGZ.'AT/DQ 1 r<k.ȳffjTLF|"1A&,Ɣ5{$^aPZO3Z:Ɯ-33>{ރ_,;'ml}{cޕqꆔrд4yc^ ]{Ozc·o;q݌wqVY?~ELwlvc'ghKcc_-"7n`*{R3~v۵߬XU&RT&'Q-ƣi7rrdʸy3iqWRe.'Oҫp/J)_q馌/!&k/>쭩tXStkJfhZեE=3KǼwqCcVqWcz,k9l?ٹQ1ФFšfIペNy!g(lyuFnͿOAL}!e%X}##"Z~,No[*|<J?OK VzK2]e*DvF6RPm)Vޯnwra@\L*Ј\i'vG~{n7 Bx&Nud.ooۜ 3j6yqzcƛ5n-iÌQ%Sɩ/=pg2|5*DP 2-BCC{~@痝T}.Vv!8DAxn|-Z 8h_/,;ߒ4mR LA_㹰ǢS8i˓9)+MU󼯟_TtT@VAPe"HE1͙fZmHHVE!BBSNE:Q(U#Lz}hhhHH9E)4+ \ATr Fl6)c *A\#I$IwN+ȓ.e_zxtaRS" *AJdn Vb2XMF`q$pҊn UNn *PLe o 2# 9Mߠʼn_׸&K˭/Q"}֔E?zT*AHTLe k8b儺ojKf~|QA"(++( 5ryQLkя`_ə1i1?zǓ[3<ѩU,̵\|7aH͞db35of-xO 54Fb\:eA?8$oE7@4=GT:OّDTH5V L&,Ɣ5{$^aP{v㞟?_e˷Si{uh=ߝĝLvZvǩxɓvS}pvR )+ôܮf/@!H%-HPE-p#1\^OK1JflH|ܧ8=@B~lY_©\?6yk[zo}8` DM׾c iTN`I@wGW|`)APe"Ken}cwvudcMۖqx@ yb>kQQ[|"Î=EN͘}q?)*Q  P>;=AB^RLbU08prAPe"Ue*d>ohüc=c|.! sSe2^rS*@4mvQ͓:ۗbk,vdi\>yo-.w(`GW yY# #jj Ч^,RKvR' GC[s9X;gљ*^*c98>158k]>2sdaQ}rcEi9 b+l{Ҍ7b):5RZZ $zX˒:xuW}t`;b#z,+s: _R,Y;w\ɱ?y)%1}^;<:>f85;aϧ58/i~ɡ?%_g<.h uѴ{gajx<3O߿G_%Lxf}s%g,u>bw'NۻLQ{<Zߘ,ػbΏXryχN]΋legz!ƪ#Z.T"j* yP.^49lү_?(;wLJHM;ULe"G=_F7QT<;xZm6sAGq9?bSSSm6sfKMM1 <<`9PN\*(((..=@gBAPe"Յ޽{Aʁ-  LAA*-Cl4.SjڐVNCAT^qL^NCATШQ#2e```zz:z AAPe"k$I$.ieyҥ,3WS/.lXjJAAPe"H8Vt0vmj4XLh5,n0T"\\QZZ8_ATH5Bb*dFx3]g u Ap)n~|8NjشɉmkyUy3 Cjn xǕ!+Dzɱ9 Z[ ݅{cBC.ۺ^SIѥVctl7+,6ࡸ:(F:޹PUSBv%dY=-`ъ[ o~/isND)+dŊ9${:.aTEfb!eBτ1VS~leJFǩ GBLsOD:}IJj +/ݜFYwpۛWJ@WL(j=:uM+P?g4Apx2I7]|5+iff5=6]Όqs7v󾫟cG,geEYe-qN[ hᝫmm]<4fszZYΞN8cc/TRSY]CVJv_εeod%s.&;FGUd2#װc(8`r{Ə4EL*Er(sn":5[̆ 4ly L2ۿ(4Iȯyy B&Zy Be <-a'OJBAa#~ɬBl|Ъ&G^e_[GWJib9{I6Q:J:P;"Оys3zQ;t"k&kJmf{1`jVsOmbsU§\q,]P~Pw/$zA6>udS;DNh !%ƭ:$:-%P7?trߴ_Rg4@6S:UBQd.qeg`lv}tS T֟Jv ttM$:(Fr%#R43QgǏ.V1/W+*7#o;@`|Qf?aؓ:GWt_5[`?yߌI c%y΂# Y>{z:|>mhܷ~,F(I ԋWw2ΗV~hjS+n̹O&bg䇥6?W}9I9pm e$=Dx#":)]𘳙oV[xaUUeҒ]+gk?B(rBE(yI!dO'XB\,BI  c?--y~'5/~J#~?vtK5K^zDVG r,jd;QJ!%٩:_`-%]6c]i!1G[]O_SzQ sA[pe0'B*.!] ƈ3V kLs:G&6Gϋjzq_EɔP2޵C5F(H[VwebK$&99u~ʹPR"SZ_LȈ:W܂Us4(?;oCMFL4U@ ]M=JbR?/i [+~gzZ [Gܗ\NP"bOK^ȼ1ИŶ=!QjG{#RܻA1wCJjC Ux~*-t ơ6-03|wI5Jiu2Vi嶞yA<_Aݙ3v\_=?V/fjA%ɄkQ͖4%?imp7ؠ<"Hj>`kQe=(%C^m7XW~6@ogLP͚r=~OWDדL??4>%u㼎FJYů4+M NBBJaN/\6 Oۈ) @%8TM M" t`~agoט{nu ˙S]B |m?գA1yʪI6O,<_X(R#_[HO48ې1Y=acbGl?74%$BRR?uvv^x﫞$+]Ӧ=2|Ss~#-ed$%%oR\}F#wt<.z+OSVV.++h@4k1Ə6~>ںH#555;LWҧ 72wLSS9(|`id2L& )//!//@ x<^SSSCCCBBм;lhh񆆆0hDA LMMi4М***0bD`0 Fic}ռT֓^?7el!D@!vj;ң;L׹KOӧZ@ߤͺ;ט>NsH\"_O$P/ti( k?UpsԶ҄5mmG5/$/iei8]8UUN?dx2tO9*FCl<Ү3 ='J-嬗utFX:n;C@512uVbVZC+{<  2A`02 `]v+-'\X5άEyQΑ}sWKQދqcdi5ϟLuwqK㎘ƃ%<×_c879уD31|Swӣ4軼^lvzM K'GZ!o+/ x( Njw.:mcбG] YV&dO XV!LЧV̹29L+gd:>JMIV8GR^&Lc5V?؏Ǽ7qc'9]Po[1ӽ-GY6ʊ Z㜶gvxjg{[[G97ƅ9Mt$(n{*8yQVVjffg1njg%gؘ"&Ԧa Wsqݥ'2Lw*|$p]AÁgiwXceFn{a# QZqd-;.i TbQgO~'Cf6lg[L2{ebkɻo,|Xt2~fBX"gY\05+Ĺ'6R օ +]KiL{]IfOw_>6XNɻ.?>d vFO]{>ԎM Yp5A#!'o{ZD'R}سۥ$sS7j o?LZcklK Bh/BɫOB!K:)S_I8_RZN{^חWZbMJP;ii;~sW±uQ2*l\m_ɢdƤ_?'JIgu8)-A$q/V-Ż'KԨ-%tS9]7/5o66]1OkvI90Lߊ2XB$0 ySS@q=2i(~6;dǚ ?1~G`x=&!aLNNpKdz"ĠԌ[rvd>8vk}ڿfrkK ~닰;J?T겅} a'Be~%eGXK]5E ha.,#BifS>ϋjzq_EɔP2޵C5F(H9L bi$$'ðJJ` 1_Ks=A7e} A%?_d@i8?V2Wꚻ?y)F,}g'+>Q/?d1ll*,/\8/Bnڬn23|wI5Jiu2Vi'_MAR 4ʓKq+<3LL rxgOkK x~h 6(䇠ZTYO%z8JзwՅ٪o:~C4sl8(!0TFn&aç:;>9pdH^{";xX`D%u㼎FJYJL;mֿ-ǂkO߶<,b-V!`û+:$8h?rsZ*DMe>ںH4555>쫷Q&?|wsQ&9drCCLS^^^CCC^^ (!xyw&Ѐ a2@ h49UTTa2`0.q'{ƨHyG'lo3' 7&Br[j;B$PvE;Lށڬ{i=p9t:@E8 c/ݹ9;bb[—_R| x>oA$]+/C]]L}câ3,#1oANyֱ^ nk%f>8d&~S.l_gZJcb>d vFO]{>Ԏϙo"sCs 됌괔@8EqOeb?=9O; Mn\NPFI+"K폹2tS T֟Jv ttM$:(Fr%#R43QgǏ.Vr~yP_y⊪$pQ&F}%V I$<)ʎ V-!t3.gȣG"XGn*dAS3/ݯ彍_)cʓ5w2Gro{~rI=-6ч0r2#9ӻ%rF?YQaOR'gO'3,r/'o1;ȹҒt2hEz- xZxz"\I2JxS/`+}S_ e\K$&99u~ʹPR"SZ_er!JzbN ЋXy .Z5>A#q~BK<`ckD n2Wꚿdyk(}}gGӽs)tB#>#]^oS7mVYJO:4vQN.H<#&:ŗ<C'eF 4t/Vr̝Z!c5'`OJ~RC9bGk>`kQe=(%CUfmƺ@ogLP͚r=~OWDד {CrGRFgu4RZo/~%/3ש>7#"&=?XB.|zʳ= |m?%;jY-+geILLo[1QJHqWFxhշ62ې1Y=acpaWEaVZ#:=Ò4q̧o{lkY'lj%VZhxw.}fhuny BZFFRRR&EPi4rG㢇0 )LZZZ@6:^^^ch?rsZ*w{!DO.**eLMMF]@eD&|o^L 0uYN0I233  MMMwwwb2L&c~aL*ʕȵ \cL ~b42@&攗АA%yyy&L߿?ikk@ ?x<^SSSCCCBBм;lhh񆆆0h`׏?7`@VOO/sss2w@ h49UTTaOZZZhXd]^6ve(i `t;؛6FGZ;Je=ex9 ^iia>>>{ nWC ;L vH۩J0#phkkn~Ex;9sf#|BLט>Nc] T~\*%:.gzX?\3,g '< Rĩ{j0FCO^)`+Eikk~z%Жk[]N% q㼥7w] Zٻ/e @\L }l'PRu9Smizݎk{~/~BUrw0/9q ;H"{t%J /;?hmt yƏmݎVdR+^sn1:V^P\\#z\tƪ)cHLtȞhEͭ^ #222Nٝ8K0 c2֊9\&SV"t^I.Ce -\<şddffv93qI0fCx"B?d vFO]{>ԎEQ-1@5nE6!i)褧_97Id=3^5Iײ:yg_"']5u5]}{x=j-. !D'\ɈT6 Ll +_\Q.s1ٳ*~Y >49Ϙ+;2s aO^¿nD]ua?G!EtR:mC=c76G,9rBE(yI!dO'3,rDY̎&%G(@+aQ=U<2cݣR ̳SX<)L_TTȝC˧kj鈾k7jBe)|4b&( ~c In}is[id/p9`x$@\v"](MVg޷I8mOkKcӟt{ 3ֆ H VYV[{++ Ƌo<7VVŽ]xnr)xaycxFH%k3'i7V"NHOۭ\o~6B" +1L(8ZcFIB!nR}ly3B"~_B gl}軦3/bTU Ə tU? 8IƆzԼ֕ 2MWIqeY6w\{ϟZ6oD7ʆYl{qhۓg& *b Zw5i D!ERoe0#XK_xHl {7Ʈ:v$zM86e.֙ Lǜ(w[aTU5!/Ċ,v<?-T%!aL-k}g"$[ M:~BDl?LVds&J+ܠ]ZvLocKSǪEd !6;n+;;DZZBZ@^wBqkvJ9^ZzsH{:{\eC/I:{} %B-TVKo\::MYiƗOָWMs1"yBNIBMMqAq bMTjvcb`.7Q:j_gk\օ Z7x"]#"W=ylAOksv~ۊ+_mW2"tY ңM8@ }.co2Py_~t 8^&w^?& ^& ukY%,z9\_Q.sQf&FR0vF?@1dŠ93I _edda8uq9)" +/PTUAAAyrJUҫ{.Z   rUFXTUUԚ  6b &U6+V!Is  -UU !>t).hDAi>u?tMQkmޡC:xQnz=(ʂ +),IAA3Gݾu˩ӧDA6m¸gfcͫ33۱,{r ={?xQV jΕQ<8e V!  Hq|nYiiEWޗz7mZr(IgOnRJ]+,,R-Bݷ_Fn**bX ]T]xwAAd5ӷeX}ݓݳw;,^CZZ=z-^é' 0LM Tek8,]V@pQn" ߟΐ[OV/y5;#-'0:S`rw9"g<PJAp\:BFSGȣ߷xf9zN  NIMUJ6R٩ N*K(7isLXL [uz}чSOvf},g1'`a"X-ys?dIUT HReX?,^~^|(J)˲ò\j9$.+$]UiM 4#}{+aqs*.L;wUE=3[5z_WOtzYϬ>X41p:&g3uO?yi S) 29ߣ *x,W&A 'לN1XWr4)r‚?:&٫c BFaxދHSUdYGyyG>p`Iz}=bؽ{ݻ&SQQ+F0rdIJ)@ 7bY-M r m}6vCg<İF'N'jԓl]egK a8,dA^\RR\^^' Ȳp8,kYi)+Y[oowޝѾ`Z3Xs#>## MLRY 8 cg\n3nm' 5d]~:e9c98h4}=:GD\Q%EE!((h֭eb9{lbb򲲤V]tܻO-7xɓ/ky.7wݳ|JY-QpΨDet~O?=}Pk"rM.x{W?5 AbРC)ǟG'}Z$:а:svܵ3#ϝ;yn,/$IdQA87U [GRU$qMǏf 5!md5߾#q`&B.M~,(A|qqf?&5m~~~Wt$I  !wܹwx:3urrLLl-nظӷ9o:|c3NYv_@ c/Ȫoɯf ȵEv/xS[@* DIN'@AAAbz8h2y=|nδn |JK6ujaa(V,*u ,I0d=z`!rMxGێq.l\zЗg|{+^Jhf(yДoyS~h'}yBꘜ3ۮ2;N6SWXtw@l Wv3*:F`FM|~:I#AKxj*5iw &~, ˽꒑{#M'J3"6HRtJG3wY8Ջ>Ɔ  peUu*2 G_бС<34mteIǓ뒑΃c[wO.S_/GlM'tmzobEA'M{T$=[x r.P)%WWFǾ/%#&{_NlƁAn<^tUbg??  ySW%{f qN}`Տv -;]Qh;̜pd=2qf:f$ /|3f\<>MVtrU=m:6\AA|nNXCausm^mj+~v|=qx]/Bb{*ca- ՟5}8.R4=˘. eݬ܎7 x,Ljᄍ  6n*-) oAAf^9{ظ*>9 htFV^- "u<R .jK_4# \   x\V!AA*yI,AAޤk_7F,#AA޸_y]n:v,#AAB  MAA7Mܙ  4D  4 8  DAA|\  4lrAej8Nc H~ɖ04OrAzeY???BBI[@p&mZ,` Yf34$1 @ePEe_9eY_P($<+ٴXSU%ݎS|kkIIhx%+$44eTmL_y[gmRb?.7|Ln6%Nd{,\FQ;4l7xt}t->QUaͺ0$eeJ9eG$IDIDJipp!fN*EIz)P l6T͍UU/aTȗdfð|(N'by:RJsyIU9Dg~|Mn6)\&߂ F 2C@M^wyf /}{w;(.m~ZsڵU<ߔQ[(a;VNoPeIf²,eYc97(L,è>@*+x UUM&p:mVO*N-M{4EIH~^$?/?55'v#qjVTT\jz >FQflZ+-J0aRPJjY i8ٴ'~&?۩((uym1nz今 ? c*ᑑQQ>/lI0J)P0p8zQ$UU!%°,0 g8>jh6j#: BTUu+Bș3g6ghݺoLBzjtDqR#Rmx7}ٰ0Pyy9sf͚y뭷fΜ)ΖR*2 䦡s_UBVzR P$}On6e)Z LǘC]{c8#˲,ˊ^ԗ&XJ}gNb9[qjZp9u "|Sqj7mA-0(bZ F$kUFB!0/++,ê;:pB(*Re /(PYU)! 7+**9r =[V’ޔ h܀kK8jR-!_EUJ7bޱeܦGeG4i6i()!%%&̈<#}f0)JYJENl{9 U&KQ; ,=}Z \oױ#޿_t=agOQْlE^iazH@Ѵ& ᄀd2EFFTD̥ H0 Xaa z 8l??v}߾ Cz]hh"UU,k|UUUIR(0J&̈<'ȥ`8ϯߥIb3"f>!PUVFڊK6p($ITU/M%K(bT/PfXVt?IOzZ*""|ŀK.6R[&(RE*(@UeTEQUUUTUQTU$Ql-f9:@4 @Gii{*Er,Çu<GGAB|\VVn?IJLwChܕK@|*riiiRRSZZ=$ʲ40L@@ѣPY?TUAR .}bp2#V:<=Hv (DZ@#7i*2pƹ=8ٽ\۹&1Z(9Mٳe%%͋Dѥ9܁CC}Hk߿jڻ~l ;vl9(a%%%yEEEJJ,88XeR (@0 ]0o)ӓ Z0LE+HtfSXpH_ݻOxx;B (((عsmVXTT~~~ړ_/Wx???eY9K˪'\r\bRӧ II,&[/"0g(EREIQuzrtPA FcYMWn!Va#a4& T}3ޮG9$IV0 rYNdJd" #J"ilBڮ66lf#l&qɲ\\\( ˲AAAÇ &JKK=-fYeiV? ) 0 PXFnR 7Ϩ8?Q** z䦻OZן#Fhkx^O>W^^~:ߩc꫎EΎh짭F1 S3 0Rr j`pZ朡j:(8(0R˷ *,NQ˲ {K+Y7&֛[7YEIi :99g9IHY4G˰Οu !rISRZ;TU!֡6]4 vS[i4O^PPf5jHTZZ궀&$$TJ$@(&[eF0娠Mߓ1jLGSZZ{pСVm ǬxLCy|srIC~_xWW_:~,J7;Gi'4.>{8..>C?QSxb춐fH̟/74`"IRݻu7ߌ;V)RRRҺuk-LII^Tm1$Emz3LIBPPLi6g6]S"???//Oe Q*(ëV5??CҊy睊  ];ϛ#?=̤SJU%?t<'Krѣ>8Y?{'>>FBs޿>nAZhNJ%IVyԬ2u~и8MnKReJ9 DN55A)]UU%iӇfLSJN^GN+ .KeyCDgC>4si$Df 9y*+ 90,ڟ%d񔴶>APRV^AA:O||Eއ#?4wW?(20TYbyqqqHHHΝt?3k1MI l>).њ;={q(eڌ- u䦻F(nG_[QTݮ=ܹu% u9ĺ9c8[!q7}#߻(?[QΠEMF)~aC>…>Ykԣ7PB|)_͟5Q*"iݬyH{˲ZQU &#ZUUeDX(ܲw6R\y OOf˦T(5q! 7ݸWG7/.]dff7wqxvMnz͡3egCِ9L~lJ0ã !6o0|kt֟%$6&?RR x6gVSOUN]*I[@a**!UK/3;s/ X$(TQ)U|Wn]9br;^UUݾyfEQrrr***jO`XlNo,x,PJ FCTlj 8]NiXdCX_Қ`ݵx7lr:C۬z! ಁFK/8f̘e˖]MiGUUyΝ;+W>\E͓8l])ŏe-Q IDATgYQ4(**R|Yn91fDQݻ_rz_!/|sE&DP}hz JC#"4o((0.;ukmޡCǦ35DQ>|x廍@>Æ sT]rSRPQ6;PI)U*IR=~`;Udڼy1bj:?Io|s.|GzӨ_^BZjUYS+Z`ayE90e&*&M ͊eGD>3[l^ޮR(-- EQ*V٧0LYY)M9Z5ҵ(2EVP TU78dt?"l\v1vhkǷoqqhXBCCG```IIhLLLX|#x_EGUU%IZz1[V!y>s_Mgкo0JY-۹2*LEfuWXX8422Zμ"""&ӍFG\2//ʡQ#Gy󊋋1|hD*UUİ &ȩBnÅc Ù;U`a8mtkN<7hPa>Æ[buÐQ#GPz~c/o:3+-Mrml =ѽ{JB|WnR,Xg gӎrJߟΐ[OB̝OOB)0};3c=eYɏY룤?u*7`2AZTUYQT_`,۬sLXL [uzV}!<݅GJβ}}}s_}b@YD5T!˗O8 PiD9tJہAj[+"ڄs aWUZ)PekfW8lETJ{]88v=6{f0Mq9jn*': ׬gVrX%3< Lyɪg+Pv俿9guܪ}2ZH5}{[{G}en.'Rh< eRS$3gUp(!qmtvPl_tɯOOyt}82"u%jY=ԣk]}n}>caN4NLN?Wxcִ{<_ ;hR$: 8萭gnNs%!Ⴏ({_/=qTR2#zo>42؎O~8v;j V5:æ<܌o(8K_D[_/~UTK$|]Goԛo4it[uݻve4 WX E6>rfu;ȒS@nybZpQr>n!3^|Nޒ/;xl{QOu?c@€qWѿό5p l)q}:9UGVtOzɶ`E0e zZ"9R!#M耭VkxG۷gd$%%a H#&ؙ -J$ɪRpW8%9ܬZvPJl:oǩmRV s.jةwbv/ǽ7q>̳𸌞LxEa QQJ#$(UKP&ʽV/'5ybD36/-n ~`?YX8r¼j~9zDAG%g SM:5ƾ}>AB||FF%RJ{S13[ 7T3 ȑs(F=K.-"i5z ?uܹGVݳӹ]zW~򲲤V]tܻO-7xɓ/Lx82"-Do--&:NG>[Wu$IZ^Oٽcg]4{<ՙcbbk$:uݺ^u*/se!Is%{|oc=Ҹrg}7ko}ŏ:\?_ͳ5r&6+3;|靦w;RU?wm ӱs&:Uзwk:|$,,̩SNxjB־3()*WF7u8c*!%Zm沁k Vkˇ?qr#]#+10Z:GwoYύ UJQdQ鍟lU,=}㺿2n[!*Zk2Es,JUa ڡ5:mukgմ)00՗^㹯`p6K t $ 2~6ׇ:c: ;`nڱ/j ٶz{4 79z1w4d/l~Os}FMx3M|es/Sn9-Nnz0? 4GC=QjfOudo=Πa1Ƌ^=TU$o%Y on}\6 SK7yT?z࡚{'GF~z%;w~5qH(z Nu2\HxDnbڦߘvc&)11}~۳{͛6oXyukV?M^ѣKgߘtu bA3|O>yC_|VAvHj% 5;~֗.yj׼,vLK!0Qgϗ=ُE}UTͱ;R<3 7 6Uv>绻|_ 0s D?? m4;~ҩKE8rDG+>7[Sr5VMe8? X~ (*EMGe7hdK7tHE *fJ,.uK%"< F15a-s᪼x χWLKƭC u/kU\a ͙Vm{`ɓ~ÖgTSDR}> z=wh߫9~SaK6$T.Jkv $7\0hMJFYmgs,S(p EyڎON/ OjtdUԟg-gxl&%9ZR #y/ڏ%@%1r@e8!/X:26D!g gE!h;Ɔ5"N:l]Qˇ%^}{̥+֭-L[5!9+n}CO=:˺˧>vqsfB*&ڋ>}fsfחvJEOJ~;U^ħ6jU:Q*e6|]łgKEuWWkCU%0CmUePdwf;wb׺ ݑ+ jzݧm_5=!~=M;4y纥ϵMn2WҘ'-Z}^$=&j.iz?thնKsbhmJ 󊋅Dp#mZqj^=!?.n۶+W;04-d-w@L箦[7ٺ( J0/y'ףYӆ .{z%;szvۆ }۫ >8m#%3ֹ) uEQ{=}raL~k^Dq#azw*ud<+WlLu lz 6n*-l,IX(nRC?2ErEI%1(*:Y[ZU@$ ET.<. (РQUtS _`oGY98ЏPk&H6at =*RV.]X/QZXR QRIEV/@DT< A+rs=)9q=B(|=*Ԃ"U]*Nŕ̲kV"vl)+Qe)rYj ɦX^RXݿI FU!AIwy5\~,>4x  M:6WT[   rAAIp& \8t"ȕM\* JhX8tЬ9JTZM:6p2AEyunőERR\ZAA [/,U[мTuLM6Ο@Ya giam>s kNPُ9=%<*]jAA'Ofa H0m[|Yԑo̿aږ}ӷͼ_ikX7,Î )U_nϜsnFŎ Rvk܈e MMVN227 ,>ֲ۔[_4}}]>|8ippD*TW=( XLXsS+o=JX^A*./<;/?0y/_{ꘚ"DaqAj D)Kٝ_~M1ڲ=.Y-=煻j~1 1Z}Qv(S؞ ,)zi`'j9ow A)(ˀO>~VfoV߿8>i={NtdozLk,9ČE/MG HM76H~}7lb s-;ϻvlw$0'x!w 9{L, 1+<׺ly<%\Nр vRPnb0X5` H} -[KO._sfvUA=~ \2$A9tS_3A{yKx9[G|nq 9~(ÐԴ:0%/. ײܔ٬*1iVOdZ] Z7kZ.r d]Nfجzf!̱# aZ'=:~4CGaEK6ۢO=_Ҽ dYҲ|mQco0 )uٜ6F"qzMǟ|ܬ{`?wvm;ai^r {Ų#*;XXC`j^ z-XǴg ЮOz~_\.xc\Zvf-d"AS K.>&9%r!EQSRcNfetڦ ;J^q93zI]l;S{۪Gt}M'8ّz}ZIQgl@۞}&˩DXtm<[1bMά}{ J!}甈P%JNl?$Lj7 p+ob Jn^| "\Km[X4Fv-gjCMIh >Rso~$)w>ȁzvIC: 9:䪵{:&s aP`pdmŎNord`_:S;e {4TČ[j)1[~[l`3C Z0NlA]M~d3rZeeSltC'Tc3 [ mz=zctP--)ބvoEtcvnN+!N:ӥQ0rS߷iӆ,ۻo#GY**BfC2|d7ډcg9iێzȔ4"H OU F)=U. N<S@W:9m.&R2^ubD` ;Kc-d8풑UyvVMWQ#ѡcT~E7R%vm i>):J}[)7 jK]::/w VPZbZ.Sd:S@;WrhֱNk^RMDk zf8X(:UrntDDŧe8~mmz0KrphDZ^CIy1a i]] he(?ȵS#LTvqDJ@X)5eN*&DxnTn)}ŊRVBjᤱQ& zAQ.cf-*bIOI|hX87Gv7Y/ mx)ieuUWkgU3R zU gu4rFZܬy$u;r ;ϟ<뉔RBSĞsXxj::VmI(;cLvESbLAbR{®^Ի j*M c: VUKNU4Ɛ?6VU'%FڶX ID_o;fۦb&H!}_^u|R&7km qcymiYAί5ˏ[0^O{Vbt5ز-;Cp1 ʉnw._\9OMnׄ` IDAT( VfR=emuC] QxdE?\J;vYjJX.Qd2vO.w~_z2c>dkz=ZOM(S7kڲ N4h|h4l PH nnCOo[=naqwݦ۰;?cZwz|7sV1;;عMBJ WDu_(Q݇OZey"̞z[ZR\~ܵSB|:$w~~aV-^3X* .E$|dPqS;eP*u_kQx͋'u/+Or[g~9U٫K訄39SR޳ Z:v{V~ݺK:m׬9^tZFFiӺ9pА*nV; 4tT6%G=N1Ei-(Ā*tNϥ=T$t:TYtX<7pАȨhwIXzգrGXLTܾ7M(A)4MN&ʟ^0nzV#XFJpsfb})s3uJjL_8}cŽJyTz[?'8|nߩ'~Է[S)z=敯NwϮ\;' r|i_>R%{n e>7uk:hmzW>I;=kj*WiWijŔ ˜^KRzs_51\QY?rwsM'̸wءyxݤWyP* ͺtVԾBU<7"*,⋐nظɽ9$4,?|{PTX 6 K ~_w;QAz$;٧7j |]5,Lz)5j^/V%/.:I*EU.prYd\Pi1QΞU*J*D;w>8qBXd|Tyzz`4HDyI`^a?|7*Ch?H'k$Խ<3ewƸ8z܈h=[%#[/(* _%5.ki1dO{~ xer?od)8Q|Q74GүCiK GT>XgnDagn~+=[C]x(jV vR\L+j bT)ދk_A_d-[s);>xe2Uxw?wέGzuKMIh3s럦T?رu\3ޓrJx`4H 4,<<:ۍ}6o톱1Z)Tbe+~e>i= z[0K|z/w59Wbǡ'`Pn"u58zYV_#Ǐoص_~/ ^.믽ko\ï {l0nui-ŠFOF=Ruc4=}K,{ q\7"HsbXەW?c712ߨTke4m"`pD+w#HsAiI0X  MAA&  DAAPn"  (7AA&  rAAA   (7AA  MAA 8,i> Ba!@* rA+x`M6e}9RQŅ Dʠnݺsz}CK,gٯ8bz_"4,ȫhsWX 4p&4/E5ϝ;7v^xa݊Ԣ0_]4(HLgb?٦L5I"J%ֺ%-FJuYŒ%]C_RTTt1mLSh}3 VRYY9y܈gggn목>yqhhh``` 'cKV֑$j-f6?rrm8Ff=BvU2.mWz,}X#Dкnv JMNR+zSiKh{?ruWc^~aJaclAʶtEAn@B|Xx3F 468ϨhLP}galv_WO&(o߾ÇB8Բ$?+/kCsIxtQRe4}Cv~)!.ݸW=~yZR(yz#{vV4=+N .?Ȼ|$KiKh{bo:3o@QI#ݽNdܿzxՁ~N^zp%9`@EAea&+u=w7d>ys`Ҟ?+G,Z`ܸqo-ڇq8ٳgggg9sJꄅ|t}}RSaySF*2ؔshBzJ 2eōF9veCC㠣y,E8iYGi; kʳoU'rqr಴o.Uv&RvfLC~qeɽoі<{[OhDxm9E%B;Jlj^mu4$֦:K*ߊ 3x'''333xOiŠܞ.۴L'ljcji_sSX{ۻ37I>޴K5 6ٕ"C4g$f=O[B۳эA=?Y4sMf]6ͣK,ͬBە=M9l}#NoǑsi_XLw3zl夵 Zr,'H˯%zp2eZ&RQ)x{?\Jq湯&IBhB4uBU>[t/NnlD K^ΙG7%22ɟL%ϟ/q|>I[+SXWpGHsB_7zRε 8<30?Gϑ !><v\4Q֏Ӊ2\Bȶ oی,1!&?5:v[E%O)orqK !_|D 4#)ҭNj!2!D˨FuĢBSGn4䚄O}1fi75s&0Դ14 !EI-aD7##{:bv:Mh=+ !Qݨ'_g%!dᇄBlVx):bzw!NvIl!d^|e B ”Q}}N'PJtz&>bosɈ ]М4ݑseg^~ޣ3]<S¬~}hջHf6W&F+=+TKNWFMx LqqLѥoۈk;ѬKLAt e iϟ#-0nܸ qnZIaUEs J^#UixO'3t\ Іy<ة3 f|[iiU`2N.n*ut+㴴mzѣG'{{Gps16ƽnUO܊vdj Df~~)S=zdee%4/t(]-6j̚5M3s0!o{$_5/%v՛^I ! F.vG|4|Ug$͂[ںɓ'FDD8;;+/?=_I/9ÝxA}`f I1hJ /bKg7^s1 tWSS#}@v_?sNS kwH6ghlqQp7Kкnv%%z}䬞K_ r742<߯ۢoЦ1쬻-.@5:OI[9̔ml䎫~$VScCW}Vߙ;~9S@VGlSmT@ys۷o5pGAvzƭʻfltyݻ+t6jӇI#VP򴲻׉W9:PoK$](Yo/<9%DZlwVQ(C>yshrFSʇ[whk*o>mQaaIWZJ+1E 6m"-`ڰ9+b] #)_1c$!8|)mbeFt#YA!f jh6e%G#`hH ur~-!\v2]GI}$:E(JHjʻRe*n2uTBHXXؠA!~~~\.WJƦ˓n&Etb՗*M-F,d0THuJUR" o ^s&|F3θ89s*p뗿NcJFIE߂plZd;?!W}v|]mmK$UVï)!؃BkOaNI+勚i JR%<+9m[`Tv3&3x'''33UH {Hǒ9='-+>gޡytUȱvgN-1ЏRr~y孔/j&wqpR2PXƦ]'XtE5as@PSKۖp|Bg/%ƍ_A/m֖H+WGyU(jجeg3pAVTX0w^PJJ B5k7?eu PH[A]]} i%gxx? hC:voTRG&4%B}[& @ H7&M@ H7nM@ :xxA9%9hMz} G֏K~}xۨf\;G~ BkRB(k!DP{o0SU;a/ f׫bA!mMleL;e.͛ ȬCeh8wffuVoݐ2Y3݌ !<|wɮroΏګ1!^F7dIB_9u)i ju⣹*.Ǵ]~1wϵnڍe1re̔Qk;Tn0uTBHXXؠA!~~~\.WŶMޘBQDtN4)y,m.US)y5a!d9˭HNގَJD2n"WJj1n:rGxdEefUBlN6[x6 mԂh{5F>7gn4 ir444^Tdk/r"{uSnw>Lhg\\9s`ϋu'Vnk@ +Ăӛ#:h}т96lr>|uS "AMu!sʝ,-Yb ᯲tJS IDAT~MyN=t5BlC<>gUP5BD.N\S[6m3MuU^9U>Y-~ MIܫ6j2{[OhDxm9o2x'''337;4.4 9nW FO1/II怰6͝l&c:ۃK9ʔ9I^ݬm鷢zen{ۻ3N@Q?ɜ"D sVe%^9!$1~V{Ӟ. 4Z6m -^7ˆ :ͬkR=3Nw7jbt#aPnO\(^c v>!rƍ_A/m֖H+WGi_tR{1C yEs J^#v-c0]VG 9BP%gSD$Őn!;uvsso7*)pj~sVH7 a nt& H7& @ H7&PG>a0;u&tϿy+- L&}tUe%H7y(ȸ2:NKKѦ/^=ztw7kXdMxD"|?eʔGYYYD"EmJӗ}uBK#&z?-/ֻyRؚSd u'O͍pvvVRsƄ]z8(?I򺪋^J5}V^& &F8&&ǡ[>|Sm 8Yw0]R.{JOX%t/ AF&ALVGZ.so<4r+~Y_Ϥmt+3v~T)_P~=-}6egݕmw*F==5؈0?Gۤ 6d߾}Ç/(( 8p5((Ɇ{&zwWk6'\,mq̜C|6+/OJꗧ%Do/ $EN Tbo:3o@QɿPcE+'q=W[h̻Rӥ^/׉W9:PoK$](]`;=yd_3nn&M,&5&M_jVll*p]lFM{AhkM.ګ0bp3vk12;raSQ&#W=qwY/r<֟?S^n| :s 좥wBlu fYm?qΚ~wi+~.FO7]]P-as6WD[Gb>pU͙:u*!$,,lРA???.b[mCISm&"{`u2lBwVM*\-*Ŕ-\{!}ܫhKd2@銌nӣ BE<~=EĒ)_,g7h'yl !ghӔ7vx!Z=1wx"Oz, /5=yY~-k)ԢwVjH7?✳ !]znwAQ3 %nKctNG~MWˮߨt61<'uތ'\@IPh5@ckv4fM~jtJR%/)_cĤ?g7BG颡F9u`:<  (?;MbgLZnLhU>F-]uw?^UK#| d?;Z n3, f>?7G4] 8g7& hn!|)`0:vL(1H7&;VZyELJ n@Pqqetm^}}7NLX_VJ[e㾍鵠OiwxME$)5 !Z"H?z,}6Gog*X*oZNSopz,}=ҧ|5[sV[BeGlk@X.sdž}ˏ339(t"cH7>tLæԕ.y; L!~qFgTu6ih|/;lK?V:~D3@󩲣7*G{Yw%%;s3gzJ6-&-l !%[(s)تW%ø'|W`6^- 9hX?B:/oF`آ_XM"^at&փ$hvmv[ 6h۽f|HaK%hBG[Kv7eL+|&6 / !/7ZH& H7ԖyV_JKRGiI!!d?oܚkM-SD\(7W. V9;s(bfg}(٨\p.ݸW=$%|}o!F[؈ޘo?ߗ#[e4}Cv~)!B_9u Jꄅ|t -붭EуmWP JU LJ=V + M7[Ȗ'*YO˞l㓹&ZLh UJ_= %uzJ 2eń?9ԝOmvwၿf_3O};&U/{MZ^5~(WnjoQj~:|ޔH7=@߻]Nc45D6h!Aޣ# M%-BӦv#6D}Wjͻ65h|M**,;/(%%(y&{YyYIѣIrMyENQ &EQ4Ejj^=U0 J6I-ֈŚ)m<.8/ᾴD@E%UҴtG;xȊUԧ ?~_b0xr_~Bѽy׍OtI"V\[N~ !DMCCMCE@ )% VӒ`bC#$Drޜ=,Lkjn|XYOHU6_z oV57e9,8h˥ND.N\S_e:K*ߪu=f; kʳp'i`d!!GXu&R$)kOpt{wЈs: uЏ:~V ,7%AS%D]{ny_}ݬB4a߹w6\{uZ+YT#)2klT"Bq_?FЯ3m@ 4fޡytUȱvg0zpY &4|✰9 lmM?+|iOk?-8x6..P ii8nd댍=݂$|I愐!O8mq}}FZMZht#aPnO\ب_"Xػ3M)5`o>!O mk;Mݨڕ,񻠤5yq_]uJA#Py! 8,%Cδ@Evർk7m֦_"2*U߉ BЀ;2Yfk7TyW?eu ;uuA.¥eqݽZ׬ooGJL`ꏈ-&@ұSg77&~x<~+O 6mz'  X(WUU! >Btn t& H7& x@x<: c΄#\tn3oWTd:9D&4EQWFiiiVGps,Y[?fb֓62ۛd ׂ>MZy$\ жD"E&!DK[[$)VWV7Dnq0m皲~'Vd[WMxΉԗ&H7? `ҧ]#| $z,C:Z-K̖ghlqQ; L$-f6?rrX[BYYڏH|BZho162.YX4VXzRez,zZ-m:3κ+ۢTt>Sa\H+b]e~ҭmi(AS^ڽd$ńvbǡ*G=Boݐ2YoS0_LI䑒vrْ߮qk[7sf|RU<-)$$z{Mߐ_x&/rJ"yg3sZ,,-t^ѓ'b,#2`gaHRe^'2_=<@qY'/=wYV45S'?3_+[+좤&+ךNLq䑣hB,MZroS]|Ekbo:3o@ɮ2 7n|iqlPR',5LI–m{nE! MJ !=Fiy, 5ԈZZ|oыmTol(/+V4XXah2$Rszy̬ekx;9hC-mMٮTYūe`0c'p s}7矺M׮.<^҆%AS^ڽּH6h&i!dv"7VVn޵iA;{2c ‚RRR!nbm5jZpHY'?y\-*5eQZ53f>;sYt>MPPSUȼ|Չc'p &i:棝I<^zdEef*iDU5#I8Avht{sL0IIk{= 7|=Owscx&{![,w.۸{QMiaqz;\w<ȜVl4J}-M9˓r$%F8M-zw>{flc3+Bٞ;`X~{qFR<v\vk?N'p !^"$R-E j+#B'$HP{WREC2s^A-t5\Jz#LqLQ!]m١'7pH !v歴*0L'g7U (*#F IDAT8--F^xqѫ"G7[=x';zO -\ жD"\3??ر׮_D{n>=K_2d[XOgV2g6GUk**{99h.^/tހɓ'FDD8;;+\]VOqL=8oO>~NInfOc_'.t?Lën@ H<~844400Ɇw.ͮLø'|W`6^- 9[m8Ff=Bvߕ̷nIyѬBI[IhZ֒nYbxQ:Q`]"^fgK7=)~"fr?IɂIBmJ /)bo:3o@~ [䑒vrْ߮qk[7s*Sʇ[Zܭܤh{ !_<۹ygD(*ԖyV_*Jr2%NiVX&PaaIWZJ9kQSS)P/7>E #E^wg|^`ɦ M`קPacIا')B/ȕ22fk@uŌF4M6wwCut\qoSScSBPkqs6P˶~宣hz,}3ߤڙs 亮x@VTfngl?|vr 5: 9s{YM{ I KGzx{_AV5-XjO(U=n2sIdiz TX`(򲒢GgESIWFR<50xMx݌SxΜ94Η/|[JRC7 P#6#1kdSVɁҾujl>Y-~dt{wЈsPUjj^mu49=ϊsn[~7m LSGnxW7nڮt4h2skn8㝜T_~sK`Ajش:flFi>n$sBHg'~ Wb1=]i6LXƉgIKn$ ɢk姡|ͣK,ͬBە<576:aΏe#ݪ2I@-8x6..Pbڮ6 1UҰ/KhS._H{hqƽW˪m[""%% Q+"bZWR{1C̝P(yW?eu ;n9j Q%gSĮC Іtͽߨ(ZY/t}AxV!@ H7nM@ tn_H$C dDB(| CMMCCCKKv@ (*,{vEE}uٺgO3ss &{0K"@0LCDeׯ_6lk7&b /RSL33 &|~QaRSGrcsL byϞ?=}3S3>/U(ݺyv֬n544,L w@n_L0z~c)/hnxn5oY&ff/~e⒒'ŇE'jj4߬"L&$D▵EHD d2q.g7AQP58 cJW`Bia8Y4 V f%az,}!~̐?*ߕ9Nn=jlU+fb[y8%p8Č۳@9aggeE3NhS烏_s~eذV@dq: ?I=s;C^~/wM%i=%h[FghlqQ㲟E"^z@,(3lcZ֒nYbv!h{#8pz8+)Tߙ;~9SɜZM+]QWSS kڧIN?ӢC9 ˬ0f91_4,|<`W[cf5,b͹{'GNǎvݸfV}ݓ' w/Z?UD~-%krߣdnV%_اjXPƵpR>hM\jm?> /mp>]UpNO޸5Yy׌[Rk!N"gltWWտyݻ+tvi{#\qI1{5$>ysQ.*NmkI/y|z*:k KӬ.go7u򹒒=>y\QWZe H}!q+<9N85opGXx%+eSߥ畳oxġGOiȯE~؟DT=ܦdކ։ _3 P+|816mLm?~99o" ~w(JH 7]0TGCm؜!؏0Y/5׮.xi6_1c$!8|)m3moU9GmMzZ\Hj_.;䮣$>qآ|Fh'MF$A-tW-)!#FRwlY+֌$$@R7B6m8!6܂ZtB u]-O念$f؇Mt;'~e*j"P!"BjFcW~NԒI7凓vڴ1autȡ>o"Sbc)8 n(U=z1ՐYH#Nhg5YQ}rx- h{6I-֬5ge%E&׿^T}q@ Q!Ey,򋊊 h59]gK3#WD(\/!'gVd՜-o9xiOEqwښzF;mژĝ_[-!o@ S$xVstJI YOHU6_׺!N=t5BlC<2moYu"'.K֩Қ3MuU^9 ]W:r_(oⵛ6$&~z0Z7xyթۿֵz癲AMy֥gqk'AXڷةX*6Q^J$xџͿWɪuƝZC25Bz~hzN**N6&_;{qXhݓԷlŲtt:5X MNuzcҾ;4.4 9nW IG4~9!E~N?rg7pMCZs&wqphJ64-d~N-e J;OstUX_9ޱ{a;k8MhpLͻ9boF?xWj.#5;K5U#UfHicbxs>VMB|,-2 7n^AeUw7~11!Ď 9zke+',jAU2Z>w(9|eݽkguz‚RRRPBn[vx68" A &?X"d*68@[k7PS322.*,@(>LioUnt,nDSCy_q{"pD;x`(ww } .]>b0"ZSCow=M@`2FFc>¢[mMMjL[>iXjjN%SeW._OSS q@ :!zmsQXsMBSؘU"e2uu{fiFnBn}}aaapBaOWc054۵Cn(Th8) nMzp?"P$"KTc2o"MB!E O?0 55 --/cEwܮPO55Ν:[ifnnRWWaΕ˗JKKB&i`h0pS"QwbkÆ v ܇KMe3̸jtEKM9ʍ1a0wqM 0IJ"#2DPPAR[[u@\VpցU)C}Տ8J)dYwD ~~⓻g݇㹻iafQdcm#J*W(n5m;{窖::9Sr%sss#vjMh$RIyl566'7 ÔvnZ͝ll~)df!Mh{ܱ9mw{fkd)b>!CG/BGPzEQ.h] aB `uu`&7SU4d״bq# _T<jMx 83L(he=t?i-kbiK3B5G999/8b}dهmu6fԦɣPգMx >~p+f`Nĕt3eBβzdϕY<&BP=jr/'GwUJ𐾖b!SJT^Ė6]=CY"3g_g+i20]U)6K ohkɁ ݬRK,B:]:ۺwHI}B'=) beӼ{ ;Q^7WC}:$IT[hAʕKn ['ƩGiBҜuac{v7+mnxK"0t,{ U jfgr%BH7Q\۳Ȑj/iZ[}verSMUX,QeNMx^sݺw8~j/3UA5SS_8kׅ‰㳼"n?:\O[Қғf몪PBHÏ%%5{7Ms?}o&^,3>IW n)?}jGSbbʒ3%#bng܊^2;Ֆ^pV}zjɭa널 WS3g ܝ㌑λxSa ȹ'-ph,(!}4`1k񙈴'ő9t?^w"9کPKZ֖LUu? L&m-wԿy#!ﯫ߇ 7giR@./tki BB !OZj[%rL_PĶ> E\_,Z !s:FU A^qʨ mcJQ[歮k{ްoֈﭩGRBȶ?K;>8cczBB[F}1?t݇yZ b+ eaMѕQkUʖb2vH}5csإZR2CO&ÐiETCVk}vPBCg푈!]m[|Y^-Zg\fo2?`X<}k ?yAϤQee3d[~ U-oS2EmINɯJ*Smy~zvv)-:u8bWJ* ;ł$I;#iӄwAM^{j_=G{Vɪ|.dLS g'/:SF˪No 7]7^TyF=Lx?]~^6Bݣz c~IOӷ~ؘa~4.tT S3u Fη3o:>͘#%WVUx6~p=|⨚YW[nç`hؤ||mqmoNJ~.m]|W2<8ZɃ6]t0q^ӏ~GEǍ, VrzaZ;}$q[$XҽK1ܙ8Ekl]yh]OGzu:OW??>.^#q3LKgc) }z;^P_yꞁ={88:^5>!ՍYB3J9|}_TR\hw} EzdWW[,X$ԉ3d֢;8MJKX "t3?^OJ%Ç%HVwa4~Gs;v Mhݖf[;ӢMqiKJE_pi (=iZ\ Ͷ~hϴͮTL'&!<.3gB "ϴH4/d|~qaaӾH x˂pyܮN<.؛vPean8bCuUlͭI}lvcciZ^<q:EӴSnl6֢Rf$)[O(S>UhffhdI@ D"iwǵrOgX. sq77q77q77q|g:4 Z+aZX,bS~Q n@#rs5fv0uvqܙ n@ B+ׯ9Prփ SƖ -Bq35o;!p\G'g^Ndnnndpl SZZbc۹MYyL&!@ir ʹ޻ZL悠 j0z4, cZ GMh}333G][[,=ztffftttBBf q)cbbAǎ[[[[[[;v:ĦCW7mzn:%%E(n{ IDATM0a„ yyyB0%%=#?aT>h͑&p2qn]~}PP?|~~~~~~>W_^O+uھ!ҲJsJ"4W^MBQ… TR ?.\K3yØk'=mRۦ8vF}%]!zg"փ.PȪogdo MxgXXg~掇OV Կaj|4QcRۺRf~Rew#[x\^yuhpS@er\׫2,++U5U[[rW`\*X4J%wgm+^O* % nhaGLܠ쳫/[C!* %&4 yˎL& ?SsṵpL`v+/꺹GTA3 04*ۜ|}I۽3[zW**(Bz9jP=BQuu'{}arK7/ Z`7g+j*l%PY8u%~:$]8{JIe__ %ljɛ,+22J"D"?||||||TD422b銛>W]q߮BKl37gB ۈqNO; B8,v<,򚆸 Mhh5+4!aBQwYǨQ!Eݸq]Yhee5q~ ~jB˔O |KU{5f/1Opo_\X$(j()*` |ВPlͭǕI}l6 ‚kW{yxx8Ђ)[O43EnB33C#CSMhAX,ǵ VY,ghd -s ҟiw0)_swY畓fW'J_~|))|}0 } \;IУyo2^.-m-p-.n E֎W+OE͗əSooq#0wL?Vk9fךsLBIɕ!}--ĞC^)h%3g_g+VgmeBuL]݌|`JjR!^=kTΝ}&LM^Ė6]=CUP|kXk{9Z9Zrம8@QYYQYY|q,pO{ *yw}FV)ciux,-W`ŔMwf ^9a!6^ÕwJ!%i~ff5ࠇe%בZ+tFZ?[L95_\T|kXk7KJK>7j8Mu]tOJ ̈́BIYgkgZ+SZW+\!M ,l䦩feNBHw+t'jt^ "n1HzΡ7d:sNs0kH; q7uv3'^2IENDB`O%+n@K۷o5ݒG+Z9k >xQ:Q`]"^fgK7=)~"fr?IɂIBmJ /)bo:3o@~ [䑒vrْ߮qk[7s*Sʇ[Zܭܤh{ !_<۹ygD(*ԖyV_*Jr2%NiVX&PaaIWZJ9kQSS)P/7>E #E^wg|^`ɦ M`קPacIا')B/ȕ22fk@uŌF4M6wwCut\qoSScSBPkqs6P˶~宣hz,}3ߤxg|1Oӿ˦7-V$3KB ],K}4 O>5<~r_7l^J믿d!g>>#WNxxA$ΝG`nqD Qolڲڭ B؎$ 3"X6!SYim;arP{k>^F-2t @@H bwZ =4?=uqgϜ*L2^5EFı34:y2RBp7Z{`xl5[e CD*QSL#WMvJL$hE[mF)c3d6\u ]H+WC wWSv9o@s.Z~M/5F4r7?p70-Bv]kǎ "=BEUUEmuX,(JVU5]'s۶(뺜H46BH4!P|ð`UrJ&C3#I{B3ƤOr!!cԥr$=rvyM)[@B<񹧟z26WGu^ i=szM%OK }=]=dvOuk !ƶmۦZ_ox/||X(Cvɤ|<24PT:axwWf. o'cpY.p23 ^Z.V㥅g}z;T1 hY*&&6=vlx\.r^{]WV-seaZtLӵb!7}ii׎H1zvLMABFs>T4:Ӊ⻎f,d1Ƹ'ݍZexxx"9???>>~T:N:9,w7X]7y:.Y \l{B{!peJtUBC۲~166vGvn9i4L؅q]ƺgKs~h|}={H;qwܱ}vBi_Wxl}IUIw"ԔСstu(N-JJ(j5q.V* 뺲?7z=d]7!1JIވFA e- +FӎZif4j-dWeSNjyqG4=eh<՝_9u~lkj綺z/}>w.( J7kE"FSQYR`{ !!!1|sA캁 -I"iaSxkݝH(+h !$R´"3cTUS_`&u^^7-&xnvvu!S?l6% |;Y'RT*E"*B0& viEcB h67o&\(Z-DzL%iŽcʗ $WĶms:l؆1P>caH:))=>8X\~()B/NãïM[vH޶yd"?zB/,+fLS@?;0=7 ^-D*˗ cLˣ}B0^x/S7nA+U< 2̶[ZWgnK!=v|xO5cXB`i`:0Kz!EHgRU덈֚.E܊1Z˗ ۋF-\47T h*F9~\V51n&HoMB!!xGVWY#2erEoWg~kW~\v}XUU=#cht]#}KԂVٚ!{\ZX_\ !&ԁQ:Zq]7˥T*-"r9&P+T*t]l)L |uB0 a KJ&f@dn*juTPZV[V<`Ӗ-EVg,d fʵD"߿֝Nnٶ}MWB.88 8jOF2 ۝zů?۽\m"I2bsSszgZw"aC{z!|/}ӟn߶U8"Cayk+ 1ss3j-QxH4s9ι $a^KP eDe"J|WA0mJUL&S q#nWr%BD"~к:ncMNXIءX(3wB~AyCumR}{]]]H dyS):gxf7 kӨ?:~oa._ꨐSx^8зT2t'g.)( `n%yVS:|ڦ jٴ$!3s =پ~YniZxR00](Μ"'z^O}k#N=#wA@=!PsLL(Ll(>0tRcX~zkozojt|;}w,o99<922&Dr΅REP$!BQhOȸ(my?T2wIENDB`gMTP/web/images/Capture2_thumb.png000064401651440000012000000250321167500173600177540ustar00darranstaff00003030200010PNG  IHDRTCY sRGB pHYs  tIME 9 IDATx}w\ygw&ٜX"1DRrIs,˖}嫻*}>/,O4 IY"H@,؜y3rwowX‚@v{W׾e7ZiB (B9J!.A(R@dׇP0#H=S859Hh( ϯؖͦW CÑD4C BZ)W/ L&Lx2)B462,R,,#=7ԜIϳmۖeB!d8q*!9Yry`66/xuZ,K)ú͋вMek\6'[; G[ss,ı-!%sjS˭ү<~,l7J(ERAB41 PJ?@eg}ja}7@X)WNwAHJW3 {衙tg}W85TDmmǙ.?X_0'jV DS󶢨eC8pazf:HIJE1|~|*D<X@hgf{Ϟ%qwcBv%oټyƍ!3>`4| $HD"rY4U,Υ3?+"-O$kj|o}3{sǏiǝ>nk/0 [[oTض%/o|m.+ 555ͭmc#æiLfYmî)+LJҧ›W myQ8q]q\q(,:8('"xϮ%(mlPQB*Il[` ,oYCQ:S"`dEFB xMlcʱOUWv9]Q8m#mRcq)څ1:q2q %ش,c׶, Fr_`GH@իU"āw~G ƒ,{P΢/ 뺨7nذ###9U'招׷ʫsw]}CYB0Z_W>Xr?Dg;PH0?wSޱst*b(Q#ǡv,@Uٺn$J+H82809=Y[@bvgg ],%[e@iZ-($ڶ1MNNhmm q.CS)v~O]+$C陰*LNoxx*9twC@JB0BNZ3)#!l $f)snbUgf7twesPXœB],* Ahi BEURX< M{(]Qbxn jф ުi6ofNML&kk;vt P꺞!eMOO5U 9LA):N^,\{ZڬJ{.0˒,e-,ϯg2}~i+,P$)}=B@8Qd&,-?O&~w?o?} >J d?~_<s-7^Jtk R׺G|<~C'5%JZER%"MuLT%E/Tk&&<_oiI4$A/Rg-;]FyN> }˚VT-X 8#BmV~JZCaX4MS$Ydr9$gfmHtJELSB><Dz) ؖ>]rG;7#<:+i,@8f+s鬮(ÊJY7\Hզ"rlP(0 0i|jZ[kxVnذ!z؈z%ϹL!T,pxp`ph(M CCCDsscsc;,NNNԐ*mxJ5Wc[ӄbB("2bQEYQy%I,jA øaZ Xi`9I!iq@ \9"Y+Uc !0tH440`1[_J^ i9[2n !BB)Babzۅ|^VMӤ kڊ:~g؝/[1M6+|x^c0F=a%[s~hZtCPE/#ucc_'"o2L&b%UIwwK*8*pkXF,Da#jdʃ-wJwb۶Wq`2jj)l)OuْN 蕢mYuhX4tmwXԦVَM+J2UE| e[ O9m۶)(k% ms5ޜ+cC*ZWH2QX"O2 )^ "u>-KHӵγ)hu$55)O2_|^̮+|0,?G(hL`ι%vjlCK{s-;oҋ?Ԇ>4lմ?7;bƣ#'޿,$"5Ț9g,HJRJli СCs@׍w=Hw)]7JRkZT,2@Es_ EZu(X0m DocSʕ, ﹷR\B,󰾡6 .iK_^ c$>働@_z&N0sT6*Z^ DjAO~+A)*+tBߏE C bB[[ Øa\mmm gR$a"6ut۶ݸM\L:1!P!B B3$`!H1Ru!0 +Pp7~K_Y~OcS>.'6qɢN2SO{.dgFBHNSk:n>0cX,ݻoDRfz_ĮZBO ]e(nƦVU18y$'\!9&)d9Q2s3׵mikJitfwEdCIJ<D6>v,Vmh~E)SEFj6_EQ9Ijk  u +Fp8D_'0>i("!%ص׋`dt|hh\.%-hCѯX1uWyx4\d>[!H!O>R`nJ/梦jעCZ 6-W_Bf@M2Q}֔Z1Dnn\B]IZںu(côeU}Vm[ ase4HJuw %\5lپ7FE+]]##D2I:Z37;;o?}ts[g$ U  .U;12Y$Szzz O aB(Kӽ|>xA2$/I_LD s Dqb|sGzUL&_53ggfΝ;sz:ʊx*tSיs,$sӓ, B6e8JCipdxTڱ4Vw[ݻW{w;;у|20He^}L3bKljx  ,5>tn-w^q_ Wt@Zw[ǵzkم`6oG#Je/!jjkf?*Z{od 3O _I&^~K.IBǶKUX+?ֈiO\0͢MG7{ySqb(Bhjt0o۱u-╟%39's՛Zd hnnYR&)n77\8 _s߿eF./n"z@3E6muMRJZxI^r^i+ܔZ ٶ3OySS#mp\ 8YUTO^޹sץ[cfv.cfvz&U=OGX99ysG b␚ m466}7 Y3"s:wfӬ(;޹~?ٟko*Jwkj0@$,+##pP Ϟ%jiI{(nnjPs3ɚڹ63esٚde}1bX&Ig2}}]]WCB h1u%gnjnkԊyӡ6GFǚmg"SSX$nڴy% 3ZQFRT M%6 @Uid&ɆnXu94[;;zhmdQ'B)x[˾Oۚjy%%]_=lX5<+1Lݛn{sߏ >^?x}:P Ը8˲I6,KUݰ5JܤdO}[G䞩ѱH,Q͋q:"t`PcDžyt(8H<]Ҧv{#(!׵6|S Jѣ];ۇΚ idӔ RJ10m@ Rc˲`jgLT Oy(^H@ a  Be,1؅zxeYۖ%&P C)avy`G%Hgr?9Y˖t_+fdeΝ7Ve<_={(+Gx#_̯Z$97ӹ`nM!VH̲8B vdx;xWwخ[c;(!@_ 4M+"IcO9 6G3a8${ JC%QB\-w|UR|_ba1Ʒ..B >Ңu{*󀢕ϖp6=K(}p#cɰRJ8|tod[O}VȤ3t۷<|õ![?/9O)Ae@e{}ӲLVPw|+8 `200г~}6RL(V.SJ~ P$QiZ\zxtH,(H9UEQp!۶B!!۶y9!Pj&˂3Msnzp"-卉.kf%&Z9O<ǎ՟ɟ_l۸cHqݹ}1%A!F3g_)J)w:WBzw-Žς[J l_dWk驃'ϴvuŜ *?˲~!.\nSJk?_lBNo~M::>T U0*r:="HWp!Ra0@\0t{N |Ui<{ @%RBA?S;#ޙ3gX]6rر}m|q\-ż^֐.,)CķӹLz`pHVKCRQ*w /46> © fsl]M]ii S 333`RV3,U|iݹ3[:6<wU?cx;h2jݮIL&Cx\V4aΝ;Ss}B' 9ݲ@Xd<MSj0|dL׀C<RJ>B!3~ ,)9_CJ`߄\\N?9OAUH5 Ƶ7ևO{| 8u=TOOOOO*p*6%/RcYի@VG'&?u~|?r [oF#aV &8Gw$Vf5[[^}eoS B;F` f' S4`t:=659e-o)uFB@`ʖTDI3g{i6Vix|i%9~LRtstt QI&&+zUx l*kбёQ<Ɨ0)%s|)qXNEe iFR1f`E}>r9R m\u?Y^* DžrKP!<꼧 B3.#7*.չS񷳃%ccEj۬*@PN 3k"r ߿on>mO? q\ń`u]..u<:yxWٟ3&Ҵ$gs?>36/k!H$K-MܹS=uR5?!t>I&,Z)+Դ*$MLN$*ϱ,ǎM$ Q v6oWF6l|O/!o#Hˡ҈ IDAT2Oॻn2?=r˝ڡ@H҂~K׺!YBb ÇɩB_t D~:$ctM 膥s(RBξ?65?11919 \ʾJN<=8h#8[(=~7|ȻVZ—/$.cXmyۉ#o547 }0 wT~W?dߤNɕh@[Rib֖EVP$QmYBSKCf.gl<ee`g9JK R54Zw-x=w! 6oڴ8#ҙbC)br&3|A 7:mBѹ彑ζR)с1nhhu.fH/vo:"/dcKTJU_6M5TX>69ܛNں5?|?ħ0:wF+1<84vϊk ^#sc|ooZlް> <1E//}EEE`Yf{lۡJѱvJۓ+P !D+W+ŸnQ:-pMlbpX4|G?馪}ŗABizn^CoӚR2TU5,՗_ijmwtf@1`R4ujfRE`YꦽaR)=Ɔa!]]iHqqY0v (:ñ*73Geڧs^twЌF}-:7 czzw_;GO=]E ,.8@!w3׺OEQaXJ~@߿n]GOz˲*ڱm˱ Pnl, xiZϻr(q]{bUqC=X ,//,$ bѴsZ{®=?9u5b9p|etԙdԿh !Dk7і*5y>ը?0%Ugm'  <˰e9q(^s,z/UT*9;HfK!HqůDnt_qݗs/tMݽ}}`$WF|ɝ.k3?zo+ G 7deEvsmXR PbL B8;5NWg<:.QUZخYA,@xK>hG!Ђ(tvf ֗D`T)IԤbB^lK0*\s./ Gc9" K ֊rlvG8t$쟪,q/"naE67 d3G=>RbuGaDZS|!S/aZ#,fm!qH]^ɞ5>zCh{cJv*H$z /6=5qR1`-x[x38\z \!.s{cJiLrVۥWO~ʫL 5aR[V-{K|wWW]F,%ܙSJ0 Bra5MC}R Pmsyph!!b峅IH`:/FUeerr2! nܴ~y35hD^*˲ gy*2/JAȊ븬e͢xn'E?Gk3k4 f82?TTJ<'{e 0YVEa l8`6P ˜R @m0f @ D'!De t^2@;Bxy.ֳk A,޵g d2w= suvW +o~OOQIVq&=kNu(=s׾իWooLR73F!˴ XPn;QBBH$jUAMǓ1 8wr,txꄢgNuk3ϴ$jZ\, _<=Qp˵ CB-[D"`Ѩ EQb-q۱ !pzr§ ƣ@usJ)sX !qglSFy&ԲLsF0`PQUPɉH$88ob1BƘ1}z!B{X RawtBbmں3P'''-fηq\.KgҮeb~熆+ []ݝm.~[tww `BcRbF[0LU.5Z;Ǘ $qb\*W;/z Sp8JrG躮iZ{{;.^ohZu]KRn _tUpfz*+3~ha~VW9깿85R6~O- 8d_wVUecɒ?ѯ|E瞸tkhS厀_}бTz#oIQcj[nmnRk{9Q4׾&W!t84;::<DS#swB HQůx\^ y?>cDT.bQ0c#,;_> lmm}á=ݝ2~p ]q Bo1!FiY׶_ K%yl-TF]Uhz-F fRReNuI ڹbzz:ͨپҋ-p$Q+ZT(x[Ǎ~u/{晋/C啣G5NoXCUJezϿ>ߎ&&9A]\kKtm<ܸq}rfjKRFn<)^ p(_1*N9c>Eo7˒25>ǐڶe˖ǺkרOQ8lm1 T1@A0:8.q wRYCpP]O-/-UήNӴ5C_Z]T}>@i3CA"=亖 Jcx8 (;m^G.WQNmvqaz,nmlP-0m-1eYD?q̵!q]F"+h4 ݘ_P$aǶ-S35縗^|J-&RP)f$IL+ΖEbQНa̧۲(}/MMyKy*sf?+CwPG~= ѵW {Ԥ:h<2й4aM_4-@<ǫ8緅K 1NVdEiiMె{*7t%dey繭[fٹg{.cIڳ lPR)Lkk؉욙YH{Ȋ<22LKT#c={vS/7dYҩ~5 cjr7PKu$Ge`/O-Vb-'iru8=9!+ 4-^)qF{lKx:H)A((eY,{^>Wc&-JXv]ÈyfaAA!FC, <|snlO8OeEb-ghwK^J<}l0.D|v5)eY_%IB\U>Eζv`vvhkXQX }2:tmx}aǖeF!V ! !"W1'>|Lfs<~jUM`̱]_~o]C*fC]r뉹o$w{%.K#0~h] d< 2,}><yq:=!iji} _gϋ6[ԵU]X%6]x1L.//޽PȧR?m#`af~nR DJ JOE!N1bш硄ӪT)Ϲtpl.L$d"8er1P蝃/>|xa~Z͆vJ;3GfQ7&fUN*2Ξ3gN۶ي{ cFAuw!b/-zR'hjFVRQQ7]p`lݯ64 1ALCx~x&/H8dۤPȻn3Oߨ6+B)|8YɊğtR<qA\0渽{k! rbeYf뺎!k$VXvArvEQdYx̾CGTU [fgz! Kp[3$2izT.[|@g{߅`!בW~=ęT丞-_p q#?'-?XZ$9 C@oټeC,,-u>S++Uei |.S.cr$-kkuǶ@0dY/%f=|CZ^\YX\i`0j!D$u'9An\B%M^s333Bd++OіԀ{zi:[_Cw욛A[ZO!'{`oOЕ:Q_su;_vaȪSnO&wήף\ؖh[^N=\)277i`ա6/f33G_{sddkꕣ~5 n\BJ)_xCɶm#I%0mEUl"!,#Y_\$%a0 !ku ."R˲eReY$uZ(%R`'+<FIc9wi8 "ax J2pcؙ8!UqpZM õZ/` džPiXcM"MV [nJ<;Y^MyEcTw.~k$M<쑫VK9o{R-o~c(W'HE߽gϞ'NtuwCϽ6mBؽpwwNn4fA?A*z xO~믽*xϧrݚ U/ى)p-M#Ptfzj`_ŲFI굲XD#j…[~ȭHEy陭H>ebbBy:߸t aQ!L$Ĕ%2(О{/^"^kuM_~jᑑˤDۖ;{x,kO&mBwCR̾[Bŕ)½%|-R+ VԈ|.ߺu`ѷp:U6`9i;<)qpaq3qhrZw^r+ِLj䋛rJ,r8eXr00ӚH@syQizCwk3tnt:%nK-j}~DX{ QXoo(Jj$f l*9ՋaQV_{]ݺQJeeF12?Jߦd{ŋoWT~6eYр{8TRJ|~ձꋫWWg?<3|}/ßJGlq.!B˥r,R.WS(y˖ʥ3<{m$ZYZzC?9. BSU;BDrTdayyi=r̲̬muFoݰl\.3JOѨ?i 4vF9lY#{`s͉zNj…C/];yLcݛ'Ɓc5 B#Q3љ/GN*jDxHvC!V[1lص/8)rk?^4Q?/}!K+eJ^b?X/ܴmq~FW6u=c6s_+@L/tx$4z] xs\7 2f6cL%e" MFOuUQ6]KX7% ֠j !\?pwbi2hku1'b\>h~l[[Bu pxN(60mR~ǥĬL%]Ie bcAx~eeEu_FTST(b<1uj{6OMMO@_``xQ\ `C~Q+8"](w^]^hX;T]7!탫ʑ=w1 ho J/|׎{#@\3ѻԩlTsܥRwu ؽ.\g^|%x o)̪|-аRE4LK][ZZdXQ״3Yp_LD"rYM!Y׍`@5 s+%Ҳmrl 15`f)^RDYXͷ'۷m;{jK4h*h< B\/}+=sS˪mXR=r<XZI%}~K$7oߩ|>qN)zhW=pDZ1X4L, !RT``az]%bk,m˖|JgBQU~XgѧpB‘J0 X.+j`Z>^Ma@G"їhobߏnhe=M3(@H;0p$HOo6MQFf$5`sdBQFbf(M3dŜ=pH~J?w[)qrc3V=>t +S>?E$.q]7Wg23 R03gΔɧr\.(Դ, qexiqP/yAu6oؖ!b\j5òW BGķ":::&&gfzz{Gnݼyk|~aǛKZ;{}R(\xB {\.*'HrĊfz*e ʹ@[[|pa9C`G㚦 عkŅJi:.˓iB+CS|.Ѵ¡4BmIIDAT۞ni;0.Z @kuSuPgg´&l~5NtsYqE G"7_McRVX ts0lx癹 ŘD9F 7Dz i604$ -9N`,Ҹ|LE")L=sz)ܶT*Z(NNM޽2p\1qcyk4-*PXQ T,I]  UgYf$%aL6MSdaЯQuXju\*6 ?5v <8Um;J_-W-HxddDUն^'>L"e1tOo=?#No ]1j#~O}.]/Mm<mX( ]۶Tur[n.,H^|ܹw HӄgN kC7~М BqVVV Q[ʛg{?k׮m߾ٓ33}]JEz7hn@ }+/=08B(˲a7\MݳM&׊~T)y-]Oyvtt4lr_ SbroյKl^MBiڶ<0 @$ytB:9EB4,I%G3m흊`6aGq\E ݐ0 Q0\**@0ax|EH9̹%xY)۶~Zv -K$F۲|K($ nB-`}(,eY " 4 yq0ƞ;m^x,$1Ƕ9~;H{Iã3fƘ؊,[( ŗ!ĈC<.+IFJZn#E rIS)e鎎B! {vn4lXMn۶ʵ!788hۖ_mۧF{-/4Mf2YDǵ^>uws aa(c{.O\6f>[)9bofSdkkK2ٮ&!NooO&]yNDk쌤ٌ۶h_ؔl +3Oq\00KRTںy$=x(!co/+jL絯WW/;ޥUYQg;;\ܹmO煟GY\DEɇ9H${x<>;>j Vy^̖Q.F2Ҏ}CVE-B LӲl' 0"HV`8BW0ѲDHe),&m 3xQ,eI<8M$L3$kw&Qk45r%}B1@,BZXNџ vԴߧb[\^å|Z&R!MOOt+\zT*aNt'O) χCD1J_|##!<9pȑŹnV ?Iln|f-s]zi݀رD,hBzf1k%,۶x@A-]o]~07]|O$q)>/12v.VkׇC٥+g;FLf}ZK L7bt*U,W bq J\6(B?J]8{b7V+eG6E?<̃x|B!8j(z -\w-#=qPeY^ Ͻw]=c{XMx{#gbP HVX\b!_,Y* XLNyYczݳٶM8N6BcP۶!9<B_Z\\ZbfurzvUH..kuЀhBzm- Z]u\rƐ ሪ]ӓK˫ѹ=w]t>OcL8 P^v#ҭj)kDeaf|feg]m չr1>y뻑p721I=#B(Jrf̮a^OFBր<.2P5CF܃%hHP7S Œ(aX?l1 =/ yM9cY&\X m׵Zkk+/y x< =*>T˥Օ];wr͚[{Cg-C=K|ekµitIf Ŏ.+K8!P@ß8W\9| uM,Tkn4^=z"Hҗ.oI$$#j{[;zC3SQZ!ջ @ꁀRZնR0 .!R\w~9LCDh[^^]Lܾs`.c-ץA]{}5{O>Ud9x.Bq]vcJi9Fcm`[@k*/cj =_{$~[ D[f"hPmzJ_o/Pc7n`^F5~Co MLsqw޾s㷾/V*Zm~j,ktfJ k- T9łY4wÚpʆpz @k±Ve9(xϳ#⊒l:WT&ݑLZr 'u Bs յZM$C7$YA8RiK)yq\Y7W8 bji >IENDB`eNuI ڹbzz:ͨپҋ-p$Q+ZT(x[Ǎ~u/{晋/C啣G5NoXCUJezϿ>ߎ&gMTP/web/images/Capture6_thumb.png000064401651440000012000000251621167500216600177620ustar00darranstaff00003030200010PNG  IHDRTCY sRGB pHYs  tIME  6$݌ IDATx}ٓ\y9[L,=; @ADJ$RD⥔DRf1 ! jZ@',"zr9˥dܲlAgc`g0 1nZnY7 =BϭrDz,"1 O eP@vB aXYw_K,ˊH)ekO,"n-ORnڗYmޖRJ7 !/_=?Ӯ(66\/`0ί/J;zL_OӪՕbLv,* n6?T}na‘$@k;ZųJg/k G/zGs(,LrZw2݅`8dY%I_[]2 c۶nڱHo[qT% {DQDnKۀCM!ss龬3P@EQLMM80 0𭴖Ol9z.A与78+W2^+,-oF}g=5 o~&pe>iܸZͦ$7څ`0P)FZ-= SVhR@1!-xe x_PjQSiy!D)5MCvn!J\{#jW6}skvs.JR)H}pZDJtIirBfKy`0$"0@r@ a2 yW)dYn=}[bh~vFeBma6 &'.iZFe9N$JoS1J!a:'ۖb8 c J$R^ehDю[\Q!@I^A|>՝i6AaM4m˞|< cʲ`yi9J%_/$+$$ۺZW hjeXc(0b1 @s,kMð qc \-WbѰl=YeϞ9Τs|Ow2_( qK9# $4=xcDeuBB2)H}jͬ{3k/~ϝ9)-X^Z^FEIڿ'%&C./qmqaG~NGIxAz=O$(fCe |>LѼzkۖifSy_y;Zf !i2 z r$ˊe[>3ð$ˑhŅ++-6|D/x{yI{xsgܦGnmy,/2U+je^;S*x¨8Y"&JwܵÍ4]c~+Okj(im@)Ӈ_`rQJi0S\-A07N}ŹyIl%i_vPI`-Rŗf38s;B$Yk@cz5CוD!n>BeYfd2ە؅(蔜rAyƗggO'Sz'̯F9CEAgt  !M!!A_ՕŽ>6أiO/.G4e!W Kt)dY߱J{ b~`p,k噙ci/r]1C`hVO rH)]5p\g%_ܳgOTUdPbPcvTܵgn9FUlVݺ3AX1ѕɓcwx5vs,žcpn|_.Ug%,˹7RJ- q&e9 W&dE Ҝ!])ڣZ@@'oN߆*]HtN\EOJ`r)^ѾJsЁaC7ZvrV-]$k~()B0\[M7(02FͲD"u]Qm;`_XLjmqqdlW%;Qfvf:ųhzfG;}.C7jB+NOOHVÑ{vp! Jxnn"gCm D<~yr Sn Aay g.PKo"깖gʴ]=iZvޑ @a|VyyޓVL}X R(zDdx4 0mD‘R)葺I9g6*\ N8ǞA~@N_nT1O S m;FRo{{jjj~u^zaaz~/ GcڶGw,72`e +w@J|o^ǀē)]eE1 ݲE  x8b}رmQpm9 bA%$-&Y:6L'PL"@B.#yv~t7 WHQ]K-+0eYZ-ߺs}jc Htk;5-6hE&whD J7DeJkń.ufu(M ֜ d2Ku%\.NCh@ pر}1 jlێbmKLf{kXRCZ튰ވ~ ,02\_vhfz:I[+.{޽[@VW޽cc[xC]drDQ*M-˪VMs1E*MXQad:q%1t},WJG#3WJFKsJ\*M: +gk˱rԗSG-qDw7^ş  i + v V _ V+g}>֪U0ᄧ7 KMJ)8N`RP5Hr9Y>2:6ʼn8[B1xñB "hզJ5RH%Ylo?p8 ! tɦ#v[4J 8:YN 36BڍTWFá5a$ HG;/qQ@V &~J yuAPлX,&ITU+X As7TRL$I8nyy9l„Ȳ1u=x?[a=;@J.6Gca AF/ӊ( db+ZßڻsqLYc~gh(`08J4)bP*:yr1|.{a,Ժw ׵ @dve<@$_8}Kdpzs]_|kǎN&&31Ղ׿c$Vz7GFF;FّmdX+/؆٧<8{r0*@|B!3ƓJ)o9Έ GbڭiIVqBX{°m7h4le BZ6ڹShՆ .^4kj_XZB`t0b꺮dJt!`dbM`:DcF0Mn)<u1#K˺5"()ZIHS l7&-񜉫s4sx25?;F ,z4p8Z 6Zچ*RTQeQTTCga mOON>zɶe_}D"ۓN4?ڸ;`]g/JY.\?Z7̮:D/>ÇSJv2=U+Z>q|}gϝMFbs+6yJ2( i W۾2 C^fN-ӏ³gN{zWg߱jNV9,\vΡn\,^vEX2 GOwp;Д &2R(:xxZ"WiٴyjoPfEQC'fؕNg2݂(.Ma'|zN1Q)RJBcF gl;5b:04ǫt,bطﱫ$QػgPp8H$B@.cX6ijPA=$ z <qfm ?vj bcx_R#6O&^e^8aYu=?eO21g$|>}vPnS7o+Sd#V*uC0gN'Ά``R CLvbkfq> !+f?Ef=e=}k 059)u6u7;smڢ,mT*hj:  ^֪ Wxoo_W ǎTdqKN 8qsotVOtw:y":?'n:VB˗.F}yeovf*ˁPtjj?4<u&MgnJe'O$oZ# rm}KwteY=h|풉#Hi|Siy3 ²j0V,ǥR(3kJ iWg^'/BRozO_m;ETJo;՝e2QgFg}뭷GG0)݆A,w=㟍Ţ~m[;&^kXϹgZו~vjAM *%I O?>:6 <(A4SPR]EӡJ)m7jZ͏x. ;@4]>0+ .^k~`jwgRJEq]WED @uM2"Hޏ-<6.?鮭 IJY~xg9)Tf vh@d![o#h2$?yt,U%0EpObXinh|QSoI0#o`ek@2!R*,2\)3w {ʆi2 އwaM1^`߫)M{HC5KmZ0h4뎇eݱyűDGA"ܦKz!5eѯ8k'DzVEAQGacۆӟ_Z^ G?vp޽=PF[3@ ;o}R2<2ɤOC@0ל_t !4něܧ‰L՝hfꡄ@N^[yuѮF&0Z An ~T<$s,cYh,lX(r0}ejHl뚪TX4EbI-ˢzwlf ʼfv BXo6Ei7+ bԕ+S3ijZntqq4j\*V!UeڼmZݲ-YdgcbJ&&,RE6჉;C?O Ƴpm#H~{Bv+&.]?|DjKw{l51[ұ)2W'&&:a3SW>ywh/-..v_x4u xn+W&:7Y]]?T|>_, KeB @&NtKf IDATۿ8;v?j]݁'N-M_``G4}KKevO\]+KBza{ٳ渇uZ^!YZwH=]v E />ܹŅ]#5 +_{SS<0lT9wG#SoB_y!`EЯތtezO8A B80c#=kYf6;TxQ E#!UӺzK7MsOc? =dTfkwJ b{IH]ץKJuF GJ٣;M[[ϸ<@wѬʊ&IRmM|r6+ v,Z kzcN,<$Rj\ u/}թ%y>>OFZTq2eezP"J'j`L(eYlUေ ?(<[qjZcg4Zhَ'>  ~x3"BbH6w0]YYZEno,r(*T)o̕[@CXkRJkbLP(?j?뒦cĒ^W&0ʍG>Ƿ/ݡ7{7^`iÑg8%ߗf ߪZ(MÓ%dO7fE2tD}D2y[PB>B'V܈յPjB?x׶ ! i2]PxW,>͗̋vLDga˜8X|tddHSAшD(?;lMWկ,vGiPJO<۫!丮_! ݏӇ eBX,5oWY(Ϝ>^yneeҥ W&.YԨ|㍟.$jJciaVqUU/vR%vUUO(}x:K0o]*(!MBJMC7),[z@pd2լH]6gg>Avuu9 [䗑<..ԍ+$K6wu0!Z `YV4mAVǖ+@jF)7 M4s 6 ,2 [.L殿dyP8Wn/"nf#On?͆ѵqEYI`pc*vVFQ޺Z w#^iZ&sCܙp8i"mB}1OXyB<׵,&gm˂wgCںq뺩T `eyJt6 E@ iٔ$qeT,wڝaIe],SQ9S]+uDQ\2fP5u!]WE4M{_!D(eev(y㸞Bc>e IENDB`TUdPbPcvTܵgn9FUlVݺ3AX1ѕɓcwx5vs,žcpn|_.Ug%,˹7RJ- q&e9 W&dE Ҝ!])ڣZ@@'oN߆*]HtN\EOJ`r)^ѾJsЁaC7ZvrV-]$k~()B0\[M7(02FͲD"u]Qm;`_XLjmqqdlW%;Qfvf:ųhzfG;}.C7jB+NOOgMTP/web/images/Capture2.png000064401651440000012000002144721167500132500165570ustar00darranstaff00003030200010PNG  IHDRx(+sRGBbKGD pHYs  tIME 8}1 IDATxwX$wA QDQqo[W:~V[mu:ZwպZju/ 'CA]IrÓ}͛ڳ| P@sl  =BQB@ACAA"2vE4P96E  ܳ2 4sh~4  ~u‹}B!AAyOtR96iBAA).> ?lAA佅fAsh/ yG@.PIFH%z4y_)k6K3"\z&wlҪ~%Pҝ>iZYMuVҪ'®X,h'Q8E I.BFȶmf:1~ijlP5zHO59CChV(oO##DFD;qHj^ռ=@иj-(:UG)"{'D(Q!5 *E <")-Hj*2i:tJe=zݼwʺiJɦDf 4aϕ"P5ڣyv-iDznh)F@DFʲC@!Bx585&4s(w'[Nk߱s!|h2'IĬZEL6!yJ A[ώ ,H\$}ΧNWX:(":#K%1Z _Riͩ^Egj4ZF*wYSBS‹u Qb^YRF^bXjܝ5[xhysKs2OH*yՇ5bZC]17@>ydZ#B'@^:D3'eL q}/Ƴah<nYOSu-ILX2ŘIvڣHQYq\HNm;t쉁$[AzĥO~Ùzq+4.[kYskW˦>6‹%k_ܼlY~n"^y `,xvD!O}R#\d*Hp*DV- E ''+Z:i2 H׻uN uަ}dzNR#;; {A>NG[+V# <ψHjf2wC<}YPϾշ<ĽZ<rvNuB"/,4&2{Dei&•jY [Ù1Ϯ>9VRk8{Yi[Ne)@aY'[m_PFMgO V/3# A>fD9n%Mgqg%xBCG=©XTcͳ*/N/2T6PlpE!z}Yf&Qʌ0F6Q}/"Q,:9e*Ew5cUio.[A(̢x4u PP484so2!G VWj`4KJh;_6+NU#s6KVIs(FԲ 94NmUTL/(DUSffi*\Y`YD;wgx7fZhs4|Y|XXZU )<<<{%L!.;]ݧ|u’Ƕ-rT*~B$dx4oP,#3qʊ&lzƛRs+C23fee i@ )BHFF!(L.1 MS@x^V4Si(L"fx]cĎ4OOO.LTX`9W*d2hVV*;8jRz;DO-]9&LLK5;iύE൪,"D6F7Q._YaiI JߵTl&ϹjRRCE[ ȶi3G9ͦk<88ZiRbn'kH"KiB GEAAŊhTqsW>X,Q@8QY,XA>2 y&GA1X6A%m  (4AAAwAA| =  HMAA@&  RDAAJu  Eh:8:l4%cUAA(GY7!>.!>NX\GHhV.~ Yժfר0([ˏ>fAA*,wtETwEL9j)y,nJ8AAJPh?38R;j¾Kd~S2ͻ&X}9ºzS]!k'gxՕ0[>Ϫ#sZͳdъtSzǃuibg n:]}G{qێ^8pA)M'gTF.7?9"F.gDDa|wPlN ۻ~:ia*2Q${qxکތ:FbբXXSAAr;:[u!>bdߵ4X}1~ún62):[Us%srrs}gxUT^{*ׇqְ}Euiͷ67=@ gvAAr&4ֹ~H_"}¤V,Y0O6xBwܮZ ]e[@E\?-j~'Ojچ4Xpu6_ǂ`c3AdO! R.(i0}tURݵS?-4zYL}3G~㰑[,%#l)Cܴq壡S"=\x Τ.knWjMwEV~d8cp" R.(Nf ;LQRÇ)U26!k'zΟU4XE>N9lZS,Nhw64tRܺ;z4A)P֨isB!Hj5Hŵ AA8RT(srn\\(ke7ؘ[YD.2*1kb" R(S J!RΨeFA)246  R@  RD[AA)25}k-`   E&%9ɸAAA|AAA  G:GAAJBhDAA u  DAAFAA)P,L$JCRQZN*>!33j^bG?Rl Ĉ ̌ kk4,jii etT*KCX%h/ sssV+\RriNM(w>~M8y)Ldg昛4hcccc=ʡĄ_]{HV7o>-*I):r䈝mAS]#"Y>==}AAA*UO:VO?M2VКaBaYbBSVN  De4,>-]EQl6k5ޭCD F}{7faz>#FS'D$egf䶴M3H"HDZ5.]=bfP娧;tfС4Ek44REYXZrBʵߞ+|b]vW >rUB衃o#aٳs;t֣&YeSRR<==9 =4M[XXtUdYD:d^9@7wbbH~ZK5R ǵn8<,,D"QF8,9/-)Fl!!ƍ{N|*̄kK5s^սu Yy|6B(uO4Cg1\fKش~m⫯ GNq%qZhD"HIJlRRq X[[X[[jRRR,kJa V89 4X04}A=&j̓K-wxXG_,BS̩jF e/95qq_ Huꦧq1/^8::D*\ SoY!W%'K ]14J$.9SsIjG:<A>VьB;tsŬxeY\>qĄwwwRS`` hڔ3%%=y܊FyF@@x0iG󖋳h <[ޣrhU?fo&?W<<΋]h\%HOOcYV$3(NM4Y󙙙Enщ/NOOHB94Y4Ewh7o*Yͅ,|\Eˆ. OZTRZ0 %A4"%u%Fy8%QR-Vь%$Z_ipA*N0!))ήrʂЬP>3#U@.?#hl^w BQGU  !01hS,+`7$^]`Ț)MմT^ɿrox~[ThJUro#Ta*UWǧm6݇_GqnԆ\t@0 //НY) MAk?~РAǏ6)|R?N IDAT8,˳<8b[oq0[L_eN4M_ u+̀8ey|'eRF쐐bbb Nz23!63!6KFDnPVV P#H:%zFMc__ųFMt!TSǡF-ZZj]@Au&>OrN-*X;s}M3˳cekx%1A"B 6 Gǣo2QZؼ *D2uY"b8nF u'6tNL ZIt!W__$VNԁI}y~DcyoO j@mw=i5}ݙ_& \|K^r/|ղ,RJϟ_96:i\ @ Cظr1v$.u"Rx@^حW^׮]իS]59N˳pf,_ڣ+f#c5B/Ќ#c59;t3(+ FӬYSNTTTh}7\$nP MO?A˲ڊ5zm{ !,hD%2nQ%׮\jҼ%4kr6\|h|4=&/ah8!MNnmgqnR͖_׈ U,QFSdf('GD}^ưMx Q3`ӌ2Qˑ`jg-&; '=18Y@MhݞKOiZSNI 9<ZL[_i+≝ͥI?ÒvW A} ]&,E-&je#JLEݤ-g{P7>i H~TrFO;)ZmN:B(hk49O-02~2?' $pjy'kó^B(џ +++NNBVl Oė4),f*xs+SX#Q FgU+4[ !7]6&W7~cGfu#FZXw v_V?(ǰZw_yw}ُhѦWO5-ӐR@=7wdНݪ~5h+5\RL\J15QNXU{V3ZYj#BDr_d]ϓ#mU-D߯?Z1 m;6`L~Q0#Mq){o}!RRm4{83xW?BKs,Ѽd+[Tc)-O ˫Wwjqd׭49M=rhq2JfBru4ZW gIO'|})Z&}f?XA23]zhl.=zid.asļAQٜɼ޵j2R'۶i8v.^v#y:P1eF ݨi[ׯ4#RO7qqq #rppSepCxė/_:8Wc۴'.k4P C %b(b G8a9sǽHb[ZMQޡ[s\>zcǏT>KgXjUR+Ν:rN䝬[=woݨנ[MEhw 0X' op֏i-W; [GՌ.6'OvInno XLy%'kgoMhHL~JA|p;N񿸤{O}Š'L?r24n.BBچd-BީjlU}QI?wSt2uw͜s8ԍb>kX `Lafwn]7jFJֹT*ԱӑGN𯍣i;KO;Roxyț1.4t 7nԸq"4k )&tBBzk|2b %l+}6١Iqy{WS-i3spNvΦlkjpJnkf !-8cش94^xpݷ M]}[!YO4t}n?X?dbK{U)8;݊lVm+{jw-]Wav] ,G|daWBޭjKM>qMifV /J䫞>VO|f̮?,Cݸ 1f"C1 wokg4]* jQ; CAc[ T")[eǏ4k៖pcM[rTjee}I-3#C@`zz6lmywܻ [6MQ(F3VJKwꌳ-R%E5eN΍Bto+N_\ OHUSV>/[b3H+)58"Jh궍:4@aRS5m~"'! /߇gDždhdv]ǹpyHɭDqy¹.ݲ2uRHEsvjJr"'>8Y%[IINV*˽S,fb#9Є7a Gy+5MM#zVfG.O AnȻ%gDJ&R+f)f,m[G)BB~k郳ǩ^HEɹ{u&}v:͚ĄKvpt*e>0*L!]U^_) |66: P-۵rnٱ犃 S/8G}HLxhSzv" d\Y6`ʺp ]}zʜnQS ~tCWl=}b %[B#no n`Խ];͂#pI؈+Un~#p6쳱B4i7h[ûuoou)]:EUj5wTlN>U<+4h.¥pXzO/tF_sP] (Ǫn ;h`3=^ ӋVBA1*QX*tAFmj҅,u󜽱/DŽy6m3gORΗ6]_(zG jFOP,[TĻ{w}v]ДV5n&Nxx]Y 6h.-Z''*>H\d,$XćymV5+FUl4,P0/5=uB7 HA6;UK-j}wɚ~ƶh&ћgOF?phwb4h v D"㟠,.Nh]sAOg.:p_>Yv"2򏾂npRB9g>Uʅ*3qu?Q1N? ^#Y ]F ch&Ӌq>~_ 04]w;ck 嗷JZ˜зћgOF?pzWRlx:~%a7&cQ驵Li':{c#$z~ ’<.F0gp&M<|2]=r[~4nőQ~v?eD㫿OT+ ~aڦs6RwīϪUzk}_%h6cSOV@ڎV"jŴ7~S=: ,+F^#%m:|y[A#oI\BCwuR6 tF^/`[cj6zn϶ch9F[P%ɩCPFc{C~Qx̀Bh.4#2qhԦۊg:+ "x<޽xeBH b]r>]{6\2X׺6neig}%rrYzLM&Ƿ?x$ݝYyBׂbE3MZ~eC>Usi׺Fƀ~)Ӌ]oP].T ,m֯s $&Ry, -?=7+M9,;z?νݱzW}h c8d|˔f(]b OZmw)7R 9}4M[XZT)_>|F]RG}9bS"M| ^oˬH*;࢈҂^%޾q]q.,Yfy%.:qG3``7]WHN J33У Jk>~'8 ^}3CM(ӠsgfRiʩUR]K>2,APh"Hr­{Zx>":­Ȁ5uB#;Aʢ%  ܙ)61_UxAC(xMD Ep-R^16R&.V@>ҟ ]cuz'_w2gMw#b&K ~˧{H]̯{6]Kyv0/ wկu c_穗Dܚϩ~yS_]̮so|a[sץVZoςEϱW3[/EVɠhRG/omtcV-2Arԝj2Vݪ(#" W$"QpR{c``JX,U.,Yt͛7^ze4&0vv 43hجH١t2|yzls4S!D3i& §/iaw?Q09o2d&dPy]=\rbcz1sX8HD42d+f(BȪq0 LXwgBbu S~ˊBH[sOhK& `о-Z:5F'2R'-R34ĩrϧ.s-p wfZ~ZϠ !L%Ser ?3֢C:c^u|5' IX|ɽ'V5%+l2dő7HLM 7(K9w)c;l}x?Է<*Dw_O`~[Wke{"Pw) u -N_:"`sviM?WG3hl[!riϮYM0H84 ,jZ0fN#3/_"C^`yw v]o2`ʜ/ؐDY}3wo=_ʾf8$)SXxuz!ю2:| {Vt³c./mnzFN ;Ocޢtw{Zyդєs|Մ5G_[t 9BJB !KN>Ez1֯leSh.$jK^?ڡݹs6Vnv7kKmXSe+L3NZmؤ)|kҲlphGJׯ6kmJD4EwuE ɏgeƧivNX1̪M>.q<:wycu_?zFОR8{\Rp,~x'O7՟`լ6{+a)ێX[cy_~x׷}fV穗D:".Y 2_/\9r {{\]9v(7Dϯ9eq7Ih%|!9y{d߽5բ\Ox|zEn Ksŭ8z{niw7/1NI IDATs1;hd߁v[I_MyƩy]Y=q2`fA_pvqrd j N6Jh⅝[\984"39M~(T()"ԛ-(wjocG9m߱e}Ve{Ԧj>#Ь fW&ae EZd5T~J nѺ,ݖgUI-NGvb}-Fau/_3Q̑}'WFC* 5rA}Estzia:pS>0CnqC/ M c^蝘=TZ@Lj5m.W(]"gfnY@[7OOK5/̀ C놺= OrW;ԚRylrRնǟhR[c;](/Uk4ʜW/w )SN>8ysfJrRe7ؘv<<!SA;vl5kݨ~G"ϋ23: />N32dVf71 @zvt+[hƬ~!B 8VrA훙;prgQO=<[ D: 3є?- >};wEo"t|2qǦհ+ " BAGg q,mi+4 B,3Bm&?") +4MAPh"Hprv7/|`F*j?7`(4ZAU'^;u8pOkX۵gڍ$>gXY $ÿV zz}#Vgw&Osŋl SM<]R?N A88"j9U5o.aڌZ3B]y!~628v<@CpM&@/^S: ooV**F9z`) MӖVV>A^}s|:K)E*ĦRa@ \.̀,x+ K+՟Ha_`# R{u7A42ݸglruJGC,y}M_'wmn?e㹍[)a~?fAE}N)#UUGhB), ^PBsж ׆L)ֵ^#/2Uu67ꑒ*-G MD H$iidXSw#ppJ;!bˆk1fV9~)22)RxBt۹'=P4My&R~TOxxDayZH NOP"Ih"CtM  D><^UR)^#1)S3 M. Z<D"C{vnA>DJK+P%)Υg2>57h)HIN2إ[1*y ,a..}-hN͗.3o]4K+kEʾ}cGg]uĭJ́S~"!?5|ONmun׿4#~Bޓ+{f>_ZcT\/'xii1- ">(~Z·g4*fo̞fn5V8%>9vOg MNJn%Of50TA.t߃]#){43@tlO_$G$@ˤDD 8dA 2)<В$">A!rsv_ nm&g9裈Vx2PC})pʚw 1ݾPxܻfv|콌ﶈ ?=g{k:Ze_:26hłgK?{2s۷Te<{>˱wsGV^?~!e}Pf=l^-]xs 8&.4Y7jYHo@/хE~cweJdfK/God(]x~$Zt0qBς=34цG3#gԖiBҳ񏏶_&3'@Rbm vjr2 L}dYJb6]s&]nDw;vGHVBD%BUNuý׎|ޢ;o o3Xj_whlT@HKH18 @q={^#V3;ъBgReoFzZ- 6cu΍jb&B^Vv0gYb6:;DL yO?ZC;Kbe':rJp[ڐz^jо||gH91csi>9k],ZYݞZA?Ph=x@UUDMxv;;;!jfbnLj0zx,+4OMH)3ҬQw2۩m &N3hϊNg ZU[-ۊ-lEkk2Yɦ)<˄3a=_g?`sg9Xu6\~Dm⺜Mv5 ҫB,A Q},0mwzq^oiFH $\W<@y>~,zZ&ZܘN.E{ݡz;nAqVxt35T0GjoD]u^fs[,;eEZF/gplGgv&=^K+d ՀHmVb@rss544dQE 77а4 ^wޜtýW7NMz֜9FVv|} Nw;p'ij?ĻNu[t WظKfu-gs:ݦ/w ;{oV q֛7Y}5!o쟵u3JћuYv$t𿷧;k>D SXcsLAFhܢ´BeeҒ&%N :#- *JP ]$ BP&ILwrUGb}Sv}<6Keok]V q')fm ڪpm)QOw-%>K,8{עbEjiIsqʰ\Eg.m^T%LtFi ='4v)y Gs괡$>4/-I+e[I//J9l ˬf5V9/|v{jMAZMܢdx2X.X[[`3nqnLz9 #tW.Yu* IZHhiʶWV0h+3d+$s9r%eSz}{g^`̢;ȃTd5G,,ԫegR0={̩Ãg9Jnކ'A!lX+bӎ3ء 9)>r@ـReTk7&:=")g5o7ZoGyv,E]RpG}qUsxϏk(Ӻ_QYg[b߅"y1+--1@pX 17 Xt'Mtg;S#]=C6v-kifkx@POO.Vlc*Jؽעs5qem7cc Y\>=t MON7+˩U\lAfdl[]2NاR>&\ߞI\1~~!Y~._[5@vJڗЪ݄Qh\\GTvDv.7l-y`7l 5o;|ŝ/nm7-HhDA?&:g8lu^xK'*(fK]L)vcȻ2~g#'mi+..T 0uUFEZ:~QQ(:䷔QALw:7d& 4A&)&fh:N9v9no"MqHz4Z@/^@/q늋/:')`p .-+777777'X{&Aɟ#P^ bw$J%uMElDA5DcLdsRc~#"AhԧU}(IԿE_XWm_2SuUWd)5= )4d23Pf (KO Biqqb_P(ڦX`-ee.]HIZd&%%)++c!  M~CSSCk7?,U|||RRRm۶[󁻜c͂5y;1!æm*%@Ec -C /:mZX`- ggb쬡)dRM436Tooe\9zX1CPv1뭯pϾ@/Y ẑqz.y*' Ma"vz =yca*մp(`_ A~W :(FzʵsVxt35T0G* MC)@M3# u5u֎AwsM9fn+N nq}>;B̲AbMwV{tkgc赴_Ƥ" XOgvVCӫoy؎Fkp&Ak,Z4uE%i5biP^Q^ d%$85=wsRWӝF6 M<۵an#f_!uݎPOQP~1Lv#LA$<Q}_7;hzYB B h4Zrrrrrr9)c΀a.6/h- w|x I񱮕գw9{}]&&;znɍ+cl\zyl]LvWH3׹X~`nӂ\r组7as} K f囉;7DrJz^̚/vod)*fo:Ε Cx4[Hs}ѾW;}e\<(X6/̽ZTi FūF6 M|\b6e9n>kgH]#ShԿ Հo0w (4t I@>d ^$6n+Z dxSPPkhh)\uGf wS>e<+&2 IDATVt)ͼlko>jHK[㲢ǾHOU}9{N,7%Xmp 6etR"ԗeXC7T;K;qmh{ڧ=&-Ң[NfĮ·Rsq-ͩ2/%gijvYӯ^$]m v'FŐA2`m8Rvz =$/  Rz"a"HK@FnQZvaZvazN!Ȳ2diI`ĆrJP( .APTjIHzs/*rrL6.(_QR[eݸ8, ~!~dtM9}/C?$2V"<HJrSф]x9 D #IBSH6(!ޫԯY7w+hLc>/%_W0Y(`F-. v;B=F\h^L0fsxȤ&Y/ 0hX`-6f >J7I:Wn9@"l2Ɯ?5N45EZI"a΅zk~vMUv}'ȺzMԀ1\iw }5tU7K\hF3 M+)X.!Ѿؑnue)4_s'/h0?k;& gNZYV6r;9սD5}1nqnLz9 #tW.YB󏠨k-e =㷏R^`*+ǹO4Ͽ{Bd9as3XoQQy$v`^nlk+Juv\,h?P[U{#:ͪkǎ]{yfVU-u%o¦;ۙw.ZfPptƕ޶݌-lg񮵱271dm?fQf-Lt 8$ɩuaVIXDMʩU\ɥIAiBbL17&KTchn[6wܞ˱N{ܼpIG^Zw~N~cn=M ,CʳGTvDv.7l-y`aOO_}L=9# <qi^֘1mܗvkXB & 4q9u.E#G62ax[k)c `t;$2Mo>zST 0uUFe~"#O^TP\{>>))^϶mZ[[7~ +ΑHdYck~m4%_Y An6ءתg# G,ԺF]o{r7E--:.lЭv4oUtͧ Y'zNxc5Frf2 PDjOK*O&qf@>>k#'˴0p .u& ΩQ?%_|HX>gxDi̛٦?Ӷj@|]ɋi`=,fy.g)ۏko,ˬH\'@-sFE3K&;,|vϾ Zs`9]z KEOb{sݫPG[]}b_mzj:BZx-vؿ!}d_$=;aDidȘPOS[^/yqؕ8\x4YĵHbUMY__8غZ[n}=-`}-MgnL}m4b6m-~IAzie)h_}Cm=wsRWӝ`4GdN ۼ_9ΥvO;]s  MkQԙYGw|/:}(]L*P1G+MR2i;ƾg ޿CVYg/O708\;rl:#~K~vʫQM taܽKFYMU(ܙRڋs VtuJ/>FyIg>XTi FūJ'REq:j?fyӽ7>S &OAqxMD²AhV\TV R:xٛqKE$6g>l &6(GuDy;#4@ 5"{aB,_Ck"-Ai2qn}L^2@sQyF[~;[Oe l߁KlmcJbԺba,'8ힳ_SIvIj Cvt#Z[l7=%f5G/:z9h B;/u5@& 3e}mVb@rss544dQ  77а S[sZV&^֨̄sK_H,k]MJx+@~;/Vܕ\8vޤz:{/_y9+.u$$ |+f5 ]u>xXNBTjǜ}M3yLkEifE]2@h7!5vl%qY=14i>4qyY[-J.L.L)YV,-IoRP3ҠR `E`0( Jmř%[?*XIqNSt)LfܙEk:MSgh3 {u(&lFAep^ JFC>yBԤ~ߜ@h7!&Hʙvkml#MvQָ;V*Do7'+$e˔i~* ΄14+E3-I+e[I//J9l ˬf5V9/R^`*+ǹO6i5/sej%!8Cp8p~̓'juSub3*9,f%( \_Ri9vO$LKKKKK kXsd5)V*C;.ٺ*)&9  ̈́^fֹ¸g݊>/+YU;fR ~"a˗#M XټJ=Ƅ5<&:GAD&nd8C̍Ū օ6wF_R@/ٲ4>;FJ7aӝLt v0w/lmag+MWVMCb0A-,6߹~yZbQz~8Ρd2G}^RR$3M#4-eU! MAA]hJH?pS-FF`$2Mo>zsS͓zl&BJ'eZżӹ٥, Ph"  MAkÓv2!v EtIBӯn'% )&)yUK~}G//g\;zB+Ґ%mA05aj\lDA#is[\W=q]γG\tP}tBdJJJ@1",xpm#Y*qٹmo}^{2`9K|NxWPme,ZX98Ck]lJB=irx]J果?jehbT5|E@F8}-O&hVL赜c1S> ?A~awh9cg蠠gS Dgn?p/ f'ਥ%}^c߳y_uF^Mp㒐E^Wlxc@8F$7Zެ˲ O&@orn]ZD Hb ȯ#!ă=-&Z-+[LXtqq;Sn9Q Y!  Ќ:ﰽiAӃRX6VPP@SWWktkI*Zi7v!H=:j $ Q.Xb]_@R}-&^Jh5WA_hܢ´BeeҒ&%N :#- *JP ]$ BPAstTLFKh>ҳ?шe_mtWonص:iLI][_(T䶅cgE_S+}Vi2LXϗYl==A_tI+r2*Fե*d[I//*xb oPߙDw5gSNHͣ'gU$mc5..GBÿw: u=|eR,A~ksitT#U i|IKHk^ˈ!J&Cp86p:E'Obwy&:/MneJTLd,o J3/SʨrjUyYUKP" H3闠dT`׻wGBJ^ACx T \(iJd rj")^Y̐UxL&s AAPh0lkd>b71dpPAlYFޞs`WIAic a1X,& XAKKa^:DGϰk\ mo߾=pEGgakhj7|/QJބMw3534nc* K`Yrj!WhrsAi>4}9Y,?ݽuN}^RR$:` o?j˚;n<ۥ"_70u_|D V* DA5#v׹-Zu%'d9yRO]7ak e) 츖N| $2Mo>zXPhryV1tnv) $ BS&Ghע)an/b}2Ҳ:=qhkЙ' qWOUׅfJ^ҫ_A,>$*$u ,hd -X~H+vk%WJ3DN\{Z`$ǯpڕ n >q ?OiI+$ ee4 aD-סxhMAA)s17fC|P<:O2շe5L3PۿqؽaZ{ª1kC3 pĆyo,:ucR= If6?Azy$ 1ߨ" &Auʓ*cB?q]Q]9hU+մp(`x Y0]%_Y%Ċ0Ni'm1 .SX#8]tXlDϣ[4mG= Ly^O ݮZ VVVIIIzmںJ%al7W+IBLE͡.쏹;}]>yg:[J&赏Cގ8 FiIs7?q0vX!H$ l[zOVH.r~APhZb@Dchh8;;7t*VY/xϘ K d'7ZZ"B$:)>ֵztn^#g(/{<Zo8*((]LOnɯ7 J7I f囉;7DrJۃkrשˋNCbRA,D8q1Iczѭs>B:Ԝw/ gA,@ZZr?0׏͵TdE/1ʲא(&N8.T_?ǯE\UtpOL8#fRFA"ms!ku #MIseo\2Ye$ڗNPj4Wv׬֯[2f[Sۑ;od}o5S  MihZLFEPOq/qX$)C@ lx]Pȕn5FuԦHKTv)>Mf+_o`*Kq8ؽx'?o]k J33yL`V$J)0ఊV"+0jZSTF#  &fA/؋R"-,4 t=P},IkivPQNu8!{׊0B9jΝYT4ORꈫy-Y N qr?q-h\O5bwT1$r^"ᙡ\#hLHY;PQÆ b0hO'L\B_=w]F V29C,෨(% C8xzV$謱cOFDOfInr-Sj*Ӫ$7棖Խ4+p̼\RN*˩UeUeT:ϡE-A= ֐Zb#F7SsAP9::Tѵ/d،bYƨ(w Jot39x7M%:iL"@*]Ebh'مU7[Z;dd w M;;>x@@vvvB4-܍)^hnnv\O+\g6Ibff*U{mTz)T:GA!4Z344͍2::-44Ta1X,7ծhfQ}z v=͞{dk =n6I:ͪkǎ]{yfVU n;e̥vX-K4dn5cOGvCڇՋ];nXQ =n^8.ؒ>&zka;Eז{Ü]Y<ԯ1ϡohL=Yn.L@F:& x0VP~i/%D^2I ajt7kشBEK \4P0wOߥfwK͠= 7jK9#xPqY߯7_RuʔƽWyZG9ÀiV /d՟r9#>' - !!5Gη7?ΫNuyn>6h́t/ ,<Ր|e EiF6-..9W-tnXGGP羮/ºXhPVNLLd0H Fbbrbi---G7ԣT^0c~37v\=HC]ME]cWf#jiۍUUVEaXPUC+Q4%FqnlCH #piVa3Y赏CގXԅWF% Zo%ԺM%AUu`-;PWSjĢ*aFBc;sy<$K-Xzh(1ݾW;}e?ן(X6/=+:=a!)Wz>τ g𬝓ϒpnb?mÄ.Aeh9ay,D;ssNᒨ;2k\)]19/to6*pSvB雏*$Ȋ"%'/x_`5Ot)ͼ"n 7*J"O /EhidvnS5gS վ^Ywi͠A[3bW \IލSwDrKF6Xe1bw@o}{9U6D;vYӯ^$-7eTWe$e3`!Z 2h !~ud A~uM7HBr֛{!P{U?.pg" 96E߰ݸ8, ~ZI>L6.(o B37 e =++ʒwGjPJ5E@lޗb-a=UNENjj'+w*kA Z#E̪=S혳ԥ2jо||g#>P.N qr? /ƅ'g$H+j&0e$ w߫rz}^m{vȪ߻Ckҫ[Jhv!m1h;low F+((cSWWkqu˜y ʚ'/ݲu~4p?LiB난4#WR/8; h/8إꈓT~}O.;2Qv*d# u߲K 3_b.Z^CTܣ^}d=?\n][ۡ\;VԄ3as,o֌"z5B{Po(TUkgw⪛DxD|JCѡ?Q-:7&?37x?9zhיeB .A҅3'{HKPTtY`դL̜/t,!#I2h`$K444"ap8BݺΫn]8yj)bxX?9,S17?v+i_5Y'Ph.AGPAOק3)F z\BĈ̼XbV19,f-+Y /V++;k4.dD_v%?cS_mV*ͳ7:g1k$8t  ߌ1>V75p\hT,+mw}nѻ7Įs1a聻 N=nfۛE$Zɏ"GS: t#<啕Q9lnq0`SU5T_NJ$Y;xT|_YC F309;J>IDAo>tM5iO۴\e4߾}cƥk9S hȔ;e}ҥ]ī-n?\lPiYhf.\YaA,9S $St4inu_qBAD|\4GSYu3\c\`nkЯ衤=ؕˤ7ۼ>f qM ]-yRRibCp']]&ˊÊdָC|vV v6v^ B57%%y54bAIpפ׵=7~k36c'oNXq,ppR %5"L%f; =ƄDDh&mrI@%"$ث ƍhpг/C}Ýl1tc{DP~輩.yJ^ t r4rcYy~ ՉiYFIh;g77Kޫj^Bʋ).h|>_~׿{Φ! (`E{T_Y"d8)֭L;t%_dޡ ͬMa37pts010Wzp6FTn'\zSw>'zoKV}|>9+Xrfkp;6N?m}~,v/~ءԼې]hdEʟ0ĘjaVߤ LՓfjkVJet GP;#!|oi2q5sRL? ͖1>F<|젡c{[C$M}yySxQC4i Ҹ\.!.f``K8 Fm4.hCjR{o|nT =3q~i{8xb:m#foLBt+tȫ|uakD H?daKwnD}aLvVwLBZzVݽ 'D0R\CB%>׫gIʘW'22r_mTZJRoy׉[_܌[F,א% DQH:w' 5}|c37i\\\^xPۗp[붜GƻOO)HU((C-Ur /ʈ/ځ Po ?IF IDAT"@RG]iلOu\M,I&!hxȪ 54T0OVX)$@GK40$2D]3cR΂_ЭYCFmco'}R]-X) [O@Baq'm5Dbh?`@$wi-/{X*7r0@|q3wA~|8`ޱ:\D1+X"1th4 a s+|aj }9m78șwNAM9)8?w zV7v&1*oYAMi^2cAݸPwԡYF-tN6$Ӏ/>?]-J/W~PIjof8[{S6ugBZ2Rرř84ψ}Q]sڷlG556wo<0rd4)&նvoRNOz:쾲wǣOźܦߌ-dAS']zhikkRJI-hak]dj”HN lfejj%.( V1+MM#9 &;^är55Ih%׼pc{2kxBBjvPGѣ!ĽZ<'CK2)'DV4F:*(M:NRMMMrR NؠA{I:UE-LBF >9  z֡GM:ر#x j@c :OvπU- jrXeGwbK7]㝘DA UIϫNˮJd/SAAt4? z>͞YNq4ZVӿh4uI#(̼XbV19,f-i#(#>kږJrIQ(,AA~Gp˕Ag?aT:ژlycx{Emhqᄛ?))a1kuAx<U7sdy;ؐ6y'9 @ W}=e~'ȰU^tz>=y_Vz~1 =5Yn!)+9"njm7s4/ g$B>WɋODVX誫i:;rQOvmڶw;vJfeHae[y)yeGǰisueg4Ծm;w/V[u|v݊v/M2}uC7:Fh {Gukca!H"M ;bީ֫u;Avmzdma.֯x;}};6v]{͗:֜Ŭ(r4E9"_/`mf3:n!O vzv>DCo_eLAi&/xJ^ Χ{t̏҄gYo_(|>gv\^[}T"e&|_m!]9fֺ7C}6>7bݡb?NX-4悱wl7ߧ wE+A\Lz}bw^~<, ?Hc\Y*=tHrvڔ/{wa;O18u䬽7SrHIe-=m7*t4#6[뫼~K|ugM`N+rl2|2++B 姏 4ɡs>_(Tz\aDs+_Yǹ}I쌄PpKec0JYkK qۈAX $`:{(T3b`_YC]E~IhVЂX4~/_\H_Bh0ػ{{F>x܊jB"Z J %|WLSz={A:_в^än@b J>IEqEH*m@߮sh~A&F4U5 tuԼ& r&wZ>^|i+Y$gpGtzA:HВo_r3Шa\~Q.?M>A$ $}*I B!k=|1LٗNTTKހDD3g5X\ )xMҪ155&' ŢdeYo@u5*I!^ h6_u׌<=5|ӉTPRYC={󫴌Fj=`˕j. =iH>Fv%2KֲSHsuҎq j VĤ$%c; w@! e|Fv'VKyk׎A|C뮖?666 @q]]Q&ObVb"dV޿{kte$'% {3ܓY P(B!BBRbQΐA^$#s2e™es<+ʪu{y8;+,s --rriii@ $W^|xqJC_'3e$%%xBnnn@ C"C\t#:R#GD  H&@d#Uh$8xAt4C1K&{ٵ06iω5㏮¥Jvq%=6]=}Vr]TRGY5k`6#&{\vvvuZ~}?[v̮؍ǠI-hak]d5lf̡s6`0l׵LMMВfe؝Z &勔3@~܂d8sj-?EgE߬Y>~&:mSW<ɼN-ѻ yri[{Cκꌔwl)206SXAֻkNgcjcڡ;NvM<Z{2IL&A@nA?Uζ!tg6؏;}s6hSimeq!7[v/fOW= eٵmAQ&uSm ȶU;XTmSzٯWN >s-cg&:.*7jdObgsͺbEF:*zoe9B kjU KMEp^:g2 rr) D3"RD}_vI(ckt48̪)>+3ˡ[ܚYrBP']vyOa`IONj|bש]y ׺˫rO̼8ynރuF }qPi%Oy(y|uD+c_:X76[뫼~K|ֈ_אR^=٨_^==-4rD3;g*͖ 59#N;~`d ;1kJAN17W]^&@}~EڑYtx:SMd@RՋ n3uc*it{%c5 '񊤚GTdc2ߵ:@ J權 ?w<(:>PK?ݛRr풇J}[F*̝fˉiKlHwkUľ[H~QF\"hD9S_YV6tַ ":/t}=jc:A)6bY[䰘*&GGP"24lg3!|,XS鸆R4CQGS*Nib-QV__IX[W7o:q9/ H!h~:LU) tTSڮ<όbH5[i-}׳Ci;jͰPXI&ܓT4=,HKGkpdI{ v{x*Loe‹\zd^)o͏xxxܻw޽{R>X!9IRtn>*[ϾJfְ'Wt:N_SSS-4@ TULn?C4tmďpBq0d B~έ:ޥuY skG| q zD*Mc[ɳ=wyWF⧫,:~i;,Z :9պglQ}e[/s9'DnMj;X9IҤj{]S;{;7c mBMco5ʲM?\[A~ й}||Ο?/)߿/% \%NdU#7bB^ˎH*([Xwֺ Rݽ|{d{ZN|3bzʹ^vJӄr@> %E!O<8tH 5=kY D+5IQ-L3s /I}|"YO覵u+~/ii9CT#w֣*L _M,?6],/[a>r[ Zva'” ;n΋ ?_s+2<%/Cѽm:\sf(ݕc}Oozz3g[~<, ;iZ>*2NQն[Qzcã;o"/Sa:9 WJ~&:튖@DHkGFF2.:ׯhl~1nfa%&36+o thPfz@MX\Ȏ+Fl'jx&YzjKY J"syhOjvhz~%$h"HcBR=]f@?w4žf߾}) /(h*hj9/ RκXTF`pgR-ǘpaݗ/+YKF$3 k].,X\ )xMC): ]lbzaˆ׌<=MHZa[$WsI+]JYC={󫴌FjY5+bRxQ6@ |Ւ'*&f:A we8If8t|$1ZI)~'UFi2&hB|ɵ s u>rm\Ww9'< 0:ܢ}Gn @Bsٷ ++2JDIۤz렣ۚjj'*Zu%)ѭu?wT)GA[yv_ zCk6lMBZ.s"97si[FD*Y44tTUUgWAԬMK&"ߚ'XAZGwެg&A[ x5Ϸdv6cVVHh gS˔ gQ~ldq\B.\.7--͈@cKgZgbdO|g/݇fBkL3zL-zxJʅh|klS%)L~oi2e򪏮~LܮvVf6N/H1dPеUnOg!n4_CS#$伎 H3#?8$U&ڡbl]\\^xuccc7774#w傈֭VpW'5{)7q#z=~"+2O!}xMcgzGZڔCcOv,~gבUtaoc?z[R!Of]}@K2S^d__w&$>ynqܐIL: S']zhikkR"HcQVZ"skq;K=~uUJl~OGKᅴ u>&af]h*)8]cXhj-G1wj{[ns!-:/sjJ5`"hV)WU!GϰUeE\fˉiK\&~yڿ6=ēYZ4:"Yyaғk\vMMrRZ 5ncpOf O $B@H IƣG ;C{*~9t K|]-723*1(ugsQNe' ש0Y >nA>?ʓLiHL>3!դntKڜyV i? ? d4 s9|r郒bfeaɃe|\[}LU}|X?n5WO-3%=`2st^5EC/rTf MBcT<ݳS⮫q>XN. !S9VػOK=ҳÒ継oT M5yxhNmG ^"馍̡s6`0l׵LMMВ͙⧫,:~i;,Z8ɡ]A#&R1Csvw`Do_;MJ \`yHى2slj;z z3d-oج9 {;OVo{֌J%^#gr!r3d2Oo9zL5ir*7R[#49wt`I& CUN!tg6sD[TnDKRMMMdyxP`0llld*MA4͢5GKA >[  z֡HMΙLfǎ\)Jff&AA3`fKCVYv'MC@PZRS*UeW%n  :G=]f,zz-+M9gKͼXbV19,f-+@ /V++'Z$@ uddP= ]۵#:GDN⏗;6SشE^乗񶦚Z6r/e¼ۤN_<__h:|P*yB VC`2Qnѳ3O*%!nAd"g\V򣤣C7nsW7j0B5ֻkNw&aՕނe~;3۾6O]W h~!<@ )y)1t.C" H91Y:_I;G"ۦ8ItGp4D>}bnkrq J-hcywDj @meL4S^R*kEoeԭCCV_MWO֗5`K|qY;/+v}h&Nƈ$'|?ʮOnHNFFV>)`LܮvVf6N/4TY>Rf\<Z?jƍMicAU@UvseJ2 }IC; s_Ƽڳ _>2QfZA&hB!0ݛȬmIB5ڢ7Y!ՙQ1tzʍ54q\PP#/X^8y|2@.T0vJVsR2QfZA$ME'4B葝*0&iwB趝NÞ'C8#حLkV:?UV>SޭBFi Zt^XL4;YjoJɾY::' [-+{)CE9&][doΪsYv<(:|:ざ]N0\[tn.u?Oѹtns[juǭn_cRàE5űrsǧg7T] ֢k WonV,lEӴv \?ͳVA\?ʤ)iɆViCvFu4MBXD"_Hϡ/OZBOsX{yve^(@Eޓ5#ɪ~m <:_wIJAÝtu,++r:H}:_P̧BY'$͟\+!K:2{(=yau[) tT[t {XZ':!_Y5 BPe$|8HNRhLqޛ'v,Skᴊ١4?)VHPIC\/ҿNdgڐ C^a[PU LZo^kg K h108:^ۆ%m%i PH4声uAe6+ 2<<8B") .vը^ƋrNԐLy)ECC<OUUuF?ʌ~ӄhGGQc2⩊E>2|$D(Z<>K[|3r$9|xA!~QnXXtUR?5meU+Gv3svw`DotMPqϗڊ%=a&As+*6kΓ5cHń ]X&E*ũK7t[-xlkc$M! TQ(L;lVAC:{ꤋ[-mm uuCQ(m-I,]̪*wilIVׯuIMMM/ ŋ%%%Æ p;K=~uUJl2r/O}s֓%1+߽5le$'%_=ɬ P( pB!!)1x(ag qnMыxPjg1v̳]\\^xuccc7774#|kEw%I:4.+b~Rf\<4g93`fKCVYv'f @ x'f-QP))frӲ7D1\v`N.NUYnyPӗ3bUc>k&D#fPAOק3).F z:6X3Dtd a1kYUULXOBdc| )R ^=7sgan_T4DFp˕F&Й_p(|wǓr4YZkfiaТgbC.DSgJnяY=iD@Baq'm\ш 莦@ Prb @ hV19[|T|Psgݮ?sߛs,?vOBmI/p7 =|^Ҍ3{캅 HB! ,!?us6zr,i4Zjqfnn[%hȷ$|SXZɏ"GS#V (1ra|M]=}%,ܢM=m6_s *5ugo6fl!MA9COy/1kOgmS'SE>a:V.K?GNtt~UU+%9\92L39dYH+ߨjم Ö@itG :+o thPf};|MzKV߽gv&hVfWύWLBGAAє9_dpg!<T {v43 k].,X\ )xMC): ],h0-W."y *֓l|K"yjp#hjɓjRMM3 ;2YVV$3  hJ#Zuţ\4ծ`f?"0rߥ3!ޒ* ֎jԧs[NEGo5VVd `/-IAGQ/خ `GvioaD6E6}˼\\/!U@^AOt;hxѤۗ),//c5C Ҹ\.!.f``CͺU-ysZ& f._'EP큇 ):giN5#݁5'N{;aAGk|^\~$UUv`0bbb`0\\\bp_5[Z\<*Fxw<|;G5sRL2yWOEY)m֡E!SW$t+l}:%lOp^; xa;Az1sY8# GP;#NSf{2yjۘxB{CSdžKXvpN+S$Ɋwdkjddվә2K|qY;Lf {yɴ B#lf@@RGv@> ˡ[rBFEⵠ]~4 A~vu-?qс_v?|K}|(j ΍ܦ'ƿ977+[ gL+xپ{K랟AOogC3,6_V%ү_%Uc>k&lHޡMܕ "Z&[w_`Ki& IU/2!fpX?9@Cl:J 7O+M)vCGC_о-#eMa W2hQF'O?ZTFgf8I)24֪a`yܪ_D2R+'e:)[pq%˝8^S'~(i2hD6T$1̬5[N$,?d'@inOH^=?wWv%os1-<z3})5 nJ_c}v2Z▹.-Z4#bfeH"M0EuB̭r e*$"f=yauO&8S4wG_;[JJ4l۷oӕ۷l6-PղadjYAWW_!3%=`"=Z0A$3eeV[. <]^l]a6I7muCO?aW/V <\Oh84FR,r(E>%xR: mb֊x AAs@YrGE)T/Fj'| YUo0fR*> IXΑ&GC;Hi[*%Eh[AFhB![BV3Ynk.>s=e`D F4쬵ڷmɜk!?us6zNy>.|Bskя_?)QhsDD]ZZqy a~NLBM@R'bʟ;[n 2ƻ>7|ϭWlY,rYo 9!':FӵUnOgjuQٖ 63V&֬W3bɷ~! )%,.:r#|_GV6kf*sJ!|&/yJ^ #jh-3nyVڒ޿T Z@bvNoDru*$ vu4qF/[/%bə$C]S3t4IbktiaVMY>sȒZ!!G(ɵv㟽sֳCk zDWXFO5 ]{|baX\tڍH!E&}5OU^H%>kĚv!Tv RJ[Y'&]2a픬B\R ߩh^{o~4[/waFu4y<~hNHMGqѼ Bv؝l4&e]ݏ{JЃ!G.gpgJ{guO<l5XNau5g@^0OK=Rra%$SzK3]vPrYfJ!ܸS˔ gQ戁AZZ%r d@9v܂l^hf==vY{$.βVk, v IDATej[K7Q')7 rZ3vs8EMɺuM۹M[nS?R2,Y)A9Ξ:CK[[C]ݐb􍳧𢷊P,t6Q)+-d(**JJJ*..VJvuqWzwS `Oߐ `2+߽5Lz V-ˮINJ@+!{3ܓY P(B!BBRbQΐA^C4.+b!y1ԑ#Gu{6զkę *AfEj[% M  4~DМNEVQöADlfjjiijiiѐ/CWO_ yjMv&;AlLrRo;t$I@[rrOdcxH6]Y T*ԔLӡP`0t  okCx{ =oVqEƻJ¢uK.e A%iW^__SP*Y]T EHsdR(.ˑ ˥P(L&-݁5'N{;){0pu+n&|ƀػi2fvπU&lO(u 8aEvlf@ [fVr//w.y4"H-@cKgZgkÚH8&(IATĂX8ѳ{".`=O잽cgDwςrvElpI"5)?1B؃|?O2}֮ͮc9 Dt6Xs}>Z8ǚ/jQBҩM+j ԲO#G8ɋGڤ#ԭm6O5?0]Qٔ(ݶ nk;Q*&}[P*sJRS2~ J?Co}uM s6CL PioJPذ RTn8)滕g.<몎S*T*D =mPΜ`~ܸvEU^z)u֟-H+!!7hlb B޹풋ONZJ!bs +~Y{;!n=Լe9kh ! dƌ{W`thuw` Gh~Bє^FWEkrRt?!?HWqtBHpmt"M%2iފ!.L񼩀s \.O}25j@Bh}w!Gaʳ_ۡ59`>fȵVE^%&M)G;e0ٻwz*iӏ%o;Qz@jV+KO4)u:QyE(XZ~.!zԺĻFyQUg ޷{{.5Ri^:KjM,:ԒK)iRITr%$!4(gF*٭tVF"Ƒ_*OrlHa^X0alEڳ+u'Ao.~Ckx}D#` GIԾ][ZYYp׸zF%ư?ɢan#z(aZ?hwOsE7b%%%y[9*'nR3#7,3#g<r뗻ߺzsFLNs /?~cǎ?{P/cGz$J>Tr?(W*Gu(i~4_ysiJC34Mkh<sm٘6=,nFLQ'fVS2O {BQT'}o!QE+iBHT|$埰ovF$%RDzlJ\df➤,JwZf4LǷs&!c]-B%{E8UkƢ^Tu i!܉2:]}®H0#wr\<0HatD KOU/h( X9hf[v"0X9lj"^pUky9>ZRza3654^ .;f*Wٕa}E Ä b{\QIH4L_:?9!Qw'v޻Oc dE_eq_1wRl&*lhKЖ|ma j"#llL&溁}#OS_|In2K2ɯX oX񢣣J%cRxhuu ݺX=xQ/!H.K3_ ܹWlMWn +|||_SlMGGG???D !kr&{ּW(Wv$Ϸ" (sw{61DN:!^R'tpAau~j}c򄐸soy!ĖHRI]jFWtF]mL.ryB4ʔezy'u׾~ %q/|Oq[,Yn?v51G#ۣR+NKzSS|*,5rrDn{H;W7*{іt|o].5wG[aff1WL[`mj ԲO+BIfdExrFfl<Bv Z(!TXݦ$Eʎ\0A9sqoQ?5YZ-][ʵܜًv{`*`:i;9vp_/z͆(sh O\4нQ60##,ʬζ\73T|p-_+W_rq'dS(yyyr"A+M '>u5s~ZC__gɵ[gpvy2PZz@^ķ ^_&](*&ܢ(a,>vj\KImH4yP( , xb˗/VeZz)=2%M{CpU{Kmm(B2j̎/)l͒v@yRͽu={P_D Ly3;HsUiZC34)4ؗkƴX:SVS$(k͡򥹌Lib -WM(EQ$#;ǚ; $`T*J dYphB9jښ&yJI8"SarX,0W1$%V,NUe㰲rCTNC3+^y.Cq49i42v[:jGJNY9QX[ ch1(DFGyWwqUvh·@V0 Rej"W/\PD.Ss( M&u*hQ-9Vܰ/s?~ُ1UA]mCSwjK4ʔcz8""\ 30G~K(AF%,q>@}]B~RawOC{vs  t=}#'=u'^pߗ._rի׮]?\+f4$dhhenZa8T\h!0IUff$M]FEoc{$BH^yNqń$NMygMoB==V9AW!Gneu3 PRy,,ľvH 7>:L\pwf{Uv hg;;vi& U.ܜfy?H4%BMgg 7`lrex=u7꧆ߠ-DiZnE}[v`ks4ۮ݈~T( 'g&2>6[;| 5^Syh[E -dB(6$Qhh4ujTJ0ؚ2165\hcq5s~ZC_K:s,C<ɱe#GkKTUДMMy}jQcb 6O7mjK?AW8hf yZhqpK-aqB [-7+pQG#۾^1`†يgwW  3`|||nܸwbk   be\)2:o8V^\e礪 (ż-qdX Vz ύ_.oO*|RgRR+u3BA.7xmjvfD:{0uHك & фRFh4F$~(VYv6c愆Sj˵52J˚5=\KϦ#r'-Űd![$Q̻=ī.Fgx)׽{!3{Eg_ܲ.f,nK|$e+vvYsjݜ5}vFg":8GLJbFD.w+8;q]xU-UPTPf+N<Q^Y} 6fRJ%O *hۣuhmZ?8M(%%2iފ!.% X:]i)J9hXHn9bæ>IOK.>9j}6$H4.ȥJtOtF#6d 񻵙o4cuZ';Dr-%^фґhjM :w[skjPIymY8=`†يgwW @ET$%2i0sFLN Œ&ćw^ڧ^{y:pu*7{](hWxM7tAx hBK4OHMɓ˕ Lܓ"&e.z)UwJu%3KW obfYczBt#df;9 .WTaumlJy~NX:0AфґhBZT>)R  u0 DCMJeff6%hK~̲@M$K`x<^ttRdR*<0Ssbk:::!bH4JD$u q(utH49:B! E5---B%Dx"H(X&i_xjE)3v.1"XdWI%ڗH4%J===Je5|~llVm?̘fG7,:hi%RLp&:Fh/)ɣUbr9ᔇrm/?O!l-Mq9N[N2MWͷUCϤ˕) V^Q.)D~|q?/H%Q= BkV^sz0 фJwsaC/~hBh4%3%yCXLβ6'4P[G8:2rݞ3I^:SW#WV6's>L\yז~5&N޻Vd~1q┇W%>6g`??$1uRIRrᑜ.4/;/^ 6hʇÏWI֌xQNHݭrIS ~>1,݂Rgl4_&͓K|4ނ2i=E^M2iM(!$h8+!ڒiG!64ugYXv0'k}JQo׿𙛓9F dށ焐uB#dMtaKy IwxH׿=mPΜ`~ܸv@ eJ*hʥng' ϺE *+YOXiBxc!9s4+4]Х Ӓ(+Q?| ! zjP032]n`2m:2ou7zTDB3*qم떨A^6'kGZpTpI||SQ3'9 Ȥy+zB;jdߠ5!DWY:wo7<~BV !of.Yܥ!4tX=Y !kh!m^^4^9!$tf@~#'_5}j9Z?, {21C8޾E $RzdJD+&IX>@ ev\\T?*K=EG֮|ж6'?0TY9+B׮EXkboZߟ](tz{i]>UZ^LyN| +V16ppU}&ўKc^K׏S.Sdu8ڧ+Iw aJG_"(hΙ L_&4]Bo=\[1u"=zzk֛g ԯg> #>c4U˕7+ !d4ˎr7ꗛDzϽQ;%j¢nYce[ԥ2ǯn敭MÉ擔I)yra{RVĞjYzd=A(p&DaG3\Sya:ֵ+9}s4 hBH4 !-pnqE}E Ä B__04g4hzR̆m/YDL0TqW`LNjV*QJ2::}ȏP"h44EX,|BقM0u>>>ׯ_)takgii%S(rrmpS@ `JD"QN>k5jz޿{EyFQEQ92YLtt ([x-eckeA (&! B,+bkZZZ BKKK &@񒓓E"P(45bqrr;DxRST["bH4JDh4wR|&IyK{/S`M@ P$-(u̖Ke<4_.˗Ip J(9ZQk4K^ˌl\@ `bT*UDS.w+8;q]xU-UPT0AVV֗b֨Y͍-$&DH4eҼC\KttFMګWwnѢEUlIm_<v%յt DD /˥Jt%}ܿwe֕Tbr8wl6'jP(en #'?&ZH4t{-=P`FJG_ϽQ;%j¢0 %D4f7ff @ PDIJؔ<\0=IO)+bO h Ds;9|.WTaum/e~NH4tgM@:lQslK-+b&$P(}l Oif*_Rhj/9%ޤVyJwh*uYOB&EGG׮]U*ULL CXKWkUkfnk`IpK. (SRSRh7111tttCཬ9;BnпQ@uoy :_; BHu?A۞w."sL b]#͘k40 CJ{ BM9n$PDN:!h4jl&HD*`em[{]lyvj0!ÈE=i=oڞ^w<] :N _h÷i5 &KFfc'+!d㇛3gKבBZ9ZuZ[mŔ''-&B-iz e e;#d=Dk!o^.δ_3!Ie24[\53` X,V(ִ fM`ձB>m}m1ԃוiR ֎t]6td\#/9 +ɞUY~цoÇ{[[[mhB,B!eliXD֛_t u3[NM*V }cܤ-py܊!hKz ߤ^*M DcEO`ÇluDJ+TT*ccc1(6d=%o|P[!+3C'xuó-a[Tw 1WܼE'NopEUF Nh4wR|&IyK{/S`MC۫cPbV [d"#V!SΰriL'e2inA gӊo#U7j?7g@ %ri[yى³h0JB\&gEW4f#-(^GH5$`4MDS&[1EDWK` Ee9 _tϞpUA(?:B077/#[MQ;(b&NtO.7x;atFYl/:QQWSSRj E՚Ԕ5j47b=zc$&f4t$%rY~ %b*wT"|ol[;^|%F]@(Vmj È_?ЮM(݉?sFLN B %AQ9J*΅|8\NJ N5>cG%<|GGs?.\S)$=jyoYAahW̵8DUj7)g;}\ _(3fwmc-t[7N 4u:gB`=_B?$LOGft8{1/*|LaI]#1HOc4Gk;l ,:ΧT]O;ڲM~pt](~ҽpDs󠥁a&t&V~z})_r{\'% \k9!%ۏl ٭B"v1"cqG=!DLY9i795My=ț)Ɍ47X^ǯVׂ10mA.]XUc}o?1࠶|tJOZ^3yϨX :MTiRC綬샿(BuW?Y({wpLJoǸ T·6w-=Fhf{;WL M $kN5[-"sf-[|]ye·A.8L] p6>^|s}<<_[ľ}k xQ)z׫p%!N{UshS]fp!{Mg.N[(j\ۭ#2~;pLoL/+>ZW߶eBzn8a S,YMd|4'L+UB}ẽV[)r0Mxt/|9D,MO]Uc9*wk@ iB̨"{1D͔gyc~Ȭ5#rxeh gd==sjs9KzksmYa1p8,+[*j೶2;/3bcaeߤoOWCqeR<6#֏<:f)r2^޾|ph{oK#l$ϗ~C;1/KzB{mئهnՒGF!41?ndUO{+7/;oc6?r9mw(:x;snrimhpAV=x@0HOG{G/zF}g|THo1u/K=6Wkcg0BO4}qpړs47tVh_kt^O }-|=ڌXj0@[y_NhPfݺB֮=]}w6kEֺ!<,4hw;3FnA PbфFLF$3bnp3P"cn[6ݣ@QJR4hΟ>)*SfҲv,17 ]8s:6YfʚĜۥ #\p"ܜԨ (7 FP44m]ΚmfT9IENDB`4ؗkƴX:SVS$(k͡򥹌Lib -WM(EQ$#;ǚ; $`T*J dYphB9jښ&yJI8"SarX,0W1$%V,NUe㰲rCTNC3+^y.Cq49i42v[:jgMTP/web/images/Capture5_thumb.png000064401651440000012000000150711167500214400177530ustar00darranstaff00003030200010PNG  IHDRTCY sRGB pHYs  tIME  $IDATx}i\u޹zyw 0+!\Ք%ƌMɩSVʼnXNRĉR%SQĐ`LDq מxal0}kw9yҶ%p @DB(DJIcC+X4DկBFGr-RQuUUj"!^+֫e0r, G„WRarU/dt >ÑRK.!#ٶ=3533==u8'(6.Ѳ"}-l<%Z]UUYX*i8uM|>of,c۞X:@:^mx )%!1u=]ץH(/yTHA.GD!8Mӂ.R(sB"즔zs% c+ %ܷ"X߃!s?;|遝y_xڧ}WEl+ڱy(G*Z8d(ٖ5ݫՅM!9ݭAURGqM{[v"RDF5а'{d`}BH84Uה|Z !c  '|>H0Ƥ`۶eYt`YVne!$$HVքxlZAo=R)ch44ވ5M$R^.rK@U,w\XhB* @ak4/kU8^mz-[ܼȨkMBG{i5 JR<98.23ADZ'l0X]4_x顇v6#]U { o7?QS=E׭5==\Wg7^rO "Q?Vz6ZHq(BʉK-511;zG?ZDknnν򒼱?O:66Xk )}T/ Y\ug&{M:Rqzj ?q=/L.*7rmV5&FD&K5?q>nOL;RJױq( v~㟾0x /V 2tGJkO]ėFiQt:ܳqR "W?䉹ҫ"Cx4.g\YT*{6 i&K6hjF8W7D>|XUՑ۶yc[ѭۅw~]<7XMgJآ! CF7wRJѐ:::y7({ -)~b !4!J"d{ޢt B8V=4ÿ-[PJiLJِ3cLHYry899)p Z}@7d\"Bc۶u]Ws&/}9z!!\cR˲TU~ UtCkiHJ-2t])q@P Du-q\J)"B9gZ]UU)㺑pq(R F\>Lj}_U5!|9c\ z`^QJ{O)H4hB ))"z('Âs5F)kͦRR(>8qr~~. KJ!e`KƬzUWy.~_~%kbBĩXXVkxjǎw !Ν04m|uo[O>u6֔hK[:غABȑ# \~cd,Qb'Of#'ctzhh;?o\ZfUq۶p(Ȧ s˹n]emڴNO]MM,f**GBT";kduGW4񎈤AmH[Zra\DdQӗ5B %I`BF4b*\VSMn=H&)Ďm%RlE:̤hvd"թFPfMOO*DuUQé6jDε=]] GfĈFʇR"l5; y\f٩di +굵k13 2 gj3 WYA\Uk2Dz4vsq24{S %Gd?]+-~eN)f0 uu u(%fCs2s{8nK* ^xw=XWWg\N$d*J/ B*UMёBNV`|fD9~UJ8|q(dYUU/b{f^㍗uu?zޙW9"Lo>mX֚mkk[TE|r[kXU(NO\4\=v|ѩ=~_fE׵w}w)˖O{5e3$mnY}?5=k^?6<ѾK菚f@)H|F`'0jz|vl(!+х_)T*ꎮ3F,۹E{z-,ᆬccG*F(kqw6P i  zSJffjXxΚ7pow/cm נE:0 ,{91k]crݻG#uKgNmپk;-otjd2ym,_!/ uB2%mڻ9Nvlq!@>G}e.lB.]pV L8k$~뭷z{{|tx݆333O?ѓCJ1?m&Veޱ=56lDJȥKP'/Gg'#TJn袔sJZ*%(g$u[H=+L8տmp]ox*"Yv9],_oCJ$c|r%-Lm=lܼO7/3$I0u7} +ʊʥ RFlB,0>>Hg@U J ^4MUٙO?ﯾ_zP+ڻ]ٙɉ񎎮KZVB!D)ʴuBؑ9ʹe/{ѝٻ?2L"!xuݻ(3}C3[jQ7[7Xu7M4͌BD|H.haC5>92 B}Be}߯j$jYRQƘeYP(Pj5dM,0eWTT? ڎ7,k .oΜ_~sO?kUǎ'LiM-#J>x.$]YVñlav<ꫯvV‰/}cuۅ3toٶS[{4ͯ|)h[*ޱqƎ7~]=sxv>HJlnw!O;v馔?܋Z(jʹ\N ߳Bd^xŇj_Ph8&es{6 |L#۷hgg͵knݩ2xv̢zy oHͬV![C8rUU:vm7oiB!|O+E '%![6ox971(Va)|J"2JEł(x4) $Jɕ ̶EQg2-S8@ c̶ÖmlWR,ânc7~ڱ3~g/,j%[ok tlx-%SS/U=78 @)=vᄏk#f Rq>`۶m#m-=:6g{WMl&Ƶ==q$A&v|n?04~\qXdc~zvO>}t_4#xDӺͲd.MYYOC"ke%SgqR Z)G& NLlݴidd$ k~\ʤ3Zc*f̬YN( x- d5Fȳk3ؖak\.+R# \ c؎5OL… UX40Ww*ۛois7%"뺪LLNtu2mu39ǀ-F~ 6 !o\oN·šD8yid|ukPs×7nپu;*OJ,pQH`Q+.FsT7---R&?mWtkU?j@v)t6JY23uX=,n2WRM{J;6 yOnRJ{.rёD&W-nǮB:oc`,e;~oi-FZb/˟;_{Gtɏv Khzlss[6oc:uP8j[ol)-D3@"W!Zq]rz Y(+4۳s×_|A R&I[[ּŧz;;t{ JĎ.UMW4"bƥSrcCk׮fRvwCH)kyf #Ֆew.J_AHk׬[x+B)}{t*%k&ӽhM ?f_m\Hk5.ͅi ڲm3p;@ 3g$ae*O$W*2|2]йv?gqXxCp0"fl64՗Ǩ{dk;'G_C2I k>~`ݫM0>%gD4gfJJz4Z5Ƕ\nt|iPh?OyeϩkᘙZ}mLf|񅄐xi #&6_$҄)M҄ aS6 aS6!lJ¦4!lJ&MiBؔ/ ͝@bjb){%(`#{PΙLJy>*JN)]CEaqR Ƹ=ຮi(eRkrԌeES9$x,wHig{*sf*w~?ʶdh޵kkuktl#ϭ>p8Uɩ|_TGKQ|p.^2W{ފ;鹋G 9WZ~VFyvD4%da3^-әڮNBi:ڰaGC50ǚvֶb^rB:|_jm\wG{n۪R|L]Ͱxu6i7b?R,[2_߅ X5]EKr)%#;:Jk=B"TH08pRDR( a~rl$BUZEJWwLό֫%DR c(A 7H*%JJ(  @(!@9DS'M*dR [m@سv)P?H r)%@R))b$rUMWP"SvJ8QXꀄԔD3TuJ$q>HH)AaP$*@BRB$z}poyhCIENDB`z!!\cR˲TU~ UtCkiHJ-2t])q@P Du-q\J)"B9gZ]UU)㺑pq(R F\>Lj}_U5!|9c\ z`^QJ{O)H4hB ))"z('Âs5F)kͦRR(>8qr~~. KJ!e`KƬzUWy.~_~%kbBĩXXVkxjǎw !Ν04m|uo[O>u6֔hK[:غABȑ# \~cd,Qb'Of#gMTP/web/images/Capture5.png000064401651440000012000001215571167500147500165710ustar00darranstaff00003030200010PNG  IHDRxsRGBbKGD pHYs  tIME )P?A IDATxwxU3e{6^H!$BBЛQA 'XTPX@/*iҥHoBIH!=6~LXfd L-sF~u`O@—]W!B޶|(BQSÚbNN!Q̓A qXF!B6KR2شWVk4w~б_\3F2eKy߸=/%ѿ,sߡE%eQަ4^Lo\~ Qd o|ݙ?'IsM˜W3WӪkhV"gLF翪k~mm}bݩsyl Ǘi: 9\y_䞇b|T d=wEPtc)I}b1#X׆&/hJVup"@9* %*)%ODd)9Z_OmbHKV"^֤=]tdo^_>OS?\ZpZ%5Hm[&wM:**ʱBubdj8t- H'Ϝ5&lI"imlY˵_B1(D |jp&Gom}ZH-&i%mDmev>NnjhV<]h5b@Sm]Y~^ѵ@SjZGnx]Y^u% nQFDkf]8~`uZaAP%1J}}g)Dz;}D*DI!"VE;u.1_*I"ؤOuPX6iFHYM!Hv(./ܺ}ZQF4eg&,/+BVޱ̰=U5K _v@T8"Y9iJG䥄y׾eUgs.uj2?s*c(ÁcgRÔEO_\k #Ql㑌PEޅUCZ?.B'X%ź>u'Beͽ8FF4m#@9pܝJ={R(B&rAC|ySy>GRa}ldwf֖̬G/[9 (R趩 ̰*Ȥi ~3վqb9n$wnEo$CD5#ąؼy 2(S^Kmݔ\zm%7+thT*B^YּkH]YytҲkט ~PZZ@:R*TZNQZf) Y-VQ4MI3r)FT0(BZZZj0TnSk!jKFoTjZ0gX>j=;M_x۳7pUҒbۏd" W|3o6: FC1q1!I0gy2!bImj"PmL7r3ߵZV k4BQl@P -qLH+t!OXRVZ%BVF]'BkO"*Z$2 f)c%Bh&&B92%5c ṔƲ@!BMB!T/X?9B!u=m8B!h"B:#!BaB!:G!BG4CB&[}cA!wG4Cͱ_'CUWQuyEU;ͅ+{j=3gI` qq!BM$t! *g;fJtlqP l !Bh?s4,YnesI:S~rPsQ&ϻc^w2r_In5_YJC:ɣ;Egn/2O"B+ymP`Im*۞',DU qbwhO/+9gp'#mw7<4gMEcbx=b؋ պHq+3WzM1#GR|]Ä(k;B!SC'Rye>~t:'}bSg;{YnfDiC~:kNJȥ"Rݩaݧ`E!Wͱ}}9ߟ6~x oaVиɭ^%=FO>1jdpZH61v.Lg.2]2*|fjDM,8tשηbh"B+P?|cVTZV,FYDv#B5r*qfiߞ]nhFDF9dg]mXKu+uq!B^ǭ=y`^Q!BX!B>f B!n0-c [9 4d<ϟefAJJ}}}jJJJU*P? EEEEyyyyyX!^z^vXJJ+RYm^=d1[OeHJLaۆ((7g&o;WXXh}(6.zjEE}WW׊'ǨRfV裏~h^3Ͷ'7_Ri2$Ij&iϞ= 1*Ff+/ʹRxX.]( DӌFҽb1I$B@\"Ĥ?Qa_z饯j8J7yyl6i00BhJ`LoA8ZVD4Pt́&$13H]yVo@@i.ZLV7T:x [OsC!^!8LBtӈ(JIi吸o^ZZ/g*JQ|>2=`e%/+hM-"K ް!%%El YܻoHVd5ZZsBHa/e]Q5 gzf'LY)F4+D"Rq(cEM$IEV봊w4}OEQF$Œ癌M`DWQk~e.yG{S?bMV0Mrj3eF)5OJH@ӌRh*2$8W~͛{cǏx(IM*3DX1|||hr\nnvvvVtts/ 1?3gԓOtZDUUE_>0i¢¡Cz]!"Y^9sfPPQIIɂ {[o1k֬9lJBN$R((Z),w:6 9@x[nMϗ(I5[ (T̖SF%*iAA%A6iS?N+ˈᷯ](U r_G W; G~ȫgeEbaYh4>zXӚ9'м|"#M3 T(U J)Wk{ oLZV/4akq\ii)$]hy*Ф(j3gɑOѣG8z ÿGVP(`0_ݸ&!`0zc"$tJ$[y6# ȁ&Ey4_ M" vsmj񵑂q*WM"O4"l}jT>~jNˁWX(-֬yuvN}ysU L>bkGhOS+̛KSwǚDj(BHvvB0-O)$IyM(PqOӳ W.݃ӮK\&!D@yVF:xB$IBbHŕ|ffAE$5xul!]GWSU{GF!9nHП9審v0мvM-!Jjm rWP}HRII o%W( GiyVl/G0N-&lQ1*4&Np$$QM @ӴJ(Z`O^eG/J@Dhw 6/iƢ(Μ13000 W^III!D"ƌ3a/s^}AΝ}t>@o"b[+'ߒkszOI(&&FN)**ϞK0mi^?l0ۨ 4z4_V^@u0QĄYr>ԾDS_yC0({QD^ϲlE19TBlT_MW$\x5?)! i\$9 E/HQԤɏ?~>HIIHӺ橠@(ѳ:jUjb)Z MKJirNl  Zfjx*_}3_GӔ|/9Ef$?`ͮSc( ЩB.2ZkM&F+$%%|d-^w F^h@^^ޗ_~IѣGvv6¶lBQT^^y&%=bںXۧmڶMժ=w{]e)0wDKONB-|駧VU/ -|v$(Hi XS}N@RbRΥ3B^%BXVYPpWyzƜBQ4ð@Q4M$*Qf˄0BeiHLΝ=g0ڶiKOG\n]QQE5kQ+ꫯCaN/IA(,,l֬RXX(n:(/--mٲ//o߾Gοrt:#G%))dp%$ۦZ7D!1 ~hk˝0',Yt)O>k0x0Xˋ>ᔴ@jݾ$99‚\0H@VxEeg)FhZKSRP"5Zwf[FhJ?R)njcV%IU̗n(CNoގ;϶o^9f 9dYeYA DQdoРA~~~VUŢ"QO___A$_ V^Рgh髥kky{K,-"AAFuhڮ?ؼqíVmVT(DArrr@T*ڤ)-)8:Q.] LᙵBodj^z&! |Vn~q(=+7?(VίKعS^={޳.;uEa ,I$ K4H4M˿"(8XT߁Ғ9sq9+T* v>FjBuVt[rmN~K05ʹiBBB%IRTaaaC,狊lEEEѕ6k4%I9^"@IPQx "$phMy&IQQсSǎjZϪBGo~(J?qDU>.u .ה^T)mw;GI<*ԎО-ZXSǍ{tFFE}q$u^o6Yj?(aX%]-HmLRo^WIV 2PZC 2@`;cNQiN'JZ(AbrJAM3XiĈ/_>UѣFGEQT(uc;!N|W lAAA```ddh61MII pf;uN1ZVT*tzZ;1'¢:WkTWp*sIYEŚx*u9E R z_^"ҝ}Ft%D"H)`(AMBN}p܃W,U*UerMG]͐޽zy#-ջuq]0j5[>1=vqVQfrtEZ9k EU~86.v5MQ˗8qĩӧb[jeYOvDqJy]]:wB*'6'k49#Pe?p{P\\] ?OwЋ @8EdM0Qod4۵k5jݛQ F^9T{ cD)rxw$EO{f' $!՝ UG4}'OvCդ)C$d4v#E( $I (*DYP[PΪU\@ d4zKQ\;cNS4tإ!X"/^P~^HlժtݮG_оCXxؘcq߬(xu,^'lxsD6D3cjwY`h2 ƍB$B$Qh z_lyzTs1JԱZTqܠA*XA|\L4?q(((xt҂+4X`u"PT*UBB/pT4M2 7J=;]cZ}[mcmwҁ} 5՝07qrhچ0o,ӦS:ut1Czz:BSRRbRRb`Դ #qHhz9B5fBuhڮ?ϘBlre[/Bz8rShVT;L B!T+۶'B!@cw#BSW/(/sU#[/Bz8F4I=  #dbbo,uzm4'oibx^/nBo٣| uhްаа脴w>:{S[rCcޟ,.ڀQfK5T;qu$ꎌM5 9_sXDttL~s-OyuSY>s7/ozsūCu>wusn1hd'}_nK?կϗћPqy.=wrً˭ [7T;l;{pŤ5 ˽yǛO9I;Dx{fdmX@ֹfIo8 &l~n?X<hrQD/8oBB3?.69*>Iњ{۷Oo;7!\)#5=%VVl -Zu~L&h޶ۈ9]DVnXwxᗭ}d3_.96_ϔ\4H[ջ[Vu+츘jV=pMh}-}#?j>qݏU_vѴ9p~eNUNv%rgFHqϱ5 9Cz/{x CsOHe}_=x*c;w>Nג9D@yk/kib#d֋MO=-0xiLkX9_YJCw׍`/]OLnꮦ:MxgO<;S +k#M)qdgyV]Ve_:_-g.g[MrrJu<9{?5e:u_'WOv=ξEi;sIlṙ>ue /: w5ֲ:z஧\׹M؀?<coCʎ]jZR)-%4,<ϮD.7:~Dꑴ?zp%}b>y>RؾJF䲢>Kְܜ{:mŨs3ak]8}2[‚+Z%g^Za)ɳAmzԵigObk~gn-]}?g\q'_W:—^셼y9n(<>de<:Mlsvza.MHCs`z;.qbp뤺kz]C?xtjHd9⣽|Eƕ=_o&Q]]BFgɣ 6EV7ڞ޿U(?V-;ɅˊZ.k~/ruK 1"rh.{q7Uֺ[ECjG)\c;mVc㾺cu57x&YdlRrap O~5[JO^26/6ϸW*bF+? 6 _+vێjȓoӝn-7[$k ~϶[Pm5-|rqIm,qΜ&6մ=-S?Pu^`h1=JNҖ/xna)<_.IZ rr{ fBnWO$ӻRxEA6HYոdVQ--$r[YK99߾;ܝ~Q k4yK!s lsi,!4Y| 1 o>J Ţ'BM|BU7{ˣîI*ǏH_8Q߮9K!B0RiѤ8B!P}@!B F!B#B!n:G!Bh"B!qݩg3DB! KNi<޳j(E/  bA!:|qV, x`D!c7$B!dMuԸLl!P 4H߻'[O@KsAqD5j j{5ah"<!{f!@$"TCmeu 5@ᗥϊZ$ߩ,??tdEymloIU#~nҿco nKj@J 1Dv/X@2Or=5t 7+;vjUJe@`m\ɷOlKJF"^c?[O]Sݣ#N+wfQ_ɽtɨY曋 fΜ5kfu=='Y:,S(ր{:BuhNn8uٖrK揧0ۃY5K-ɮ>ߚWlr3FTd$͘1}iKq٦/Y=D¡?ꦝ.5>_.6[ 7}!^V#UӾ}q2x{;Zŏ>Xh5]Th'qOGѬq龕;nFm~ս?'~x}%=F?nKLu{-?2AD:N{1?UX{ꖏ5[ /9^/8gɄ_iF4ϑGA~M}}l޼9((hz>}8DQx7~QŪuiZ!oNNgժUcǎ]j-ܲeCA$ss 3pS%*aUݲi!BaͪfQ&(kuH=n]ѴB!`}9eʔ>u NF ;ܥg\d!Knt+B4mf߾}./PN?<ϋ-,R2XO:oZ!j@S5+**t:] 0Eϙtk4[n}Ѫ!h Dp?> IDAT`7Z^\bO3 jt˫ۦBFh{Do~;m5^|ۦC8ByAR\\8gYyy`[ {tگߋiT#! _tB5@[W-:y`sŤ; F` YS]ɌVPdrSBhFlߜ@Q IX!סB!P}`PBay6.ypϨ&C3 B! 4oJ 4M!p!k=hm@TCB![s"4Z"~6B;WK:C!@f:h\O@sZ*m͝KB!0ЬYI 5oʧ$č%""=EB!&/؝99Ʒ q=Ĉ"wo̬+.DFΝ5B!0l@SE_c-H2o'YÔh?螿@!B~` HO:Yimcc%v;MgK4>)1-Z&u~L>)Mk.8,ʔJ} R^WDDDDDشnr~⧻KJjmᚋ<_G{tJig#k3jNBFᛕth*2 0ëmz9sơm_bN;R;m>mϟZZ)Vs\W<g# S__xlB?=q]b~(i)yǑD֪Ulo3^{~g^|?oQu48xA"BV3LjS In:wsDsmo,lӗ8(+i yx\n]LzW:7r' bɏab"]y[,EO@OV`fv"B5* |DQW@xb_wgڴ}ߖ=jUNy2,&Xa!Bh%e|ՀB!X!BMB!5߂˧g`3*瀉Ќ!B͛ypCjr4MS@\ڿrvajahsB!0tKyq8]dFU;+"ܹ*(}>!BM7qs>5i(:6w.:C!@f%'"Rr/׼*1{~!BJ "g,vgNN(u.r>_m̺ ҩ]LdYSB!n@SEwfyC,?QU$DG!B )$pnŢL_/?Qw?[ O@w}H劬>5cjBBjO/`YsƋK>dJ4>)1-Z&uWNqɴҺ xoՋ.)mk.ھۭcؘq]pSUsuiܼEv;l|z'47'l!@n(\:7_ >s_g;}qw=O>9_~O0f2c晓wn(zo߽V/,rYOfعPƹ}+E?Zg,8s;7/p9S}E"BhS In:w=yH9ﳁڼ5Sy)(xx*uOT<^ݺ9-[ t __xi>}<+鯬:+(;w%E_CaD!PF4G4Ձ`)b_CP"X~[!ܹ_WFeHHeЇ>ϾiϱdB@A9% 6GBaYgM]ZӕUO/[s)'TpX;;k;Ьo[ʲwk-/3 Я[*w}0ǍZʞ[$B! oVvUժʀ mۦus惇>A! !B7@rdڷgW R\\8gYyy`$!Bk4p}Ԍ6%KDQ}CXd2 @e ,q\ǁe9]'ѱ=>}teTRTyc/|slܻc zVNvywFr>Ga {eAs}ORc"keH)[]TBc}?~O/ N*B&K#?H |nlW]2 D4qPi O'fp2- l Уɖe&t>!IC8W/ 7, ~bmWy5!>hy" 3ނYfb"#!3#zy,^MDR"3誼{4oӣPϼV/[fvx<|&*nc/tw;֟-7; ۽c+VG@]vmʫܢ<uw5n8JOK-Ro1qľPz\s!{4-32ҫimaC IC@rm uI{?k)h08)>}>f_鋬 Pchxy y:CFy\h)}{.@E[;u Bp_b;OıßtqV, Y;uNMM2r 7VYVAĸ㪧6: h8W"e2AU@BĸNBxcLo+}y7Yh0 ;3sIM}z=¦XID/ԩ u}dٴzUpig9׫q$OJL(C*hNIDdjeB# OٟoE3?A /*pl |V(ҜGU2me>U!"cU|(|W#BQ#(Ӵsp$"NF;,i۾\H>3*jL 8\k͗9 aMW͠$Mewvn>*\%pY|ZSo_dy>?/vNqo(fkb 컣.lS?5j]#B]Xs#˝}6smQ" QT9 ?4*r̔W:g<־LY< *WggqD$ Whݎv8wI%E&Zn͂ͿۘKȲi\v4^ƶ3ϭoNviPCi|2iD$6vACWY":O_]ReAa ]j rsTZkUlgƺ.RUL&+}R*P;l{Zz!m].|LDXG*GѪ ? S1Woj-{'~ZpJ,vşe&GED+rTsK[@E etK5Yg|:埐 C͒^<ҋZyU\.^YP hVqr"jTEn4l[lhF:MSS3J]Ǯ\; ]rUj6bt:}A@=r+q`YݎPmGQAF@*:;9&eܝ96Djj?ܳ֯c+"mfchDbf捽þqn`_ϱqs2iD’]@Rb.hStohajHDYU6 @T<']ިP/R=\YV^U}*n&P(+Tcn_ID'xD HVMXz|rf;8tYKA=bhH)[]TGۣtRGKTahVָٟGvOhEz4>`c%6)TMwʽUQ.qE&@E,q::ױGSjco`hҴЕ? )D$X|5;bVx@DA[QӽDDd(AGNX vhԠIDW\ZVAE޸yd33fRH@(yաIDDwi"mWS4O>ciu5z%7w)O4G&@% 6h֣Yz$D$3 "QϘ9"Nxh+yl?1z+GMM߂o֣V˝@\{= ("I<(33CJ }6ϊtڕ@|gA,լR"3Qphb"&C iʧLDGBfFҧ6NJɕf{*˫dn""W`;Ǥ7C$Ptoq1Bv>ruDTiX-wfPq{4?U ݝ4sgͭlΟ!ЅCݡI^4WL-`ſqӠe[7Nm<>IS{71KBc,ı=F$f*OY=m`+w'zͼ;.Re<]6tm0>80*)#Ɔs\Agޣ|gv5^gRaι]Os~捽þqn`_ϱqs@ASCԚ2a y!sb9,89ܰc]>6Ao;pܦ{ .GT;O_5jcTv K/I.8"d\;O]Y缜ԤNP:hG NYp/gʑk&޻u(T^e|1*k3&44I=ױGSjmGD&G~,YquoDDDOD$XzgV4hkTxβX$ ѢCQҮDSXe/15 h^#'vgm*2k.X@k$eh֣V5$yOvbr*ϞL~t0<ѕOD"(5WL"V u[1+boQ^gA7 Co~l nwbNm<89bxg6PolUXbhhnQ? n:V7ju^~}SeNSoiEJ RGwlU5hyy1PBW|g k猌f/yζ֫{6N{+RI;b=SAߔ.i林w0EVk"u0c'>N5kQZ"V &Š(LVs6hФŲMƳܦ XtwS^~Î7?__9d71q׉c^&Ć1yd(rj7汝nW){4SnYz£Gii $BC^}•ʗQ5^yed Խq믃(iw-WTt|CD&t6}:q)ńSSx{w7rS e;D|F:ՍLk9}@J٣%p֚/s"ÚBKCz1dSj*ar.~^őܫ_͑Du7Hn=pٟ4nؤ-ɕ/]eD} R笔ߞ4S_ߋT*%"LmT$wOJ /XxHldK뼩h2УP(XFǁQ(tLeZtOMxQAfTK£n{۾~~w(G{WZnck/^ëWC~5zz?kdoEDn[.bNzN30#ؾob@^-[d>}3~ӣߔtVh2ШR:gYcW~M*XSOFye,%m 6۳re}o'NW?v0h.z6Ot M[gP@/YX;gdW53Z3+; 6~SΆߺk)hfMH=؉ȄX A%]㎫8<,D(E4KbqW'[P@D6Ad\;O]ˉleAӜ'xۉ6Kw9ꤲkEu_Q{WMXzNrǽ"缜ԤNP:hG NYp/gʑk&޻uZﳧM@bQNe,j}&V6l`{Nd<Ů 8qڣ91!Zdʒp2- l)}'D4{=>F}g/^{0Yyd\EjiWâ?19ʒ_bR5k(Ac-o& , e_EiߪB216©zaAZnml;~Fc/{UyJ 9m OfmKs6䋇/۹')[Һ.^Bz*ݭ:4}y˄~{~\ʺ}ttMM];9+o;tcנ,4þ9z4 - =sŸ+(Aen6;3i 77>nǚD4|#脇3Wg+LWZՐ=cҧ<ѕOD"bmH &"^͚Ȥظn~mȻl^^n^^}\5Lڵͺk4`'fcc7?I'lgrGdpoAf_odP>ͷKl=uYL#};2(:iASl?3 ,Oc׻ o&VW~f*5{1V"brlrAՈ($*)KSM] 6?XyeIӜYJD3v2ju"Ue!DTf"7*N~FOC,^MDwkُйEZOhh۸}^ݙ1k[kw\KW >ɡK6^4q=  g(am gתʱ׼wU}K{X븐o!+m=W:%U7GKqְCx.K@atw:`Wvɩu'dsw6762iEF9nHX]m$"uWڅ_ ;f}|@A~\~Ko|գCc.+f<󋻂5uWnYx e}#v7wی\&Z~r$ "vθij.?>Zf^uާe+'vv6jI{ټlqg.2JF<0}p6LA"Ru_HM9;X*J_7q^摡4B?w=o.zaPPVMSM,\!\V^eФ){ܫry~٬ 7}#CGԶ S{Lg-vʼOSUiigY- ?9'~fu{vv3w'QXRjЧψO rbG&KA]M[hʫR[j!CkV^U%5+Bc師qVǡ>M2sNL(I)[U{ͻ_ ulS?%"o e"ԧިkֶN}cXLM&߷aq7Eu-1מM:d)&7infʩdEG{1_-xzg6~Ngþ\ \; _ _誽RhZX;gdҝna1vv:Ф]ߖq9P~F칇}Q yW{\8m4~_(2hhס-q*,}蛺&+zJoGhx39?ֻdߝtD?D,LR̒5|}$6nu {݌ 2D^30qUt.RK׍$Jž07ԮQ;=f]۫ĥwwZu$M{L+8];XSm,:IuL,Rα~f.Q;ksU**B_udMA3Kn%=|x_;W0*)~sE#_r Ê/ɨeBfJ_DxXqI|,t#9//WRޑƚ>oӯIL_i"e'&,GD/Ugmr/U??\9&R)oA)u0c'O4W>0RE_ߩtm׽qS:"vAOV?4|a=-|$l2_ѽkK*}!k6ܕK/4yDo֭帗t|wD8hVS>P5U]+_sb mVo[GmF5?W骋#SD/e7SwgLݾ+ڰJuOT|!޾߯&7!ٺYmweE\N|г}|bѯF47{Ɩ4}ymG6rcVWaƚ_Qw ,]z6K_ Wt2HC`۝GX } tk@σfbqW'[P@D>>B˝$4W|+xўv݌;1jd2 ˇ)ΕYcǎ%a qZ{4 - cw99A詜#脇Q,NB{f\IDçkhZbXǸxY?3[:5uBAS*;t>gbͿPŵX=xFrXx!٨j mo mVyZZqdll ~HL@4PsFFzU335-qs= ֚BQ(D"@%m u)NkMK{S`M/uﳧMY@.!v@ЬXXrZ'x;'"fMD?~XӡEo9Eފ[`ç4ҿC"Hy\.3r/B TQѿ1&h2B`tt8t-ڙ(Y\($\Ľ $GZTj;N 4MgsU4W=,,j+Us{Id 36I P>,,jb=|Ae9ethf%*0l7Ҥ +zmR-\ZVL&=4e&h&ǡ枞,P͌ 5Fy)72ZJJs')XFʥWde'J*Kv-4g"jllp+RUE^EDbcSȖm'i]:2Wi--{\HOWpQ J{@G`+(O"yK/oKK+ 4h*Խr)\2@~WUÂMN Xǃ,yDduR d>kj5̫!Iwg^O_fm޹5m drVUG14Ԫ˺_TU)J|KRةÀ8G!T4+Ud\:p["-Q/ |'c|;_È/TY5 IDATcGuRfzsFFzU335-qJC/ hV&m u)NkMK{S̕ h3ǀ;x>ZB*TbD"Hdx&+:81 #KNqZFFF9.P(VZc''[{{Xv@ʍS(?{vRlǎ/{?2 xٳ"wЃyrsPbqSvE) Q`+qq>P|D,;6l,SN?4㎻: "yJJ2mߑݼf gdWű<0 A3;&= $<w+*ܵ%@p'rHh`Ppda4KBdXYFZkIO k m],9+f`zyt" h1Usu3 9Q$o訜|+HeYAXASP(-h^*ԫC{4m\S3{N4r+q`Y1o٧Ј,"Njkm-J>}Y*Z[nMVJҒzIjRV5h7šSRA0G'h2B`tJ?t^3{ ;<6c^>%# "'+̊b盎 (01meoת^mh]ոJ&ӶVm7UC`eB.[:!{#"6֤^m,k᷸4g=,q,=+< ]O&ܧ{օT3>zBP. aaì],Kԇ[7aWq8^ԖoV,;Fã'v*w|hse/Rm.OE'<x"X"%x{ݛse=2M\yg3esDړ9n񝋏*2Qι>t^ x.arg2157L98ˠɩu%l15@yξX7po¶#tu[t DDGW>q_f\ٴ?3zR;v# 8.OдvHjffVv5ntAHWb_:GD ʒ + 3T$ss"I#9Aq@f)gÃo]Z޵C4&MRinQZiXU:z :~j8 BaNvH$ Pp" @Pzkթ]'=5F s@ЇyrsPbqSvEb @$EGӲU뚵j ZS?wCC @fbqW'[' SRrmE[w3BʪkKY/^(pHTzuWFU4>hf$t# Oo [Q,GzE R)˲{c@` 64RA3!2K,#Ss5_'D5_6?"N@,SR笔Tb)/۹KRt'!x}sV!XF (/MJ,|zRt¥@̔MB2:i:0iV r "LnK+h,DZr-RD5ﺔð/8FBzGͿ;,P}tI8xn>=F$f/xl\kÆ-'(Tɰ q(9Nmʨi1to:c7k`穽+O=7ذCͻ{hP"eu]?Ol4(狁XXFʥWfcBm"Z]b_u8""ܧ{h'D4M "?l7=h2Bsڣ)6v-5 l(T:X?ed+LEr9"Nxh+y#*P!n} M>c)g^Xac5" JʒŨ"brlrqhyYrt8 @%exչ&|n9hҾ]H/ࢱ7CƸ7r1?s,x?FExhN`=S'z(I:9I'#c+O]xl-=x3 z홆-rM k猌ffZkfeg[XFPQ >ro}?U:ױGb=Tܠo[ִw0 Wo:~QQ< Pуch_3wlŪ}A@RX!AqUOmu"6%1P( "- EEn#pl;` AS')7,]Lh$xj<݊ wmgf8@ABdXYFZkIO k m],9+f?HRz6Br5R:g e܌oA0T8/RitCD J?UF:e+X8ѻ%w({+ u(-h&:nY kY'^jJD]z U@rdYc:,˕2)GF/3=+BFy75ߨFgS1u%ԯgv5^goc7޶OY45`XB8~蜈=uq_X=#7ekR?R&M缜ԤN{na.wѠE45`YN4C=D4ʍ"G?Ϸ/#?EDn#H-jX4'"&'V7" ڥd"}25@ThosMF o|-}Co 1Mjb Dc5DRAw;S e4rJ8G4K`QVEx o:WRK9b5.vɣ{؇*ǎ4ӯ6Ԉsqw/Z7wC{x'|,kZcL}ytUnUKBH2}U[mŅ#*2(.4j;-ڢڊFF`!l"^1&/HϜ9gw[u|z7cmh{)_W5:%5-_[yh9_fhkk++697lA9ȼ 0 +']:|iKޝ{ͨ¬`ZN>7y3/{QQݶ#7sreK }hշ{ƕ]0qWuAWa59l5 KD{<{.ܺ--,o==t[|4 ѣ,8s}P+pxlgJ\8kMovj\8j/l˚ݟt3wNpՌ)):t&ϟ`cYWl/lg|IdwݶСu,HY$5?B[}ndӡwGjʖ㞲O'9wf5K5mY5o.:pwԕ?|uu8iƗ-v޴D|qYN ('{B_]{/zu7l?]%2[ڄŜBz󈣽?:( v?4)1pC3I˾ly[zCXn|vV;9obF]=ᮋ;~LiGlaƙhz\93ߟyxVjM3gƘdfv7w̻5|܉<_<*ì102z`M;MJֶDQrpV,9o7CS{:д).s2]uv6Kx';5o٪ۛ/=žI99yz~Wwޛssy6 0 Mk[h.g4掼wgl]b0 0 >itކ[b2aas<1s0 0 hWmaam}_rV^d0 0 BHӌ g_,1cMF 0 05QL%3w~Gৗ\jcaa7u,!-Ts$;9B!#0 0 GX|!!L(Ҍڣ$=)ID<7r rss" 3`NX"DxGݻVzNG q*XsFFn!ܿ_) 'XTOz2F%:1YɊoO:XfZnnNX+Tԍȓ'|bك/ط`ŷuC6VЦ݄kaNOK)z'NUs-$3Q 0Ո F-J-RYU '<ɬP:!X+`KzKB% PV'H;*V\& FQ+P6w Fe[hL`4FX.= ,&J%5BBIL&XHGZJ)V@YbˢZeeg~{XKLt|;efb1RiFZHGqevPK Pcc+iP(*4R"\4`  Z >ǘPja s)JS2Cݼi LP0RJK)1c!,B4ֱ#un(DH9PbJL0 D!Ba֔R"#)fRdRtoQ"Ą`L4֚"@!L@*%O&̐m[c0Kdx q.bk1rҙBH4XITqm"Rktcf0 $ڦ,d|."am`JH}{~0CdL)pI_8$40(!@J@ I(mَM,kDljs@ pQue!9RH 9e $mۭտ^!eRhabXM.PF2ӳcٝm'm`*p$9~;^]~qEш+{ջHs1X:|J")@i F(Z .}# ` Xc%Ti̕aJfqMsڶێ1DR0T'Ҷl%BEv1,JhP!@ 8BƔYJdӈ B(< ضBy.)2F !XzBETB*A#l =a!MeZ`ZP68`dKFk\#TJhKH PAJ0&@))-PR AkF1c(B B:Hb1ҔYE-J AH !ؒs Hp) m,ZLQIεTMzRq)eRZcXH")!0JR b@*8%i+`!$ %iH]Z )Dž aI-YFIENDB`@%m u)NkMK{S`M/u{?~r8pS}֜M0ΓKUBesJY !wJҺ[W[7"lsկg1'Ϟ<7w5,uRS}w f{qѮՈ/:?rk:z~!-}ܔqy,ݸA7k4 ިZRyzz{"\s~ϟ?w~fv!FoJbvѶ۱UE clC"~N1'm Nstyz.95 R9`W?yo諯^-qnRgG9tuOy=T.,ܨ5|@>M~.~q-X(DL^ݤRK=9viF׵O}viCo ܱm"<ϛ9Z^Y?>1vȫ#]͛wwSv Tؽ{צk!}dPclF8G{TM&אj###ssF7y~Kc@\2f$t:Wh+/8oL2wtp'T8yOL&?nڢh|!I`(/?N7cItLr HP o޼1!O?߿ZBn2Bp_G)-}3/d(S<۶uN"EQw3qΙA (DD vlwF#ۖ%˲8[(K=1 !2"jYp툒ພ(`hv:!,W*XLEyVD"BVq׋FBz]`Ji$r 97L+ \]u=OUiiz@0&Z%cQ⯚"%MC:ns:OOτTxk|tyiae k9SoOδ3},+Ԫ}c٪9 YI<ɩ̏~2Z 'Y^۲ec^8!?{ZRzOl{l_lOO;qvͯ~e׽&.Lؿo`yk𗎞ܴqps_ >G_}cO|\ xe=̳=IVʑjO.<͛6#~Ob5r&䠧Hz3ݵjQⱐu%t?L cs9VV{{9s]Y57 {(\*1<ρ[j]oCrZ*Co<8O:!˲PEFǶBrni}Ky躭\j^]X]ڃr~YQ;4aX׿蹺k}(1wNW:cL׻ !X*:!DQ;zYu~70/ aD ɓ==J_wzP UZ PwO\ޞo8ZVѪ9]or_\ڶ}jrJ$=&O@ 8iD<p)`$5t;Ocq^o4fHiX*,=w#wJ}qd䩧d2RƸK$ ;qc^"q gN~?j-N1LS e@_ܳslrzn8=J*3?|dΎ:wT2q޽GA<ϛ_H֨p)بQEPI!7m9c0 9"D8g1q$Ib"J,2&QT5Ox _/S#cC{mt[ ˡPzg{9 fXY}\Ks3p8"O<hՊZ!, qS?ysu_h6ی H$pl|@J0V ʝ1ݩ)ժHeD/m9 6zӷVլD 0!sWA?;:2:۴VoUTuǎky}A9|Qc|q`I"e@FiVEF&' C~3ᮁ'/5 ZFg|www$%U jZqB(e;ݣH:Br]r~g6j? g.M jqlIUYi6+( +\ "#QuD"Hm$1Asgrz6EVӱH41&cۖuc a87;y{zSs3wmwo/Ǐ>t2z] D\.aʵFe2"^ 1\h@j6u\*K/Bt45ȎϮ @`M@oOJ;Q[@k6XE!LuƸ?O GX}p~Ѭs&.^+X x<ۆ0$iΣ7ڒ$L۔#""!ʐr= S:(\(8$!0*H@ȑ#===ԵRCpKo"9\}5Lô]75*C>܋W{fHTDvagVVK=- hXhn2!(B}$m$3=ʥpR ыE-ڽ)ٿ…ϑJXPd2s۷&{([jgrt{W( lNfM]iݮwh$ ʍf|ݙ/"x_)9304\bh<~9kt,mFzV$!/WP!zsH9(Jju߾}9cYV8񮤩r^mpp빎h`'.KMȔ$5ƝK[ z=k7? oI+ ɓZ xy@ p 45y#{'e֭Tj⒔pL$"" c "G)e.ܿo6-f/ xiG?8Կ^\ry&F#6 qJ ,훢ͶnlƑifz MvQ4wܳ[ķ͡ * "!&@@ 0)l-"Ȉ뀵l!fm} G#&cٮ-!p8 Eq_9P&Ҵq@8cx" !">!H2ᡁG 4sǶP$y.Bؖm;vpNX2((BuBhYַ푑`P!Yo&"bPjM#>m޼)ʸf˶=׈oliJ/1mǰEHeQpn:9ItkZ}B\C\s47[F#E hX+0u%2~ae;]K?{w[D̾@зonv ; 1ኛy??`O{'++ƚ͛n^(\i"@dW"gqt@rۑ" p= AQxȱ/|sFdmx43R:R7m玵{K7];wx'?S?xf|6֩EЈ"gODj9Iۭ}{CG6hv 9-[ )ZPZ.-=5ږ(jFӌjAL~zllқ}[A鮯_ \2u\$@*z>vQƘDWWz{9{|kpbr[r}{M\(Lؖ9qѪ]`[zB} [gf r)HJtl|O|ZU3 [fgft8s2lh1:ڙ=cѮ5nhQ܍F= -a_ gBs9&Bt#xtvAp B Rsx"SmǖG^X#z1'JͭS:Qk6oNm&ӓ@Pc8gG^y9;}qj1 =WL&]BgJn(Zxanjcf$ 狕,q.|n*s󦵳햖ʖd D @.ɂc;ef/ef7lX R վM/cah,-@*\_jk'\ !F̜xS7޸MnYVq}qjTDRչ9@W|-˒$c]#ܫ&qmZ.1$"(fgYܬcFKIdlv׾{kRBHl= !a-cXBѽZ.W; ao> _#˖H0B_޻@r۴@qO rc|9Sf'ʀVsp,EK6ۆ݂jmkV iva+i&I嗢 ګl6$cn/`A✿ʡH8T!H SN?Oo-pDzz{|=APKpa|cv!؝ ;CFEG6E#Q]ym^k$ u? t6gYͪp5 L6=)@fӁ{>#p;sn;NW2\ұQιjEQHTY@\3]\ǁ%"Vq1q BA`Ml`0R(  w)f34ʩΟ|wnBwh 7 Jk׮ѵP$%/Q BXV.^R R\Uket*AMq~?vs !p ̹F 4^e\߷t::Kgz;JB>5 fxRpV[]!ιǼZ1 K+ccѶndѬi,DR:99#j2N˒$W›^o>۶۽ݑhx}g_P@$iPsˉ\j9Peq0!`.m؉%B>D!`8B(Bq<ˆEQ €sA)2=.+*aBr9O!$K=Wہ@0H֏PU3nsyv{Hϝ??=?7m Mϛq9SǗhoؓunfK?ߴyСCO/T `Fca1+ Dd:-Iz/-v ٽ{EQQr(J4Eմ4 ii) (pXV]=tgD@])7h:=ٟ>{V%IғO>%E9Tngg#f8߿7 ⾇"R,!ٲ`(әjm۶z9c61N,//' !0t?֦m鯝FBϪ𓆮ҩ<zsB `OvTS9ԥW{ڎE6MBJV-+V1 B4;, ,-uK~R8bR܉ho(ޫE Xn5uÔx̿:X54]\c}`p%Kό@9Νl6K{_q \.F"Q,뺦aCD2~3W͌x{5ΔL8!FD=g fjb,u9+W4<j[k*U@<4V5`(!kСM[I2;;~vhÆ/ܕzK-e?Bl4 L ]82=>kEQ|'wݻݨ`uB6d-<1IPwBRA@UU8AEf%tE@l4T `6ϯ<"(cRC*r22M+J;V[RCxc%PJϞ< -m_VxBpDD0QM Ð$׫0E,-.wM,mیQ $}y3*cLp$z.RJO- ضv)XmM SX17n4֓Ii<7Jx 8WeaƘ׏d7r0ƫozr!\%>a9~d$ի]#%]8 rECR‘CvFeITUuttzi`Pov[f( BmQ Cv_JeUQ"ȍWEG$ˎ툒("v0C6޽{UU]e:򺸾PVe۔#sI \ g~DZĻNh)Y}FC19щ2aOC >|;-d~~aȈVSUFIU#&/YIBXo\Z[w٦~ z?Z*׫R+t-}˶vT F$Qh=wћ1ΜNfzg'ϙ.7RC7͏|#n0 CDW ˩# np"[7)cC͢mԚbRx3mgn.1&DX.,VSW kr<O҆Dե|r8E#Ĺb¯!(˃#<ΐ ض#hRku   n߲ɶlq"ԕUHۭVZ kbàX%Dp]2G0|K/!B@)B!c.g|]}9<>C$خMH S+b>h[H$ 28uڎm[EI_8r(_K^ڽ~]FgUɨRIj\""ñRh=Б( |䓬ժdbeYEA]6 FG(s C*<9#M0&0lOBZnhg"<8Ĉ rti.f^3ko)T^~+u]UUPy^ae_4:JL^w@8g_\"?*FWRƽBgz6]X ʙ #"ᠪ(\~Yt:[ޝoQ8jˌpY)5MgnnKQ{@(JdxR!c?r9_^:7>>mW( >0!|>9>u;'MLLL!s3 8bK{>%sivm #qrLr=T%ܡq铯1׺pr?}׾ HE^J^N*:wWryIQEP~ٹ.;#˲yC(v+i~hz خȊiA? $Is4@>cBz[Qzu\\oZ=zJIENDB`iJ/1mǰEHeQpn:9ItkZ}B\C\s47[F#E hX+0u%2~ae;]K?{w[D̾@зonv ; 1ኛy??`O{'++ƚ͛n^(\i"@dW"gqt@rۑ" p= AQxȱ/|sFdmx43R:R7m玵{K7];wx'?S?xf|6֩EЈ"gODj9Iۭ}{CG6hv 9-gMTP/web/fontsizesmaller.gif000064401651440000012000000012061161641352300170160ustar00darranstaff00003030200010GIF89aKt ~ ] o u^ z n | m  k x} l y wy h W ez t wm vg ]x z j v w {{{^ 333!Created with The GIMP!,ʀLLL'&/+!B!+/&'H,G%L  =H¶J- KöG *,Jҋ8G JH;%K 4۱7H1(o. 2i Jt!A 8@±Gd0Ł64 ²eK NHIɳOE;gMTP/web/logo.png000064401651440000012000000172741161625545300145760ustar00darranstaff00003030200010PNG  IHDR@bKGD000Z pHYs  tIME '4dIIDATx}{Uyo}9af$^@ TM~bR>ZcLc56|mҚVBH/DDs93s{g"s^Ϲ9}ャwuK]Rԥ.uK]R|̅3?1w5 q"1ƪGD EPRBR)(%! =?80(YʺWO6O:̿ijjbJ (E tPO@zh $iP2RB) !$TR\ޞY+y֖'NH! @ a)Q!%Bk-}\RJ+K4K(Rhh\[ʺEyL&5])e !boA9pI=` 料a?`|;(w+@6>u9n'WJiT)xʢkb'  8cA)m%i_@*"ž2J}[##dq"2Q*Go<R1p2=1($J@(٭K] s<֘ Qxsq(f@ކg:>L[}y;sgAPɺO") ]D'pHRe[z2A9OKu? 9nAFUг>P ;KP̲) L+Q4#Y |21x:D@ϸW sy E\RD1Y|"WG.5cF5%-?s{WHI02tX xI^z3a2v{'GM]}=ϻxys֭%+W?:{Xx/7}dO0<5V%+?%eI)5{B~q8#"`A-1 ,0't0&@AhEbU$P)B`e`TJ]׍t&B+ &ss#yIJx( }>E*K[y"1=5tBdڵZ |$ ܸCC‚dِBl07渀mq ,LvjYyѡSP(NsYqXsq.y{]g9sq9rE|;`q7}ps3f~x˄Y0gHQQJAR5Ʀضwh׀!,fL6fȑ&l7xqz"% DuBRD=Y6Lgb̶47"|3Uѕ׶ mvI[Cͮ>0(H&D &$: -)F"IW2H BP8CvTo*q>G $mr*ږpCeFqn1Rq1Fm\D"qͿRxLm@84>)zmKhE4k+7y:8űH`E^Hb 3>t`9!TJBI V^ Ϣ!a9oW|.q&c(3Ey5F:V<q|?&&}}#^*"pκ>q|TPH yz@;A(0x Cש ( b,[sꫮHb~槷c,p0*g)rdr$ 0:q(!ȭ_~jL< 1t]~BRssα*2~_] UB":-PntJ @@ƚ8R$%7ѝKQs0b[{KRQZ5t)EOPwÆ du_=-ڬ E&B=TWREdmKg%)%W0bhüXɎKcb+޾ٻvy䜈U*6W's\"B9$ў7ZWaX3 םÌl<J%OKb5C0`8s_R~!֣NXH 8΋at-1C6RKK&[:8>Epg<9̴+" _DX{m eFAGsA޳m{-rk!,J™w ֱ^8'\ڪsA pP)%y\Z'Ll8eC}EJGG)o8_2V[օ]vYq JJ!g2οXj fAu Co$ CEK pb%$]c:59'Pяp)Җ_8S` PD%rtx.>sobddmcڴE\eu[袋fvVG{r:Jΐ"b̶+RW)EUB֞(Y-JٖŔH-=^HDkS1W3bgJ0'ĮG79fcHI{^݊@D8W̞=BAŷ sCCC8$k4Kv]BeX5%ՆG:GW)OcE%dÜ,}7\1EY&š7!_|?Mv=hzm5h3V-&^~e477Vcc fΜͮlggwG{M+VHَ5`݈%ŧ*㎨Gd5y@ʀZ# o9)a׶/Jg*H$twwswqG1_ La6lۆۨsY 8ۆmYCl|Vbz;c;?:::;::f'Nɓ'&Ng---ڵK###GTo+_/J[W9& r8烣 V;+H(B⟼r_2替3eʔ\M۷#a00$)L2roa\V%8eo v.,ۂm6I M4gGea\SzL\_88oi?&Nx̙37lcͳqZ~UeOu9q-ۼt'ƍwڵko%^燐B_{1m)K..\$@ӊ8uLJ% Pr"C،8 $I8qaY]H`sQ\tozmmmB(Y^h)_W sg~3ɜzF6|mŊ3]J=L44=CnrܤJXjZv'0t',:[[xyFL?e:,nñ(|݅_ilڼ antV6J"LX,bh OVPH J!L"p yTrƈ80ট@&kK$T*P(Q"-A8\.X*_"zzz|mRj?gO=Ty'*͛D2y׿utv;<wqmHS_ fϚ?O?z=ݽhoog+464("bC;g6nDdTFa@P4rwෟd 0bƌhii㸨˘1cƷrs^lrO?\. @__)YZu*È 4d~@u؝;S?rD'=8{Yxܳ/|44628R4A1/Iẚۻ MwnRwPzgC2eʩT ^)xm$,Z١,Jʕ\ŊK/}572?Ɔ7޻u===}v-*-/IT8 X?aW^=R O @5ko)m?;˗_~oQ*q]Mo駞 DX 0s[*%X۶aێѩuš'iRH0w'Q.W>dR)̟7oɲe^ٸq# bʹ\::a[)D+ qgl1ݖem{ꩧס1<׮o`ٲ%x8686%?ضxLڴ]ۧf=t\DC*t&d2T:d2 uMr"7W#xkvβGC&t*ٳg}dlݺ]]]Pp;]PX*V*3 EN[щYfSgo`)86*rl/Z]3<< a{#i pfKu)rU*ȗJXfQ`R)!H8g٧qg3Ӄg}C J..n|e]{OǩkNf:466bwv/}~? ۧhIbX,"N<|߅E$S)8 ;r%QC#ԬqjF⭷󼨱Sq%صk):J&yrddP_J؇A?r]]p]d }؄Rŗ*ژ9ttt<^ <9W]\)XJ رÉ 4oz{{14476}$ر۶m޽{ylJq!-eƏou|xiH%#;4-%Kbv-pZ0=}BuR04u9$of6y: @:a$_ȤSBBBPJ-@>_u8>m8p](7.} ABBBmNRԶĨlw]F:i&pnܴhƌ0>C\A*i0u4 !l]n 1u44G>M߇chkkܹsq—.,,: /Fww7-+J^ITd%xp'Ui7_[&Lp+g%_( CB"@uQTxdg465- )& !$CuAL pBJEyXeTBa4>mXd4655t Mg0J)y~7^}yHRw4A* |RbuXV 16){ currentFontSize = 16; }else if(currentFontSize < 8){ currentFontSize = 8; } }else{ if(currentFontSize > 19){ currentFontSize = 19; }else if(currentFontSize < 8){ currentFontSize = 8; } } setFontSize(currentFontSize); } function setFontSize(fontSize){ var stObj = (document.getElementById) ? document.getElementById('ContentArea') : document.all('ContentArea'); stObj.style.fontSize = fontSize + 'px'; } function toggleSerif(){ currentFontType = parseInt(currentFontType) + 1; if (currentFontType == 4){ currentFontType = 1; } setFontFace(currentFontType); } function setFontFace(fontType){ var stObj = (document.getElementById) ? document.getElementById('ContentArea') : document.all('ContentArea'); switch(fontType){ case 1: stObj.style.fontFamily = 'verdana,geneva,arial,helvetica,sans-serif'; changeFontSize(-2); break; case 2: stObj.style.fontFamily = 'georgia,times,times new roman,serif'; changeFontSize(1); break; case 3: stObj.style.fontFamily = 'courier new,courier,serif'; changeFontSize(1); break; } } function createCookie(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } window.onload = setUserOptions; function setUserOptions(){ if(!prefsLoaded){ cookie = readCookie("b0_fontFace"); currentFontType = cookie ? cookie : 1; setFontFace(currentFontType); cookie = readCookie("b0_fontSize"); currentFontSize = cookie ? cookie : 12; setFontSize(currentFontSize); prefsLoaded = true; } } window.onunload = saveSettings; function saveSettings(){ createCookie("b0_fontSize", currentFontSize, 365); createCookie("b0_fontFace", currentFontType, 365); } function SetDefaultView(){ ShowMenu('DivHome'); gmtp_toggle('libmtp'); gmtp_toggle('libid3tag'); gmtp_toggle('libflac'); // Get the hash after the URL, and set that area to be viewed. var loc = location.hash; if(loc != ""){ // We have something. loc = 'Div' + loc.substr(1); ShowMenu(loc); } window.scroll(0,0); if(loc == 'DivScreenshots'){ myFrog.thumbIn(1, 'right'); } } function ShowMenu(div_name){ var Menus = new Array(); Menus[0] = 'DivHome'; Menus[1] = 'DivRequirements'; Menus[2] = 'DivInstallation'; Menus[3] = 'DivDownloads'; Menus[4] = 'DivUsage'; Menus[5] = 'DivFAQ'; Menus[6] = 'DivScreenshots'; for (index in Menus){ if(Menus[index] == div_name){ show(div_name); } else { hide(Menus[index]); } } } function gmtp_toggle(div_name) { var elementObject = (document.getElementById) ? document.getElementById(div_name) : document.all(div_name); var imageObject = (document.getElementById) ? document.getElementById(div_name + '_image') : document.all(div_name + '_image'); if(elementObject != null){ if(elementObject.style.display != 'none'){ elementObject.style.display = 'none'; if(elementObject != null){ imageObject.src = 'plus.png'; } } else { elementObject.style.display = 'block'; if(elementObject != null){ imageObject.src = 'minus.png'; } } } } function hide(div_name){ var elementObject = (document.getElementById) ? document.getElementById(div_name) : document.all(div_name); if(elementObject != null){ elementObject.style.display = 'none'; } } function show(div_name){ var elementObject = (document.getElementById) ? document.getElementById(div_name) : document.all(div_name); if(elementObject != null){ elementObject.style.display = 'block'; } }FontFace(fontType){ var stObj = (document.getElementById) ? document.getElementById('ContentArea') : document.all('ContentArea'); switch(fontType){ case 1: stObj.style.fontFamily = 'verdana,geneva,arial,helvetica,sans-serif'; changeFontSize(-2); break; case 2: stObj.style.fontFamily = 'georgia,times,times new roman,serif'; changeFontSize(1); break; case 3: stObj.style.fontFamily = 'courier new,courier,serif'; changeFontSize(1); break; } } functgMTP/web/scripts/controls.js000064401651440000012000000704641161625545400170210ustar00darranstaff00003030200010// script.aculo.us controls.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // // Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. // // Tokenized incremental autocompletion is enabled automatically // when an autocompleter is instantiated with the 'tokens' option // in the options parameter, e.g.: // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); var Autocompleter = {} Autocompleter.Base = function() {}; Autocompleter.Base.prototype = { baseInitialize: function(element, update, options) { this.element = $(element); this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; if(this.setOptions) this.setOptions(options); else this.options = options || {}; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); this.observer = null; this.element.setAttribute('autocomplete','off'); Element.hide(this.update); Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); }, show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (navigator.userAgent.indexOf('Opera')<0) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; this.update.style.zIndex = 2; Element.show(this.iefix); }, hide: function() { this.stopIndicator(); if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, startIndicator: function() { if(this.options.indicator) Element.show(this.options.indicator); }, stopIndicator: function() { if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { if(this.active) switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: this.markPrevious(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; case Event.KEY_DOWN: this.markNext(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; } else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, activate: function() { this.changed = false; this.hasFocus = true; this.getUpdatedChoices(); }, onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; this.active = false; }, render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) this.index==i ? Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); if(this.hasFocus) { this.show(); this.active = true; } } else { this.active = false; this.hide(); } }, markPrevious: function() { if(this.index > 0) this.index-- else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, markNext: function() { if(this.index < this.entryCount-1) this.index++ else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, getCurrentEntry: function() { return this.getEntry(this.index); }, selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); }, updateElement: function(selectedElement) { if (this.options.updateElement) { this.options.updateElement(selectedElement); return; } var value = ''; if (this.options.select) { var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); var lastTokenPos = this.findLastToken(); if (lastTokenPos != -1) { var newValue = this.element.value.substr(0, lastTokenPos + 1); var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); if (whitespace) newValue += whitespace[0]; this.element.value = newValue + value; } else { this.element.value = value; } this.element.focus(); if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, updateChoices: function(choices) { if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); } else { this.render(); } } }, addObservers: function(element) { Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); Event.observe(element, "click", this.onClick.bindAsEventListener(this)); }, onObserverEvent: function() { this.changed = false; if(this.getToken().length>=this.options.minChars) { this.startIndicator(); this.getUpdatedChoices(); } else { this.active = false; this.hide(); } }, getToken: function() { var tokenPos = this.findLastToken(); if (tokenPos != -1) var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); else var ret = this.element.value; return /\n/.test(ret) ? '' : ret; }, findLastToken: function() { var lastTokenPos = -1; for (var i=0; i lastTokenPos) lastTokenPos = thisTokenPos; } return lastTokenPos; } } Ajax.Autocompleter = Class.create(); Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { initialize: function(element, update, url, options) { this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, getUpdatedChoices: function() { entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; new Ajax.Request(this.url, this.options); }, onComplete: function(request) { this.updateChoices(request.responseText); } }); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather // than sending out Ajax queries, which can be quite slow sometimes. // // The constructor takes four parameters. The first two are, as usual, // the id of the monitored textbox, and id of the autocompletion menu. // The third is the array you want to autocomplete from, and the fourth // is the options block. // // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set // the option fullSearch to true (default: off). // // - fullSsearch - Search anywhere in autocomplete array strings. // // - partialChars - How many characters to enter before triggering // a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. Autocompleter.Local = Class.create(); Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { initialize: function(element, update, array, options) { this.baseInitialize(element, update, options); this.options.array = array; }, getUpdatedChoices: function() { this.updateChoices(this.options.selector(this)); }, setOptions: function(options) { this.options = Object.extend({ choices: 10, partialSearch: true, partialChars: 2, ignoreCase: true, fullSearch: false, selector: function(instance) { var ret = []; // Beginning matches var partial = []; // Inside matches var entry = instance.getToken(); var count = 0; for (var i = 0; i < instance.options.array.length && ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + elem.substr(foundPos, entry.length) + "" + elem.substr( foundPos + entry.length) + "
  • "); break; } } foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) return "
      " + ret.join('') + "
    "; } }, options || {}); } }); // AJAX in-place editor // // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor // Use this if you notice weird scrolling problems on some browsers, // the DOM might be a bit confused when this gets called so do this // waits 1 ms (with setTimeout) until it does the activation Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); } Ajax.InPlaceEditor = Class.create(); Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; Ajax.InPlaceEditor.prototype = { initialize: function(element, url, options) { this.url = url; this.element = $(element); this.options = Object.extend({ paramName: "value", okButton: true, okText: "ok", cancelLink: true, cancelText: "cancel", savingText: "Saving...", clickToEditText: "Click to edit", okText: "ok", rows: 1, onComplete: function(transport, element) { new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); }, onFailure: function(transport) { alert("Error communicating with the server: " + transport.responseText.stripTags()); }, callback: function(form) { return Form.serialize(form); }, handleLineBreaks: true, loadingText: 'Loading...', savingClassName: 'inplaceeditor-saving', loadingClassName: 'inplaceeditor-loading', formClassName: 'inplaceeditor-form', highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, highlightendcolor: "#FFFFFF", externalControl: null, submitOnBlur: false, ajaxOptions: {}, evalScripts: false }, options || {}); if(!this.options.formId && this.element.id) { this.options.formId = this.element.id + "-inplaceeditor"; if ($(this.options.formId)) { // there's already a form with that name, don't specify an id this.options.formId = null; } } if (this.options.externalControl) { this.options.externalControl = $(this.options.externalControl); } this.originalBackground = Element.getStyle(this.element, 'background-color'); if (!this.originalBackground) { this.originalBackground = "transparent"; } this.element.title = this.options.clickToEditText; this.onclickListener = this.enterEditMode.bindAsEventListener(this); this.mouseoverListener = this.enterHover.bindAsEventListener(this); this.mouseoutListener = this.leaveHover.bindAsEventListener(this); Event.observe(this.element, 'click', this.onclickListener); Event.observe(this.element, 'mouseover', this.mouseoverListener); Event.observe(this.element, 'mouseout', this.mouseoutListener); if (this.options.externalControl) { Event.observe(this.options.externalControl, 'click', this.onclickListener); Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); } }, enterEditMode: function(evt) { if (this.saving) return; if (this.editing) return; this.editing = true; this.onEnterEditMode(); if (this.options.externalControl) { Element.hide(this.options.externalControl); } Element.hide(this.element); this.createForm(); this.element.parentNode.insertBefore(this.form, this.element); if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); // stop the event to avoid a page refresh in Safari if (evt) { Event.stop(evt); } return false; }, createForm: function() { this.form = document.createElement("form"); this.form.id = this.options.formId; Element.addClassName(this.form, this.options.formClassName) this.form.onsubmit = this.onSubmit.bind(this); this.createEditField(); if (this.options.textarea) { var br = document.createElement("br"); this.form.appendChild(br); } if (this.options.okButton) { okButton = document.createElement("input"); okButton.type = "submit"; okButton.value = this.options.okText; okButton.className = 'editor_ok_button'; this.form.appendChild(okButton); } if (this.options.cancelLink) { cancelLink = document.createElement("a"); cancelLink.href = "#"; cancelLink.appendChild(document.createTextNode(this.options.cancelText)); cancelLink.onclick = this.onclickCancel.bind(this); cancelLink.className = 'editor_cancel'; this.form.appendChild(cancelLink); } }, hasHTMLLineBreaks: function(string) { if (!this.options.handleLineBreaks) return false; return string.match(/
    /i); }, convertHTMLLineBreaks: function(string) { return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); }, createEditField: function() { var text; if(this.options.loadTextURL) { text = this.options.loadingText; } else { text = this.getText(); } var obj = this; if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { this.options.textarea = false; var textField = document.createElement("input"); textField.obj = this; textField.type = "text"; textField.name = this.options.paramName; textField.value = text; textField.style.backgroundColor = this.options.highlightcolor; textField.className = 'editor_field'; var size = this.options.size || this.options.cols || 0; if (size != 0) textField.size = size; if (this.options.submitOnBlur) textField.onblur = this.onSubmit.bind(this); this.editField = textField; } else { this.options.textarea = true; var textArea = document.createElement("textarea"); textArea.obj = this; textArea.name = this.options.paramName; textArea.value = this.convertHTMLLineBreaks(text); textArea.rows = this.options.rows; textArea.cols = this.options.cols || 40; textArea.className = 'editor_field'; if (this.options.submitOnBlur) textArea.onblur = this.onSubmit.bind(this); this.editField = textArea; } if(this.options.loadTextURL) { this.loadExternalText(); } this.form.appendChild(this.editField); }, getText: function() { return this.element.innerHTML; }, loadExternalText: function() { Element.addClassName(this.form, this.options.loadingClassName); this.editField.disabled = true; new Ajax.Request( this.options.loadTextURL, Object.extend({ asynchronous: true, onComplete: this.onLoadedExternalText.bind(this) }, this.options.ajaxOptions) ); }, onLoadedExternalText: function(transport) { Element.removeClassName(this.form, this.options.loadingClassName); this.editField.disabled = false; this.editField.value = transport.responseText.stripTags(); Field.scrollFreeActivate(this.editField); }, onclickCancel: function() { this.onComplete(); this.leaveEditMode(); return false; }, onFailure: function(transport) { this.options.onFailure(transport); if (this.oldInnerHTML) { this.element.innerHTML = this.oldInnerHTML; this.oldInnerHTML = null; } return false; }, onSubmit: function() { // onLoading resets these so we need to save them away for the Ajax call var form = this.form; var value = this.editField.value; // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... // to be displayed indefinitely this.onLoading(); if (this.options.evalScripts) { new Ajax.Request( this.url, Object.extend({ parameters: this.options.callback(form, value), onComplete: this.onComplete.bind(this), onFailure: this.onFailure.bind(this), asynchronous:true, evalScripts:true }, this.options.ajaxOptions)); } else { new Ajax.Updater( { success: this.element, // don't update on failure (this could be an option) failure: null }, this.url, Object.extend({ parameters: this.options.callback(form, value), onComplete: this.onComplete.bind(this), onFailure: this.onFailure.bind(this) }, this.options.ajaxOptions)); } // stop the event to avoid a page refresh in Safari if (arguments.length > 1) { Event.stop(arguments[0]); } return false; }, onLoading: function() { this.saving = true; this.removeForm(); this.leaveHover(); this.showSaving(); }, showSaving: function() { this.oldInnerHTML = this.element.innerHTML; this.element.innerHTML = this.options.savingText; Element.addClassName(this.element, this.options.savingClassName); this.element.style.backgroundColor = this.originalBackground; Element.show(this.element); }, removeForm: function() { if(this.form) { if (this.form.parentNode) Element.remove(this.form); this.form = null; } }, enterHover: function() { if (this.saving) return; this.element.style.backgroundColor = this.options.highlightcolor; if (this.effect) { this.effect.cancel(); } Element.addClassName(this.element, this.options.hoverClassName) }, leaveHover: function() { if (this.options.backgroundColor) { this.element.style.backgroundColor = this.oldBackground; } Element.removeClassName(this.element, this.options.hoverClassName) if (this.saving) return; this.effect = new Effect.Highlight(this.element, { startcolor: this.options.highlightcolor, endcolor: this.options.highlightendcolor, restorecolor: this.originalBackground }); }, leaveEditMode: function() { Element.removeClassName(this.element, this.options.savingClassName); this.removeForm(); this.leaveHover(); this.element.style.backgroundColor = this.originalBackground; Element.show(this.element); if (this.options.externalControl) { Element.show(this.options.externalControl); } this.editing = false; this.saving = false; this.oldInnerHTML = null; this.onLeaveEditMode(); }, onComplete: function(transport) { this.leaveEditMode(); this.options.onComplete.bind(this)(transport, this.element); }, onEnterEditMode: function() {}, onLeaveEditMode: function() {}, dispose: function() { if (this.oldInnerHTML) { this.element.innerHTML = this.oldInnerHTML; } this.leaveEditMode(); Event.stopObserving(this.element, 'click', this.onclickListener); Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); if (this.options.externalControl) { Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); } } }; Ajax.InPlaceCollectionEditor = Class.create(); Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); Object.extend(Ajax.InPlaceCollectionEditor.prototype, { createEditField: function() { if (!this.cached_selectTag) { var selectTag = document.createElement("select"); var collection = this.options.collection || []; var optionTag; collection.each(function(e,i) { optionTag = document.createElement("option"); optionTag.value = (e instanceof Array) ? e[0] : e; if((typeof this.options.value == 'undefined') && ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; if(this.options.value==optionTag.value) optionTag.selected = true; optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); selectTag.appendChild(optionTag); }.bind(this)); this.cached_selectTag = selectTag; } this.editField = this.cached_selectTag; if(this.options.loadTextURL) this.loadExternalText(); this.form.appendChild(this.editField); this.options.callback = function(form, value) { return "value=" + encodeURIComponent(value); } } }); // Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields Form.Element.DelayedObserver = Class.create(); Form.Element.DelayedObserver.prototype = { initialize: function(element, delay, callback) { this.delay = delay || 0.5; this.element = $(element); this.callback = callback; this.timer = null; this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { if(this.lastValue == $F(this.element)) return; if(this.timer) clearTimeout(this.timer); this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); this.lastValue = $F(this.element); }, onTimerEvent: function() { this.timer = null; this.callback(this.element, $F(this.element)); } }; is.options.externalControl) { Element.hide(this.options.externalControl); } Element.hide(this.element); this.createForm(); this.element.parentNode.insertBefore(this.form, this.elemengMTP/web/scripts/scriptaculous.js000064401651440000012000000045441161625545400200520ustar00darranstaff00003030200010// script.aculo.us scriptaculous.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // For details, see the script.aculo.us web site: http://script.aculo.us/ var Scriptaculous = { Version: '1.7.0', require: function(libraryName) { // inserting via DOM fails in Safari 2.0, so brute force approach document.write(''); }, load: function() { if((typeof Prototype=='undefined') || (typeof Element == 'undefined') || (typeof Element.Methods=='undefined') || parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1]) < 1.5) throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0"); $A(document.getElementsByTagName("script")).findAll( function(s) { return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/)) }).each( function(s) { var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,''); var includes = s.src.match(/\?.*load=([a-z,]*)/); (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider').split(',').each( function(include) { Scriptaculous.require(path+include+'.js') }); }); } } Scriptaculous.load();gMTP/web/scripts/dragdrop.js000064401651440000012000000736651161625545400167660ustar00darranstaff00003030200010// script.aculo.us dragdrop.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ if(typeof Effect == 'undefined') throw("dragdrop.js requires including script.aculo.us' effects.js library"); var Droppables = { drops: [], remove: function(element) { this.drops = this.drops.reject(function(d) { return d.element==$(element) }); }, add: function(element) { element = $(element); var options = Object.extend({ greedy: true, hoverclass: null, tree: false }, arguments[1] || {}); // cache containers if(options.containment) { options._containers = []; var containment = options.containment; if((typeof containment == 'object') && (containment.constructor == Array)) { containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } } if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE options.element = element; this.drops.push(options); }, findDeepestChild: function(drops) { deepest = drops[0]; for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, deactivate: function(drop) { if(drop.hoverclass) Element.removeClassName(drop.element, drop.hoverclass); this.last_active = null; }, activate: function(drop) { if(drop.hoverclass) Element.addClassName(drop.element, drop.hoverclass); this.last_active = drop; }, show: function(point, element) { if(!this.drops.length) return; var affected = []; if(this.last_active) this.deactivate(this.last_active); this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); if(affected.length>0) { drop = Droppables.findDeepestChild(affected); Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); Droppables.activate(drop); } }, fire: function(event, element) { if(!this.last_active) return; Position.prepare(); if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) this.last_active.onDrop(element, this.last_active.element, event); }, reset: function() { if(this.last_active) this.deactivate(this.last_active); } } var Draggables = { drags: [], observers: [], register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); Event.stopObserving(document, "keypress", this.eventKeypress); } }, activate: function(draggable) { if(draggable.options.delay) { this._timeout = setTimeout(function() { Draggables._timeout = null; window.focus(); Draggables.activeDraggable = draggable; }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, deactivate: function() { this.activeDraggable = null; }, updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; // Mozilla-based browsers fire successive mousemove events with // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; this.activeDraggable.updateDrag(event, pointer); }, endDrag: function(event) { if(this._timeout) { clearTimeout(this._timeout); this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { if(o[eventName]) o[eventName](eventName, draggable, event); }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( function(o) { return o[eventName]; } ).length; }); } } /*--------------------------------------------------------------------------*/ var Draggable = Class.create(); Draggable._dragging = {}; Draggable.prototype = { initialize: function(element) { var defaults = { handle: false, reverteffect: function(element, top_offset, left_offset) { var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, queue: {scope:'_draggable', position:'end'} }); }, endeffect: function(element) { var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, afterFinish: function(){ Draggable._dragging[element] = false } }); }, zindex: 1000, revert: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); var options = Object.extend(defaults, arguments[1] || {}); this.element = $(element); if(options.handle && (typeof options.handle == 'string')) this.handle = this.element.down('.'+options.handle, 0); if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } Element.makePositioned(this.element); // fix IE this.delta = this.currentDelta(); this.options = options; this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); Draggables.register(this); }, destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, initDrag: function(event) { if(typeof Draggable._dragging[this.element] != 'undefined' && Draggable._dragging[this.element]) return; if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( tag_name=='INPUT' || tag_name=='SELECT' || tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = Position.cumulativeOffset(this.element); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); Draggables.activate(this); Event.stop(event); } }, startDrag: function(event) { this.dragging = true; if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } if(this.options.ghosting) { this._clone = this.element.cloneNode(true); Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); this.originalScrollLeft = where.left; this.originalScrollTop = where.top; } else { this.originalScrollLeft = this.options.scroll.scrollLeft; this.originalScrollTop = this.options.scroll.scrollTop; } } Draggables.notify('onStart', this, event); if(this.options.starteffect) this.options.starteffect(this.element); }, updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); Position.prepare(); Droppables.show(pointer, this.element); Draggables.notify('onDrag', this, event); this.draw(pointer); if(this.options.change) this.options.change(this); if(this.options.scroll) { this.stopScrolling(); var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } } else { p = Position.page(this.options.scroll); p[0] += this.options.scroll.scrollLeft + Position.deltaX; p[1] += this.options.scroll.scrollTop + Position.deltaY; p.push(p[0]+this.options.scroll.offsetWidth); p.push(p[1]+this.options.scroll.offsetHeight); } var speed = [0,0]; if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } // fix AppleWebKit rendering if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); Event.stop(event); }, finishDrag: function(event, success) { this.dragging = false; if(this.options.ghosting) { Position.relativize(this.element); Element.remove(this._clone); this._clone = null; } if(success) Droppables.fire(event, this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && typeof revert == 'function') revert = revert(this.element); var d = this.currentDelta(); if(revert && this.options.reverteffect) { this.options.reverteffect(this.element, d[1]-this.delta[1], d[0]-this.delta[0]); } else { this.delta = d; } if(this.options.zindex) this.element.style.zIndex = this.originalZ; if(this.options.endeffect) this.options.endeffect(this.element); Draggables.deactivate(this); Droppables.reset(); }, keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, draw: function(point) { var pos = Position.cumulativeOffset(this.element); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(typeof this.options.snap == 'function') { p = this.options.snap(p[0],p[1],this); } else { if(this.options.snap instanceof Array) { p = p.map( function(v, i) { return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) } else { p = p.map( function(v) { return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) } }} var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); this.scrollInterval = null; Draggables._lastScrollPointer = null; } }, startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; this.lastScrolled = current; if(this.options.scroll == window) { with (this._getWindowScroll(this.options.scroll)) { if (this.scrollSpeed[0] || this.scrollSpeed[1]) { var d = delta / 1000; this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); } } } else { this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); if (this._isScrollChild) { Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; if (Draggables._lastScrollPointer[0] < 0) Draggables._lastScrollPointer[0] = 0; if (Draggables._lastScrollPointer[1] < 0) Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } if(this.options.change) this.options.change(this); }, _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { if (w.document.documentElement && documentElement.scrollTop) { T = documentElement.scrollTop; L = documentElement.scrollLeft; } else if (w.document.body) { T = body.scrollTop; L = body.scrollLeft; } if (w.innerWidth) { W = w.innerWidth; H = w.innerHeight; } else if (w.document.documentElement && documentElement.clientWidth) { W = documentElement.clientWidth; H = documentElement.clientHeight; } else { W = body.offsetWidth; H = body.offsetHeight } } return { top: T, left: L, width: W, height: H }; } } /*--------------------------------------------------------------------------*/ var SortableObserver = Class.create(); SortableObserver.prototype = { initialize: function(element, observer) { this.element = $(element); this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, onStart: function() { this.lastValue = Sortable.serialize(this.element); }, onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } } var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, sortables: {}, _findRootElement: function(element) { while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } }, options: function(element) { element = Sortable._findRootElement($(element)); if(!element) return; return Sortable.sortables[element.id]; }, destroy: function(element){ var s = Sortable.options(element); if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, tree: false, treeTag: 'ul', overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false handle: false, // or a CSS class only: false, delay: 0, hoverclass: null, ghosting: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || {}); // clear any old sortable with same element this.destroy(element); // build options for the draggables var options_for_draggable = { revert: true, scroll: options.scroll, scrollSpeed: options.scrollSpeed, scrollSensitivity: options.scrollSensitivity, delay: options.delay, ghosting: options.ghosting, constraint: options.constraint, handle: options.handle }; if(options.starteffect) options_for_draggable.starteffect = options.starteffect; if(options.reverteffect) options_for_draggable.reverteffect = options.reverteffect; else if(options.ghosting) options_for_draggable.reverteffect = function(element) { element.style.top = 0; element.style.left = 0; }; if(options.endeffect) options_for_draggable.endeffect = options.endeffect; if(options.zindex) options_for_draggable.zindex = options.zindex; // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover } var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass } // fix for gecko engine Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; // drop on empty handling if(options.dropOnEmpty || options.tree) { Droppables.add(element, options_for_tree); options.droppables.push(element); } (this.findElements(element, options) || []).each( function(e) { // handles are per-draggable var handle = options.handle ? $(e).down('.'+options.handle,0) : e; options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; options.droppables.push(e); }); if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); e.treeNode = element; options.droppables.push(e); }); } // keep reference this.sortables[element.id] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, // return all suitable-for-sortable elements in a guaranteed order findElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); }, onHover: function(element, dropon, overlap) { if(Element.isParent(dropon, element)) return; if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { return; } else if(overlap>0.5) { Sortable.mark(dropon, 'before'); if(dropon.previousSibling != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } else { Sortable.mark(dropon, 'after'); var nextElement = dropon.nextSibling || null; if(nextElement != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); if(!Element.isParent(dropon, element)) { var index; var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { child = index + 1 < children.length ? children[index + 1] : null; break; } else { child = children[index]; break; } } } dropon.insertBefore(element, child); Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } }, unmark: function() { if(Sortable._marker) Sortable._marker.hide(); }, mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); } var offsets = Position.cumulativeOffset(dropon); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); if(position=='after') if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); Sortable._marker.show(); }, _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; var child = { id: encodeURIComponent(match ? match[1] : null), element: element, parent: parent, children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) } /* Get the element containing the children and recurse over it */ if (child.container) this._tree(child.container, options, child) parent.children.push (child); } return parent; }, tree: function(element) { element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, treeTag: sortableOptions.treeTag, only: sortableOptions.only, name: element.id, format: sortableOptions.format }, arguments[1] || {}); var root = { id: null, parent: null, children: [], container: element, position: 0 } return Sortable._tree(element, options, root); }, /* Construct a [i] index for a particular node */ _constructIndex: function(node) { var index = ''; do { if (node.id) index = '[' + node.position + ']' + index; } while ((node = node.parent) != null); return index; }, sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || {}); return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); }, setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || {}); var nodeMap = {}; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { n[1].appendChild(n[0]); delete nodeMap[ident]; } }); }, serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || {}); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { return Sortable.sequence(element, arguments[1]).map( function(item) { return name + "[]=" + encodeURIComponent(item); }).join('&'); } } } // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); } Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); var elements = []; $A(element.childNodes).each( function(e) { if(e.tagName && e.tagName.toUpperCase()==tagName && (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) elements.push(e); if(recursive) { var grandchildren = Element.findChildren(e, only, recursive, tagName); if(grandchildren) elements.push(grandchildren); } }); return (elements.length>0 ? elements.flatten() : []); } Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; } ollSpeed, scrollSensitivity: options.scrollSensitivity, delay: gMTP/web/scripts/effects.js000064401651440000012000001117601161625545400165700ustar00darranstaff00003030200010// script.aculo.us effects.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable String.prototype.parseColor = function() { var color = '#'; if(this.slice(0,4) == 'rgb(') { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { if(this.slice(0,1) == '#') { if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); if(this.length==7) color = this.toLowerCase(); } } return(color.length==7 ? color : (arguments[0] || this)); } /*--------------------------------------------------------------------------*/ Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); } Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); } Element.setContentZoom = function(element, percent) { element = $(element); element.setStyle({fontSize: (percent/100) + 'em'}); if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); return element; } Element.getOpacity = function(element){ return $(element).getStyle('opacity'); } Element.setOpacity = function(element, value){ return $(element).setStyle({opacity:value}); } Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; } Element.forceRerendering = function(element) { try { element = $(element); var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch(e) { } }; /*--------------------------------------------------------------------------*/ Array.prototype.call = function() { var args = arguments; this.each(function(f){ f.apply(this, args) }); } /*--------------------------------------------------------------------------*/ var Effect = { _elementDoesNotExistError: { name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, tagifyText: function(element) { if(typeof Builder == 'undefined') throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); var tagifyStyle = 'position:relative'; if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; element = $(element); $A(element.childNodes).each( function(child) { if(child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( Builder.node('span',{style: tagifyStyle}, character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); } }); }, multiple: function(element, effect) { var elements; if(((typeof element == 'object') || (typeof element == 'function')) && (element.length)) elements = element; else elements = $(element).childNodes; var options = Object.extend({ speed: 0.1, delay: 0.0 }, arguments[2] || {}); var masterDelay = options.delay; $A(elements).each( function(element, index) { new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); }); }, PAIRS: { 'slide': ['SlideDown','SlideUp'], 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, toggle: function(element, effect) { element = $(element); effect = (effect || 'appear').toLowerCase(); var options = Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } }, arguments[2] || {}); Effect[element.visible() ? Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); } }; var Effect2 = Effect; // deprecated /* ------------- transitions ------------- */ Effect.Transitions = { linear: Prototype.K, sinoidal: function(pos) { return (-Math.cos(pos*Math.PI)/2) + 0.5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; }, wobble: function(pos) { return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; }, pulse: function(pos, pulses) { pulses = pulses || 5; return ( Math.round((pos % (1/pulses)) * pulses) == 0 ? ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) ); }, none: function(pos) { return 0; }, full: function(pos) { return 1; } }; /* ------------- core effects ------------- */ Effect.ScopedQueue = Class.create(); Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { initialize: function() { this.effects = []; this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); var position = (typeof effect.options.queue == 'string') ? effect.options.queue : effect.options.queue.position; switch(position) { case 'front': // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; }); break; case 'with-last': timestamp = this.effects.pluck('startOn').max() || timestamp; break; case 'end': // start effect after last queued effect has finished timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } effect.startOn += timestamp; effect.finishOn += timestamp; if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); if(!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, remove: function(effect) { this.effects = this.effects.reject(function(e) { return e==effect }); if(this.effects.length == 0) { clearInterval(this.interval); this.interval = null; } }, loop: function() { var timePos = new Date().getTime(); for(var i=0, len=this.effects.length;i= this.startOn) { if(timePos >= this.finishOn) { this.render(1.0); this.cancel(); this.event('beforeFinish'); if(this.finish) this.finish(); this.event('afterFinish'); return; } var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); var frame = Math.round(pos * this.options.fps * this.options.duration); if(frame > this.currentFrame) { this.render(pos); this.currentFrame = frame; } } }, render: function(pos) { if(this.state == 'idle') { this.state = 'running'; this.event('beforeSetup'); if(this.setup) this.setup(); this.event('afterSetup'); } if(this.state == 'running') { if(this.options.transition) pos = this.options.transition(pos); pos *= (this.options.to-this.options.from); pos += this.options.from; this.position = pos; this.event('beforeUpdate'); if(this.update) this.update(pos); this.event('afterUpdate'); } }, cancel: function() { if(!this.options.sync) Effect.Queues.get(typeof this.options.queue == 'string' ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); if(this.options[eventName]) this.options[eventName](this); }, inspect: function() { var data = $H(); for(property in this) if(typeof this[property] != 'function') data[property] = this[property]; return '#'; } } Effect.Parallel = Class.create(); Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); }, update: function(position) { this.effects.invoke('render', position); }, finish: function(position) { this.effects.each( function(effect) { effect.render(1.0); effect.cancel(); effect.event('beforeFinish'); if(effect.finish) effect.finish(position); effect.event('afterFinish'); }); } }); Effect.Event = Class.create(); Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { initialize: function() { var options = Object.extend({ duration: 0 }, arguments[0] || {}); this.start(options); }, update: Prototype.emptyFunction }); Effect.Opacity = Class.create(); Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); if(!this.element) throw(Effect._elementDoesNotExistError); // make this work on IE on elements without 'layout' if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); var options = Object.extend({ from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || {}); this.start(options); }, update: function(position) { this.element.setOpacity(position); } }); Effect.Move = Class.create(); Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); if(!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'relative' }, arguments[1] || {}); this.start(options); }, setup: function() { // Bug in Opera: Opera returns the "real" position of a static element or // relative element that does not have top/left explicitly set. // ==> Always set top and left for position relative elements in your stylesheets // (to 0 if you do not need them) this.element.makePositioned(); this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); this.originalTop = parseFloat(this.element.getStyle('top') || '0'); if(this.options.mode == 'absolute') { // absolute movement, so we need to calc deltaX and deltaY this.options.x = this.options.x - this.originalLeft; this.options.y = this.options.y - this.originalTop; } }, update: function(position) { this.element.setStyle({ left: Math.round(this.options.x * position + this.originalLeft) + 'px', top: Math.round(this.options.y * position + this.originalTop) + 'px' }); } }); // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); }; Effect.Scale = Class.create(); Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { this.element = $(element); if(!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or {} with provided values scaleFrom: 100.0, scaleTo: percent }, arguments[2] || {}); this.start(options); }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); this.originalStyle = {}; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { if(fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); this.fontSizeType = fontSizeType; } }.bind(this)); this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; this.dims = null; if(this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; if(/^content/.test(this.options.scaleMode)) this.dims = [this.element.scrollHeight, this.element.scrollWidth]; if(!this.dims) this.dims = [this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth]; }, update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); if(this.options.scaleContent && this.fontSize) this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { var d = {}; if(this.options.scaleX) d.width = Math.round(width) + 'px'; if(this.options.scaleY) d.height = Math.round(height) + 'px'; if(this.options.scaleFromCenter) { var topd = (height - this.dims[0])/2; var leftd = (width - this.dims[1])/2; if(this.elementPositioning == 'absolute') { if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; } else { if(this.options.scaleY) d.top = -topd + 'px'; if(this.options.scaleX) d.left = -leftd + 'px'; } } this.element.setStyle(d); } }); Effect.Highlight = Class.create(); Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); if(!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); this.start(options); }, setup: function() { // Prevent executing on elements not in the layout flow if(this.element.getStyle('display')=='none') { this.cancel(); return; } // Disable background image during the effect this.oldStyle = {}; if (!this.options.keepBackgroundImage) { this.oldStyle.backgroundImage = this.element.getStyle('background-image'); this.element.setStyle({backgroundImage: 'none'}); } if(!this.options.endcolor) this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); if(!this.options.restorecolor) this.options.restorecolor = this.element.getStyle('background-color'); // init color calculations this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); }, update: function(position) { this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); }, finish: function() { this.element.setStyle(Object.extend(this.oldStyle, { backgroundColor: this.options.restorecolor })); } }); Effect.ScrollTo = Class.create(); Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); this.start(arguments[1] || {}); }, setup: function() { Position.prepare(); var offsets = Position.cumulativeOffset(this.element); if(this.options.offset) offsets[1] += this.options.offset; var max = window.innerHeight ? window.height - window.innerHeight : document.body.scrollHeight - (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight); this.scrollStart = Position.deltaY; this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; }, update: function(position) { Position.prepare(); window.scrollTo(Position.deltaX, this.scrollStart + (position*this.delta)); } }); /* ------------- combination effects ------------- */ Effect.Fade = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, afterFinishInternal: function(effect) { if(effect.options.to!=0) return; effect.element.hide().setStyle({opacity: oldOpacity}); }}, arguments[1] || {}); return new Effect.Opacity(element,options); } Effect.Appear = function(element) { element = $(element); var options = Object.extend({ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, // force Safari to render floated elements properly afterFinishInternal: function(effect) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || {}); return new Effect.Opacity(element,options); } Effect.Puff = function(element) { element = $(element); var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; return new Effect.Parallel( [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { Position.absolutize(effect.effects[0].element) }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } }, arguments[1] || {}) ); } Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }, arguments[1] || {}) ); } Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } }, arguments[1] || {})); } Effect.SwitchOff = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); return new Effect.Appear(element, Object.extend({ duration: 0.4, from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } }) } }, arguments[1] || {})); } Effect.DropOut = function(element) { element = $(element); var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); } }, arguments[1] || {})); } Effect.Shake = function(element) { element = $(element); var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left') }; return new Effect.Move(element, { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); }}) }}) }}) }}) }}) }}); } Effect.SlideDown = function(element) { element = $(element).cleanWhitespace(); // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if(window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || {}) ); } Effect.SlideUp = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); return new Effect.Scale(element, window.opera ? 0 : 1, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, restoreAfterFinish: true, beforeStartInternal: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if(window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); effect.element.down().undoPositioned(); } }, arguments[1] || {}) ); } // Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }); } Effect.Grow = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || {}); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; switch (options.direction) { case 'top-left': initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; initialMoveY = moveY = 0; moveX = -dims.width; break; case 'bottom-left': initialMoveX = moveX = 0; initialMoveY = dims.height; moveY = -dims.height; break; case 'bottom-right': initialMoveX = dims.width; initialMoveY = dims.height; moveX = -dims.width; moveY = -dims.height; break; case 'center': initialMoveX = dims.width / 2; initialMoveY = dims.height / 2; moveX = -dims.width / 2; moveY = -dims.height / 2; break; } return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, afterFinishInternal: function(effect) { new Effect.Parallel( [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ) } }); } Effect.Shrink = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || {}); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var moveX, moveY; switch (options.direction) { case 'top-left': moveX = moveY = 0; break; case 'top-right': moveX = dims.width; moveY = 0; break; case 'bottom-left': moveX = 0; moveY = dims.height; break; case 'bottom-right': moveX = dims.width; moveY = dims.height; break; case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) ], Object.extend({ beforeStartInternal: function(effect) { effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); } Effect.Pulsate = function(element) { element = $(element); var options = arguments[1] || {}; var oldOpacity = element.getInlineOpacity(); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; reverser.bind(transition); return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); } Effect.Fold = function(element) { element = $(element); var oldStyle = { top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; element.makeClipping(); return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { new Effect.Scale(element, 1, { scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); } }); }}, arguments[1] || {})); }; Effect.Morph = Class.create(); Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); if(!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ style: {} }, arguments[1] || {}); if (typeof options.style == 'string') { if(options.style.indexOf(':') == -1) { var cssText = '', selector = '.' + options.style; $A(document.styleSheets).reverse().each(function(styleSheet) { if (styleSheet.cssRules) cssRules = styleSheet.cssRules; else if (styleSheet.rules) cssRules = styleSheet.rules; $A(cssRules).reverse().each(function(rule) { if (selector == rule.selectorText) { cssText = rule.style.cssText; throw $break; } }); if (cssText) throw $break; }); this.style = cssText.parseStyle(); options.afterFinishInternal = function(effect){ effect.element.addClassName(effect.options.style); effect.transforms.each(function(transform) { if(transform.style != 'opacity') effect.element.style[transform.style.camelize()] = ''; }); } } else this.style = options.style.parseStyle(); } else this.style = $H(options.style) this.start(options); }, setup: function(){ function parseColor(color){ if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ return parseInt( color.slice(i*2+1,i*2+3), 16 ) }); } this.transforms = this.style.map(function(pair){ var property = pair[0].underscore().dasherize(), value = pair[1], unit = null; if(value.parseColor('#zzzzzz') != '#zzzzzz') { value = value.parseColor(); unit = 'color'; } else if(property == 'opacity') { value = parseFloat(value); if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); } else if(Element.CSS_LENGTH.test(value)) var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; var originalValue = this.element.getStyle(property); return $H({ style: property, originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }); }.bind(this)).reject(function(transform){ return ( (transform.originalValue == transform.targetValue) || ( transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) ) }); }, update: function(position) { var style = $H(), value = null; this.transforms.each(function(transform){ value = transform.unit=='color' ? $R(0,2).inject('#',function(m,v,i){ return m+(Math.round(transform.originalValue[i]+ (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : transform.originalValue + Math.round( ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; style[transform.style] = value; }); this.element.setStyle(style); } }); Effect.Transform = Class.create(); Object.extend(Effect.Transform.prototype, { initialize: function(tracks){ this.tracks = []; this.options = arguments[1] || {}; this.addTracks(tracks); }, addTracks: function(tracks){ tracks.each(function(track){ var data = $H(track).values().first(); this.tracks.push($H({ ids: $H(track).keys().first(), effect: Effect.Morph, options: { style: data } })); }.bind(this)); return this; }, play: function(){ return new Effect.Parallel( this.tracks.map(function(track){ var elements = [$(track.ids) || $$(track.ids)].flatten(); return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); }).flatten(), this.options ); } }); Element.CSS_PROPERTIES = $w( 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 'fontSize fontWeight height left letterSpacing lineHeight ' + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.prototype.parseStyle = function(){ var element = Element.extend(document.createElement('div')); element.innerHTML = '

    '; var style = element.down().style, styleRules = $H(); Element.CSS_PROPERTIES.each(function(property){ if(style[property]) styleRules[property] = style[property]; }); if(/MSIE/.test(navigator.userAgent) && !window.opera && this.indexOf('opacity') > -1) { styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]; } return styleRules; }; Element.morph = function(element, style) { new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); return element; }; ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( function(f) { Element.Methods[f] = Element[f]; } ); Element.Methods.visualEffect = function(element, effect, options) { s = effect.gsub(/_/, '-').camelize(); effect_class = s.charAt(0).toUpperCase() + s.substring(1); new Effect[effect_class](element, options); return $(element); }; Element.addMethods(); y: initialMgMTP/web/scripts/prototype.js000064401651440000012000002131351161625545400172150ustar00darranstaff00003030200010/* Prototype JavaScript framework, version 1.5.0 * (c) 2005-2007 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://prototype.conio.net/ * /*--------------------------------------------------------------------------*/ var Prototype = { Version: '1.5.0', BrowserFeatures: { XPath: !!document.evaluate }, ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function() {}, K: function(x) { return x } } var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } } var Abstract = new Object(); Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; } Object.extend(Object, { inspect: function(object) { try { if (object === undefined) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : object.toString(); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } }, keys: function(object) { var keys = []; for (var property in object) keys.push(property); return keys; }, values: function(object) { var values = []; for (var property in object) values.push(object[property]); return values; }, clone: function(object) { return Object.extend({}, object); } }); Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } } Function.prototype.bindAsEventListener = function(object) { var __method = this, args = $A(arguments), object = args.shift(); return function(event) { return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); } } Object.extend(Number.prototype, { toColorPart: function() { var digits = this.toString(16); if (this < 16) return '0' + digits; return digits; }, succ: function() { return this + 1; }, times: function(iterator) { $R(0, this, true).each(iterator); return this; } }); var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) {} } return returnValue; } } /*--------------------------------------------------------------------------*/ var PeriodicalExecuter = Class.create(); PeriodicalExecuter.prototype = { initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.callback(this); } finally { this.currentlyExecuting = false; } } } } String.interpret = function(value){ return value == null ? '' : String(value); } Object.extend(String.prototype, { gsub: function(pattern, replacement) { var result = '', source = this, match; replacement = arguments.callee.prepareReplacement(replacement); while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; }, sub: function(pattern, replacement, count) { replacement = this.gsub.prepareReplacement(replacement); count = count === undefined ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); }, scan: function(pattern, iterator) { this.gsub(pattern, iterator); return this; }, truncate: function(length, truncation) { length = length || 30; truncation = truncation === undefined ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : this; }, strip: function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }, stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, stripScripts: function() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, extractScripts: function() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); }, evalScripts: function() { return this.extractScripts().map(function(script) { return eval(script) }); }, escapeHTML: function() { var div = document.createElement('div'); var text = document.createTextNode(this); div.appendChild(text); return div.innerHTML; }, unescapeHTML: function() { var div = document.createElement('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) : div.childNodes[0].nodeValue) : ''; }, toQueryParams: function(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return {}; return match[1].split(separator || '&').inject({}, function(hash, pair) { if ((pair = pair.split('='))[0]) { var name = decodeURIComponent(pair[0]); var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; if (hash[name] !== undefined) { if (hash[name].constructor != Array) hash[name] = [hash[name]]; if (value) hash[name].push(value); } else hash[name] = value; } return hash; }); }, toArray: function() { return this.split(''); }, succ: function() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); }, camelize: function() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; }, capitalize: function(){ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); }, underscore: function() { return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); }, dasherize: function() { return this.gsub(/_/,'-'); }, inspect: function(useDoubleQuotes) { var escapedString = this.replace(/\\/g, '\\\\'); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; else return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } }); String.prototype.gsub.prepareReplacement = function(replacement) { if (typeof replacement == 'function') return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } String.prototype.parseQuery = String.prototype.toQueryParams; var Template = Class.create(); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; Template.prototype = { initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { return this.template.gsub(this.pattern, function(match) { var before = match[1]; if (before == '\\') return match[2]; return before + String.interpret(object[match[3]]); }); } } var $break = new Object(); var $continue = new Object(); var Enumerable = { each: function(iterator) { var index = 0; try { this._each(function(value) { try { iterator(value, index++); } catch (e) { if (e != $continue) throw e; } }); } catch (e) { if (e != $break) throw e; } return this; }, eachSlice: function(number, iterator) { var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.map(iterator); }, all: function(iterator) { var result = true; this.each(function(value, index) { result = result && !!(iterator || Prototype.K)(value, index); if (!result) throw $break; }); return result; }, any: function(iterator) { var result = false; this.each(function(value, index) { if (result = !!(iterator || Prototype.K)(value, index)) throw $break; }); return result; }, collect: function(iterator) { var results = []; this.each(function(value, index) { results.push((iterator || Prototype.K)(value, index)); }); return results; }, detect: function(iterator) { var result; this.each(function(value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, findAll: function(iterator) { var results = []; this.each(function(value, index) { if (iterator(value, index)) results.push(value); }); return results; }, grep: function(pattern, iterator) { var results = []; this.each(function(value, index) { var stringValue = value.toString(); if (stringValue.match(pattern)) results.push((iterator || Prototype.K)(value, index)); }) return results; }, include: function(object) { var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; }, inGroupsOf: function(number, fillWith) { fillWith = fillWith === undefined ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); }, inject: function(memo, iterator) { this.each(function(value, index) { memo = iterator(memo, value, index); }); return memo; }, invoke: function(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); }, max: function(iterator) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (result == undefined || value >= result) result = value; }); return result; }, min: function(iterator) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (result == undefined || value < result) result = value; }); return result; }, partition: function(iterator) { var trues = [], falses = []; this.each(function(value, index) { ((iterator || Prototype.K)(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, pluck: function(property) { var results = []; this.each(function(value, index) { results.push(value[property]); }); return results; }, reject: function(iterator) { var results = []; this.each(function(value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, sortBy: function(iterator) { return this.map(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); }, toArray: function() { return this.map(); }, zip: function() { var iterator = Prototype.K, args = $A(arguments); if (typeof args.last() == 'function') iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); }, size: function() { return this.toArray().length; }, inspect: function() { return '#'; } } Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, member: Enumerable.include, entries: Enumerable.toArray }); var $A = Array.from = function(iterable) { if (!iterable) return []; if (iterable.toArray) { return iterable.toArray(); } else { var results = []; for (var i = 0, length = iterable.length; i < length; i++) results.push(iterable[i]); return results; } } Object.extend(Array.prototype, Enumerable); if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); }, clear: function() { this.length = 0; return this; }, first: function() { return this[0]; }, last: function() { return this[this.length - 1]; }, compact: function() { return this.select(function(value) { return value != null; }); }, flatten: function() { return this.inject([], function(array, value) { return array.concat(value && value.constructor == Array ? value.flatten() : [value]); }); }, without: function() { var values = $A(arguments); return this.select(function(value) { return !values.include(value); }); }, indexOf: function(object) { for (var i = 0, length = this.length; i < length; i++) if (this[i] == object) return i; return -1; }, reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, reduce: function() { return this.length > 1 ? this : this[0]; }, uniq: function() { return this.inject([], function(array, value) { return array.include(value) ? array : array.concat([value]); }); }, clone: function() { return [].concat(this); }, size: function() { return this.length; }, inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; } }); Array.prototype.toArray = Array.prototype.clone; function $w(string){ string = string.strip(); return string ? string.split(/\s+/) : []; } if(window.opera){ Array.prototype.concat = function(){ var array = []; for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); for(var i = 0, length = arguments.length; i < length; i++) { if(arguments[i].constructor == Array) { for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { array.push(arguments[i]); } } return array; } } var Hash = function(obj) { Object.extend(this, obj || {}); }; Object.extend(Hash, { toQueryString: function(obj) { var parts = []; this.prototype._each.call(obj, function(pair) { if (!pair.key) return; if (pair.value && pair.value.constructor == Array) { var values = pair.value.compact(); if (values.length < 2) pair.value = values.reduce(); else { key = encodeURIComponent(pair.key); values.each(function(value) { value = value != undefined ? encodeURIComponent(value) : ''; parts.push(key + '=' + encodeURIComponent(value)); }); return; } } if (pair.value == undefined) pair[1] = ''; parts.push(pair.map(encodeURIComponent).join('=')); }); return parts.join('&'); } }); Object.extend(Hash.prototype, Enumerable); Object.extend(Hash.prototype, { _each: function(iterator) { for (var key in this) { var value = this[key]; if (value && value == Hash.prototype[key]) continue; var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, keys: function() { return this.pluck('key'); }, values: function() { return this.pluck('value'); }, merge: function(hash) { return $H(hash).inject(this, function(mergedHash, pair) { mergedHash[pair.key] = pair.value; return mergedHash; }); }, remove: function() { var result; for(var i = 0, length = arguments.length; i < length; i++) { var value = this[arguments[i]]; if (value !== undefined){ if (result === undefined) result = value; else { if (result.constructor != Array) result = [result]; result.push(value) } } delete this[arguments[i]]; } return result; }, toQueryString: function() { return Hash.toQueryString(this); }, inspect: function() { return '#'; } }); function $H(object) { if (object && object.constructor == Hash) return object; return new Hash(object); }; ObjectRange = Class.create(); Object.extend(ObjectRange.prototype, Enumerable); Object.extend(ObjectRange.prototype, { initialize: function(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; }, _each: function(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } }, include: function(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } }); var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 } Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (typeof responder[callback] == 'function') { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) {} } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++; }, onComplete: function() { Ajax.activeRequestCount--; } }); Ajax.Base = function() {}; Ajax.Base.prototype = { setOptions: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '' } Object.extend(this.options, options || {}); this.options.method = this.options.method.toLowerCase(); if (typeof this.options.parameters == 'string') this.options.parameters = this.options.parameters.toQueryParams(); } } Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { _complete: false, initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = this.options.parameters; if (!['get', 'post'].include(this.method)) { // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; } params = Hash.toQueryString(params); if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' // when GET, append parameters to URL if (this.method == 'get' && params) this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; try { Ajax.Responders.dispatch('onCreate', this, this.transport); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); var body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (typeof extras.push == 'function') for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { return !this.transport.status || (this.transport.status >= 200 && this.transport.status < 300); }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); if (state == 'Complete') { try { this._complete = true; (this.options['on' + this.transport.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } if ((this.getHeader('Content-type') || 'text/javascript').strip(). match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + state, this, transport, json); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, getHeader: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) { return null } }, evalJSON: function() { try { var json = this.getHeader('X-JSON'); return json ? eval('(' + json + ')') : null; } catch (e) { return null } }, evalResponse: function() { try { return eval(this.transport.responseText); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) } this.transport = Ajax.getTransport(); this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function(transport, param) { this.updateContent(); onComplete(transport, param); }).bind(this); this.request(url); }, updateContent: function() { var receiver = this.container[this.success() ? 'success' : 'failure']; var response = this.transport.responseText; if (!this.options.evalScripts) response = response.stripScripts(); if (receiver = $(receiver)) { if (this.options.insertion) new this.options.insertion(receiver, response); else receiver.update(response); } if (this.success()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } }); Ajax.PeriodicalUpdater = Class.create(); Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = {}; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (typeof element == 'string') element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(query.snapshotItem(i)); return results; }; } document.getElementsByClassName = function(className, parentElement) { if (Prototype.BrowserFeatures.XPath) { var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; return document._getElementsByXPath(q, parentElement); } else { var children = ($(parentElement) || document.body).getElementsByTagName('*'); var elements = [], child; for (var i = 0, length = children.length; i < length; i++) { child = children[i]; if (Element.hasClassName(child, className)) elements.push(Element.extend(child)); } return elements; } }; /*--------------------------------------------------------------------------*/ if (!window.Element) var Element = new Object(); Element.extend = function(element) { if (!element || _nativeExtensions || element.nodeType == 3) return element; if (!element._extended && element.tagName && element != window) { var methods = Object.clone(Element.Methods), cache = Element.extend.cache; if (element.tagName == 'FORM') Object.extend(methods, Form.Methods); if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) Object.extend(methods, Form.Element.Methods); Object.extend(methods, Element.Methods.Simulated); for (var property in methods) { var value = methods[property]; if (typeof value == 'function' && !(property in element)) element[property] = cache.findOrStore(value); } } element._extended = true; return element; }; Element.extend.cache = { findOrStore: function(value) { return this[value] = this[value] || function() { return value.apply(null, [this].concat($A(arguments))); } } }; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { $(element).style.display = 'none'; return element; }, show: function(element) { $(element).style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: function(element, html) { html = typeof html == 'undefined' ? '' : html.toString(); $(element).innerHTML = html.stripScripts(); setTimeout(function() {html.evalScripts()}, 10); return element; }, replace: function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString(); if (element.outerHTML) { element.outerHTML = html.stripScripts(); } else { var range = element.ownerDocument.createRange(); range.selectNodeContents(element); element.parentNode.replaceChild( range.createContextualFragment(html.stripScripts()), element); } setTimeout(function() {html.evalScripts()}, 10); return element; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(); var value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; }, ancestors: function(element) { return $(element).recursivelyCollect('parentNode'); }, descendants: function(element) { return $A($(element).getElementsByTagName('*')); }, immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings()); return []; }, previousSiblings: function(element) { return $(element).recursivelyCollect('previousSibling'); }, nextSiblings: function(element) { return $(element).recursivelyCollect('nextSibling'); }, siblings: function(element) { element = $(element); return element.previousSiblings().reverse().concat(element.nextSiblings()); }, match: function(element, selector) { if (typeof selector == 'string') selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { return Selector.findElement($(element).ancestors(), expression, index); }, down: function(element, expression, index) { return Selector.findElement($(element).descendants(), expression, index); }, previous: function(element, expression, index) { return Selector.findElement($(element).previousSiblings(), expression, index); }, next: function(element, expression, index) { return Selector.findElement($(element).nextSiblings(), expression, index); }, getElementsBySelector: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, getElementsByClassName: function(element, className) { return document.getElementsByClassName(className, element); }, readAttribute: function(element, name) { element = $(element); if (document.all && !window.opera) { var t = Element._attributeTranslations; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; var attribute = element.attributes[name]; if(attribute) return attribute.nodeValue; } return element.getAttribute(name); }, getHeight: function(element) { return $(element).getDimensions().height; }, getWidth: function(element) { return $(element).getDimensions().width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; if (elementClassName.length == 0) return false; if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) return true; return false; }, addClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).add(className); return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).remove(className); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); return element; }, observe: function() { Event.observe.apply(Event, arguments); return $A(arguments).first(); }, stopObserving: function() { Event.stopObserving.apply(Event, arguments); return $A(arguments).first(); }, // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.match(/^\s*$/); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); while (element = element.parentNode) if (element == ancestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = Position.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); if (['float','cssFloat'].include(style)) style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); style = style.camelize(); var value = element.style[style]; if (!value) { if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } else if (element.currentStyle) { value = element.currentStyle[style]; } } if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) value = element['offset'+style.capitalize()] + 'px'; if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) if (Element.getStyle(element, 'position') == 'static') value = 'auto'; if(style == 'opacity') { if(value) return parseFloat(value); if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if(value[1]) return parseFloat(value[1]) / 100; return 1.0; } return value == 'auto' ? null : value; }, setStyle: function(element, style) { element = $(element); for (var name in style) { var value = style[name]; if(name == 'opacity') { if (value == 1) { value = (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; if(/MSIE/.test(navigator.userAgent) && !window.opera) element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); } else if(value === '') { if(/MSIE/.test(navigator.userAgent) && !window.opera) element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); } else { if(value < 0.00001) value = 0; if(/MSIE/.test(navigator.userAgent) && !window.opera) element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + 'alpha(opacity='+value*100+')'; } } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; element.style[name.camelize()] = value; } return element; }, getDimensions: function(element) { element = $(element); var display = $(element).getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = element.style.overflow || 'auto'; if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; } }; Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); Element._attributeTranslations = {}; Element._attributeTranslations.names = { colspan: "colSpan", rowspan: "rowSpan", valign: "vAlign", datetime: "dateTime", accesskey: "accessKey", tabindex: "tabIndex", enctype: "encType", maxlength: "maxLength", readonly: "readOnly", longdesc: "longDesc" }; Element._attributeTranslations.values = { _getAttr: function(element, attribute) { return element.getAttribute(attribute, 2); }, _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { var node = element.getAttributeNode('title'); return node.specified ? node.nodeValue : null; } }; Object.extend(Element._attributeTranslations.values, { href: Element._attributeTranslations.values._getAttr, src: Element._attributeTranslations.values._getAttr, disabled: Element._attributeTranslations.values._flag, checked: Element._attributeTranslations.values._flag, readonly: Element._attributeTranslations.values._flag, multiple: Element._attributeTranslations.values._flag }); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { var t = Element._attributeTranslations; attribute = t.names[attribute] || attribute; return $(element).getAttributeNode(attribute).specified; } }; // IE is missing .innerHTML support for TABLE-related elements if (document.all && !window.opera){ Element.Methods.update = function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString(); var tagName = element.tagName.toUpperCase(); if (['THEAD','TBODY','TR','TD'].include(tagName)) { var div = document.createElement('div'); switch (tagName) { case 'THEAD': case 'TBODY': div.innerHTML = '' + html.stripScripts() + '
    '; depth = 2; break; case 'TR': div.innerHTML = '' + html.stripScripts() + '
    '; depth = 3; break; case 'TD': div.innerHTML = '
    ' + html.stripScripts() + '
    '; depth = 4; } $A(element.childNodes).each(function(node){ element.removeChild(node) }); depth.times(function(){ div = div.firstChild }); $A(div.childNodes).each( function(node){ element.appendChild(node) }); } else { element.innerHTML = html.stripScripts(); } setTimeout(function() {html.evalScripts()}, 10); return element; } }; Object.extend(Element, Element.Methods); var _nativeExtensions = false; if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { var className = 'HTML' + tag + 'Element'; if(window[className]) return; var klass = window[className] = {}; klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; }); Element.addMethods = function(methods) { Object.extend(Element.Methods, methods || {}); function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; var cache = Element.extend.cache; for (var property in methods) { var value = methods[property]; if (!onlyIfAbsent || !(property in destination)) destination[property] = cache.findOrStore(value); } } if (typeof HTMLElement != 'undefined') { copy(Element.Methods, HTMLElement.prototype); copy(Element.Methods.Simulated, HTMLElement.prototype, true); copy(Form.Methods, HTMLFormElement.prototype); [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { copy(Form.Element.Methods, klass.prototype); }); _nativeExtensions = true; } } var Toggle = new Object(); Toggle.display = Element.toggle; /*--------------------------------------------------------------------------*/ Abstract.Insertion = function(adjacency) { this.adjacency = adjacency; } Abstract.Insertion.prototype = { initialize: function(element, content) { this.element = $(element); this.content = content.stripScripts(); if (this.adjacency && this.element.insertAdjacentHTML) { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { var tagName = this.element.tagName.toUpperCase(); if (['TBODY', 'TR'].include(tagName)) { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; } } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); this.insertContent([this.range.createContextualFragment(this.content)]); } setTimeout(function() {content.evalScripts()}, 10); }, contentFromAnonymousTable: function() { var div = document.createElement('div'); div.innerHTML = '' + this.content + '
    '; return $A(div.childNodes[0].childNodes[0].childNodes); } } var Insertion = new Object(); Insertion.Before = Class.create(); Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function() { this.range.setStartBefore(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element); }).bind(this)); } }); Insertion.Top = Class.create(); Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(true); }, insertContent: function(fragments) { fragments.reverse(false).each((function(fragment) { this.element.insertBefore(fragment, this.element.firstChild); }).bind(this)); } }); Insertion.Bottom = Class.create(); Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.appendChild(fragment); }).bind(this)); } }); Insertion.After = Class.create(); Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function() { this.range.setStartAfter(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element.nextSibling); }).bind(this)); } }); /*--------------------------------------------------------------------------*/ Element.ClassNames = Class.create(); Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); }, _each: function(iterator) { this.element.className.split(/\s+/).select(function(name) { return name.length > 0; })._each(iterator); }, set: function(className) { this.element.className = className; }, add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set($A(this).concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set($A(this).without(classNameToRemove).join(' ')); }, toString: function() { return $A(this).join(' '); } }; Object.extend(Element.ClassNames.prototype, Enumerable); var Selector = Class.create(); Selector.prototype = { initialize: function(expression) { this.params = {classNames: []}; this.expression = expression.toString().strip(); this.parseExpression(); this.compileMatcher(); }, parseExpression: function() { function abort(message) { throw 'Parse error in selector: ' + message; } if (this.expression == '') abort('empty expression'); var params = this.params, expr = this.expression, match, modifier, clause, rest; while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { params.attributes = params.attributes || []; params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); expr = match[1]; } if (expr == '*') return this.params.wildcard = true; while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { modifier = match[1], clause = match[2], rest = match[3]; switch (modifier) { case '#': params.id = clause; break; case '.': params.classNames.push(clause); break; case '': case undefined: params.tagName = clause.toUpperCase(); break; default: abort(expr.inspect()); } expr = rest; } if (expr.length > 0) abort(expr.inspect()); }, buildMatchExpression: function() { var params = this.params, conditions = [], clause; if (params.wildcard) conditions.push('true'); if (clause = params.id) conditions.push('element.readAttribute("id") == ' + clause.inspect()); if (clause = params.tagName) conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); if ((clause = params.classNames).length > 0) for (var i = 0, length = clause.length; i < length; i++) conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); if (clause = params.attributes) { clause.each(function(attribute) { var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; var splitValueBy = function(delimiter) { return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; } switch (attribute.operator) { case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; case '|=': conditions.push( splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() ); break; case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; case '': case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; default: throw 'Unknown operator ' + attribute.operator + ' in selector'; } }); } return conditions.join(' && '); }, compileMatcher: function() { this.match = new Function('element', 'if (!element.tagName) return false; \ element = $(element); \ return ' + this.buildMatchExpression()); }, findElements: function(scope) { var element; if (element = $(this.params.id)) if (this.match(element)) if (!scope || Element.childOf(element, scope)) return [element]; scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); var results = []; for (var i = 0, length = scope.length; i < length; i++) if (this.match(element = scope[i])) results.push(Element.extend(element)); return results; }, toString: function() { return this.expression; } } Object.extend(Selector, { matchElements: function(elements, expression) { var selector = new Selector(expression); return elements.select(selector.match.bind(selector)).map(Element.extend); }, findElement: function(elements, expression, index) { if (typeof expression == 'number') index = expression, expression = false; return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { return expressions.map(function(expression) { return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { var selector = new Selector(expr); return results.inject([], function(elements, result) { return elements.concat(selector.findElements(result || element)); }); }); }).flatten(); } }); function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { $(form).reset(); return form; }, serializeElements: function(elements, getHash) { var data = elements.inject({}, function(result, element) { if (!element.disabled && element.name) { var key = element.name, value = $(element).getValue(); if (value != undefined) { if (result[key]) { if (result[key].constructor != Array) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return getHash ? data : Hash.toQueryString(data); } }; Form.Methods = { serialize: function(form, getHash) { return Form.serializeElements(Form.getElements(form), getHash); }, getElements: function(form) { return $A($(form).getElementsByTagName('*')).inject([], function(elements, child) { if (Form.Element.Serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; } ); }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); form.getElements().each(function(element) { element.blur(); element.disabled = 'true'; }); return form; }, enable: function(form) { form = $(form); form.getElements().each(function(element) { element.disabled = ''; }); return form; }, findFirstElement: function(form) { return $(form).getElements().find(function(element) { return element.type != 'hidden' && !element.disabled && ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; } } Object.extend(Form, Form.Methods); /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } } Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = {}; pair[element.name] = value; return Hash.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); element.focus(); if (element.select && ( element.tagName.toLowerCase() != 'input' || !['button', 'reset', 'submit'].include(element.type) ) ) element.select(); return element; }, disable: function(element) { element = $(element); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.blur(); element.disabled = false; return element; } } Object.extend(Form.Element, Form.Element.Methods); var Field = Form.Element; var $F = Form.Element.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); default: return Form.Element.Serializers.textarea(element); } }, inputSelector: function(element) { return element.checked ? element.value : null; }, textarea: function(element) { return element.value; }, select: function(element) { return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } } /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = function() {} Abstract.TimedObserver.prototype = { initialize: function(element, frequency, callback) { this.frequency = frequency; this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); this.registerCallback(); }, registerCallback: function() { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, onTimerEvent: function() { var value = this.getValue(); var changed = ('string' == typeof this.lastValue && 'string' == typeof value ? this.lastValue != value : String(this.lastValue) != String(value)); if (changed) { this.callback(this.element, value); this.lastValue = value; } } } Form.Element.Observer = Class.create(); Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = function() {} Abstract.EventObserver.prototype = { initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback.bind(this)); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } } Form.Element.EventObserver = Class.create(); Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(); Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.serialize(this.element); } }); if (!window.Event) { var Event = new Object(); } Object.extend(Event, { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, element: function(event) { return event.target || event.srcElement; }, isLeftClick: function(event) { return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1))); }, pointerX: function(event) { return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, pointerY: function(event) { return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); }, stop: function(event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } }, // find the first node with the given tagName, starting from the // node the event was triggered on; traverses the DOM upwards findElement: function(event, tagName) { var element = Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))) element = element.parentNode; return element; }, observers: false, _observeAndCache: function(element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { this.observers.push([element, name, observer, useCapture]); element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { this.observers.push([element, name, observer, useCapture]); element.attachEvent('on' + name, observer); } }, unloadCache: function() { if (!Event.observers) return; for (var i = 0, length = Event.observers.length; i < length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; } Event.observers = false; }, observe: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown'; Event._observeAndCache(element, name, observer, useCapture); }, stopObserving: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown'; if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { try { element.detachEvent('on' + name, observer); } catch (e) {} } } }); /* prevent memory leaks in IE */ if (navigator.appVersion.match(/\bMSIE\b/)) Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in // scrollable elements includeScrollOffsets: false, // must be called before calling withinIncludingScrolloffset, every time the // page is scrolled prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, realOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return [valueL, valueT]; }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if(element.tagName=='BODY') break; var p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; } } while (element); return [valueL, valueT]; }, offsetParent: function(element) { if (element.offsetParent) return element.offsetParent; if (element == document.body) return element; while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return element; return document.body; }, // caches x/y coordinate pair to use with overlap within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = this.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = this.realOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = this.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, // within must be called directly before overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, page: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent==document.body) if (Element.getStyle(element,'position')=='absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!window.opera || element.tagName=='BODY') { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return [valueL, valueT]; }, clone: function(source, target) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || {}) // find page position of source source = $(source); var p = Position.page(source); // find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if(options.setWidth) target.style.width = source.offsetWidth + 'px'; if(options.setHeight) target.style.height = source.offsetHeight + 'px'; }, absolutize: function(element) { element = $(element); if (element.style.position == 'absolute') return; Position.prepare(); var offsets = Position.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; }, relativize: function(element) { element = $(element); if (element.style.position == 'relative') return; Position.prepare(); element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; } } // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Position.cumulativeOffset for // KHTML/WebKit only. if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { Position.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return [valueL, valueT]; } } Element.addMethods();ACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, element: function(event) { return event.target || event.srcElement; }, isLeftClick: function(event) { return (((event.which) && (event.which == 1)) || gMTP/web/scripts/builder.js000064401651440000012000000110361161625545400165720ustar00darranstaff00003030200010// script.aculo.us builder.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ var Builder = { NODEMAP: { AREA: 'map', CAPTION: 'table', COL: 'table', COLGROUP: 'table', LEGEND: 'fieldset', OPTGROUP: 'select', OPTION: 'select', PARAM: 'object', TBODY: 'table', TD: 'table', TFOOT: 'table', TH: 'table', THEAD: 'table', TR: 'table' }, // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, // due to a Firefox bug node: function(elementName) { elementName = elementName.toUpperCase(); // try innerHTML approach var parentTag = this.NODEMAP[elementName] || 'div'; var parentElement = document.createElement(parentTag); try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 parentElement.innerHTML = "<" + elementName + ">"; } catch(e) {} var element = parentElement.firstChild || null; // see if browser added wrapping tags if(element && (element.tagName.toUpperCase() != elementName)) element = element.getElementsByTagName(elementName)[0]; // fallback to createElement approach if(!element) element = document.createElement(elementName); // abort if nothing could be created if(!element) return; // attributes (or text) if(arguments[1]) if(this._isStringOrNumber(arguments[1]) || (arguments[1] instanceof Array)) { this._children(element, arguments[1]); } else { var attrs = this._attributes(arguments[1]); if(attrs.length) { try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 parentElement.innerHTML = "<" +elementName + " " + attrs + ">"; } catch(e) {} element = parentElement.firstChild || null; // workaround firefox 1.0.X bug if(!element) { element = document.createElement(elementName); for(attr in arguments[1]) element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; } if(element.tagName.toUpperCase() != elementName) element = parentElement.getElementsByTagName(elementName)[0]; } } // text, or array of children if(arguments[2]) this._children(element, arguments[2]); return element; }, _text: function(text) { return document.createTextNode(text); }, ATTR_MAP: { 'className': 'class', 'htmlFor': 'for' }, _attributes: function(attributes) { var attrs = []; for(attribute in attributes) attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + '="' + attributes[attribute].toString().escapeHTML() + '"'); return attrs.join(" "); }, _children: function(element, children) { if(typeof children=='object') { // array can hold nodes and text children.flatten().each( function(e) { if(typeof e=='object') element.appendChild(e) else if(Builder._isStringOrNumber(e)) element.appendChild(Builder._text(e)); }); } else if(Builder._isStringOrNumber(children)) element.appendChild(Builder._text(children)); }, _isStringOrNumber: function(param) { return(typeof param=='string' || typeof param=='number'); }, build: function(html) { var element = this.node('div'); $(element).update(html.strip()); return element.down(); }, dump: function(scope) { if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); tags.each( function(tag){ scope[tag] = function() { return Builder.node.apply(Builder, [tag].concat($A(arguments))); } }); } } gMTP/web/scripts/slider.js000064401651440000012000000242431161625545400164320ustar00darranstaff00003030200010// script.aculo.us slider.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 // Copyright (c) 2005, 2006 Marty Haught, Thomas Fuchs // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ if(!Control) var Control = {}; Control.Slider = Class.create(); // options: // axis: 'vertical', or 'horizontal' (default) // // callbacks: // onChange(value) // onSlide(value) Control.Slider.prototype = { initialize: function(handle, track, options) { var slider = this; if(handle instanceof Array) { this.handles = handle.collect( function(e) { return $(e) }); } else { this.handles = [$(handle)]; } this.track = $(track); this.options = options || {}; this.axis = this.options.axis || 'horizontal'; this.increment = this.options.increment || 1; this.step = parseInt(this.options.step || '1'); this.range = this.options.range || $R(0,1); this.value = 0; // assure backwards compat this.values = this.handles.map( function() { return 0 }); this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; this.options.startSpan = $(this.options.startSpan || null); this.options.endSpan = $(this.options.endSpan || null); this.restricted = this.options.restricted || false; this.maximum = this.options.maximum || this.range.end; this.minimum = this.options.minimum || this.range.start; // Will be used to align the handle onto the track, if necessary this.alignX = parseInt(this.options.alignX || '0'); this.alignY = parseInt(this.options.alignY || '0'); this.trackLength = this.maximumOffset() - this.minimumOffset(); this.handleLength = this.isVertical() ? (this.handles[0].offsetHeight != 0 ? this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : this.handles[0].style.width.replace(/px$/,"")); this.active = false; this.dragging = false; this.disabled = false; if(this.options.disabled) this.setDisabled(); // Allowed values array this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; if(this.allowedValues) { this.minimum = this.allowedValues.min(); this.maximum = this.allowedValues.max(); } this.eventMouseDown = this.startDrag.bindAsEventListener(this); this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.update.bindAsEventListener(this); // Initialize handles in reverse (make sure first handle is active) this.handles.each( function(h,i) { i = slider.handles.length-1-i; slider.setValue(parseFloat( (slider.options.sliderValue instanceof Array ? slider.options.sliderValue[i] : slider.options.sliderValue) || slider.range.start), i); Element.makePositioned(h); // fix IE Event.observe(h, "mousedown", slider.eventMouseDown); }); Event.observe(this.track, "mousedown", this.eventMouseDown); Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); this.initialized = true; }, dispose: function() { var slider = this; Event.stopObserving(this.track, "mousedown", this.eventMouseDown); Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); this.handles.each( function(h) { Event.stopObserving(h, "mousedown", slider.eventMouseDown); }); }, setDisabled: function(){ this.disabled = true; }, setEnabled: function(){ this.disabled = false; }, getNearestValue: function(value){ if(this.allowedValues){ if(value >= this.allowedValues.max()) return(this.allowedValues.max()); if(value <= this.allowedValues.min()) return(this.allowedValues.min()); var offset = Math.abs(this.allowedValues[0] - value); var newValue = this.allowedValues[0]; this.allowedValues.each( function(v) { var currentOffset = Math.abs(v - value); if(currentOffset <= offset){ newValue = v; offset = currentOffset; } }); return newValue; } if(value > this.range.end) return this.range.end; if(value < this.range.start) return this.range.start; return value; }, setValue: function(sliderValue, handleIdx){ if(!this.active) { this.activeHandleIdx = handleIdx || 0; this.activeHandle = this.handles[this.activeHandleIdx]; this.updateStyles(); } handleIdx = handleIdx || this.activeHandleIdx || 0; if(this.initialized && this.restricted) { if((handleIdx>0) && (sliderValuethis.values[handleIdx+1])) sliderValue = this.values[handleIdx+1]; } sliderValue = this.getNearestValue(sliderValue); this.values[handleIdx] = sliderValue; this.value = this.values[0]; // assure backwards compat this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = this.translateToPx(sliderValue); this.drawSpans(); if(!this.dragging || !this.event) this.updateFinished(); }, setValueBy: function(delta, handleIdx) { this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, handleIdx || this.activeHandleIdx || 0); }, translateToPx: function(value) { return Math.round( ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * (value - this.range.start)) + "px"; }, translateToValue: function(offset) { return ((offset/(this.trackLength-this.handleLength) * (this.range.end-this.range.start)) + this.range.start); }, getRange: function(range) { var v = this.values.sortBy(Prototype.K); range = range || 0; return $R(v[range],v[range+1]); }, minimumOffset: function(){ return(this.isVertical() ? this.alignY : this.alignX); }, maximumOffset: function(){ return(this.isVertical() ? (this.track.offsetHeight != 0 ? this.track.offsetHeight : this.track.style.height.replace(/px$/,"")) - this.alignY : (this.track.offsetWidth != 0 ? this.track.offsetWidth : this.track.style.width.replace(/px$/,"")) - this.alignY); }, isVertical: function(){ return (this.axis == 'vertical'); }, drawSpans: function() { var slider = this; if(this.spans) $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); if(this.options.startSpan) this.setSpan(this.options.startSpan, $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); if(this.options.endSpan) this.setSpan(this.options.endSpan, $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); }, setSpan: function(span, range) { if(this.isVertical()) { span.style.top = this.translateToPx(range.start); span.style.height = this.translateToPx(range.end - range.start + this.range.start); } else { span.style.left = this.translateToPx(range.start); span.style.width = this.translateToPx(range.end - range.start + this.range.start); } }, updateStyles: function() { this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); Element.addClassName(this.activeHandle, 'selected'); }, startDrag: function(event) { if(Event.isLeftClick(event)) { if(!this.disabled){ this.active = true; var handle = Event.element(event); var pointer = [Event.pointerX(event), Event.pointerY(event)]; var track = handle; if(track==this.track) { var offsets = Position.cumulativeOffset(this.track); this.event = event; this.setValue(this.translateToValue( (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) )); var offsets = Position.cumulativeOffset(this.activeHandle); this.offsetX = (pointer[0] - offsets[0]); this.offsetY = (pointer[1] - offsets[1]); } else { // find the handle (prevents issues with Safari) while((this.handles.indexOf(handle) == -1) && handle.parentNode) handle = handle.parentNode; if(this.handles.indexOf(handle)!=-1) { this.activeHandle = handle; this.activeHandleIdx = this.handles.indexOf(this.activeHandle); this.updateStyles(); var offsets = Position.cumulativeOffset(this.activeHandle); this.offsetX = (pointer[0] - offsets[0]); this.offsetY = (pointer[1] - offsets[1]); } } } Event.stop(event); } }, update: function(event) { if(this.active) { if(!this.dragging) this.dragging = true; this.draw(event); // fix AppleWebKit rendering if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); Event.stop(event); } }, draw: function(event) { var pointer = [Event.pointerX(event), Event.pointerY(event)]; var offsets = Position.cumulativeOffset(this.track); pointer[0] -= this.offsetX + offsets[0]; pointer[1] -= this.offsetY + offsets[1]; this.event = event; this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); if(this.initialized && this.options.onSlide) this.options.onSlide(this.values.length>1 ? this.values : this.value, this); }, endDrag: function(event) { if(this.active && this.dragging) { this.finishDrag(event, true); Event.stop(event); } this.active = false; this.dragging = false; }, finishDrag: function(event, success) { this.active = false; this.dragging = false; this.updateFinished(); }, updateFinished: function() { if(this.initialized && this.options.onChange) this.options.onChange(this.values.length>1 ? this.values : this.value, this); this.event = null; } }gMTP/web/scripts/frog.js000064401651440000012000000227511161625545400161070ustar00darranstaff00003030200010//------------------------------------------------------------- // FrogJS v.1.1 // Created by Eric Puidokas (www.puidokas.com) // // Licensed under the Creative Commons Attribution 2.5 License // (http://creativecommons.org/licenses/by/2.5/) //------------------------------------------------------------- // CONFIGURATION VARIABLES var thumbTop = '60px'; // distance you want you thumbnails to be from the top of their container var loadingAni = 'images/loading.gif'; // image displayed when preloading images var scalePercent = '300'; // percent thumbnails expand when fading into main image // GLOBALS var imageArray = new Array; // Extensions to the Element class from prototype.js Object.extend(Element, { removeDimensions: function(element){ element = $(element); element.style.width = ''; element.style.height = ''; }, removeOnclick: function(element){ element = $(element); element.onclick = function(){} }, setCursor: function(element, cursor){ element = $(element); element.style.cursor = cursor; } }); // Frog Class var Frog = Class.create(); Frog.prototype = { // initialize() // Constructor runs once the page has been loaded. It extracts any linked images within an element with id 'FrogJS' and builds the array for the FrogJS Gallery // It then empties the 'FrogJS' element and inserts the neccessary DOM elements to run a FrogJS gallery. Lastly, it calls the functions to load the first image and thumbnail. initialize: function(){ if(!document.getElementsByTagName){ return; } // Builds imageArray of all images, thumbnails, credits, and captions var anchors = $('FrogJS').getElementsByTagName('a'); for (var i=0; i gMTP v1.3.4 - A simple MP3 and Media Player Client for UNIX and UNIX like systems

    gMTP v1.3.4

    A simple MP3 and Media player client for UNIX and UNIX like systems.

    Darran Kartaschew (aka Chewy509).
    Released under the BSD License. 2009-2012.

    A simple MP3 player client for MTP based devices.

    Due to Oracle Solaris 10 not having a native MTP based MP3 player support application, I've written my own GUI client for MTP devices. It supports:

    • Upload, Download, Removal, Renaming and Moving of files as needed.
    • Drag'n'Drop support for uploading files to the device.
    • Folder Creation and Deletion.
    • Album Art management.
    • Metadata support for MP3, WMA, OGG and FLAC audio files, ensuring correct track information on your media player when uploading audio files.
    • Device Naming support.
    • Basic creation, editing and deletion of playlists. Able to import and export playlists in *.m3u format.

    For other needs like managing your audio collection or ripping CDs I suggest you look at another full featured media application.

    Support and Discussion Boards

    Support and Discussion Boards can be found on the Sourceforge project page located here: http://sourceforge.net/projects/gmtp

    Requirements

    • Oracle Solaris 10, other UNIX or UNIX-like system
    • libvorbis (v1.0.1)
    • libflac (v1.2+) - FLAC Hompage
    • libmtp (v1.1.5) - LIBMTP Hompage
    • libid3tag (v0.15.1b) - MAD Download Page
    • GTK2+ Version. (Recommended for Solaris, FreeBSD and older Linux Distributions)
      • GTK+-2.0 (v2.4.9)
      • Glib-2.0 (v2.4.1)
      • GConf-2.0 (v2.6.1)
    • GTK3+ Version. (Recommended for newer Linux Distributions)
      • GTK+-3.0 (v3.0.0)
      • Glib-2.0 (v2.26.0)
      • GIO-2.0 (v2.26.0) - Needed for GSettings support within GLib-2.26.

    Installation (Binary)

    Oracle Solaris 10
    Download SYSV package, gunzip it and use 'pkgadd -d gMTP-1.3.4-i386.pkg' to install.
    Debian GNU/Linux
    gMTP is available in the unstable (sid) package lists. Use 'apt-get' to install.
    Ubuntu
    gMTP is available in the universe package lists. Use 'apt-get' to install.
    Arch Linux
    gMTP is available in the AUR (Arch User Repository). See: https://aur.archlinux.org/packages.php?ID=47774
    Gentoo Linux
    gMTP is available in the main repository. Use 'emerge gmtp' to install.
    The Chakra Project
    gMTP is available in the CCR. See: http://chakra-project.org/ccr/packages.php?ID=1893
    Calculate Linux
    gMTP is available in the main repository. Use 'emerge gmtp' to install.
    Slackware
    A private Slackware 14 x86_64 package has been built and available here

    Installation (Source)

    These instructions are from a Oracle Solaris 10 perspective. You may need to adjust some commands to accommodate your respective Operating Systems.

    expand section libmtp

    Download the libmtp source code, unpack and run:

    $ CFLAGS="-I/usr/sfw/lib -L/usr/sfw/lib -R/usr/sfw/lib" INSTALL=/usr/ucb/install MAKE=gmake ./configure
    $ gmake
    # gmake install

    Note: If you don't have a libusb.pc file (configure may complain about libusb being missing on Solaris 10/11), then you can use this one. Copy to /usr/lib/pkgconfig/libusb.pc.

    Once installed, test the mtp package by connecting your MP3 player, and run mtp-detect from a terminal prompt. If all is well, you should see your device details scroll across the screen.

    expand section libid3tag

    Download the libid3tag source code, unpack and run:

    $ INSTALL=/usr/ucb/install MAKE=gmake ./configure
    $ gmake
    # gmake install

    Copy id3tag.pc to /usr/local/lib/pkgconfig/id3tag.pc

    expand section libflac

    Download the FLAC source code, unpack and run:

    $ INSTALL=/usr/ucb/install MAKE=gmake ./configure
    $ gmake
    # gmake install

    expand section gMTP

    Download and unpack the source code. Update the PREFIX variable in the Makefile if you want to install to anywhere other than /usr/local.

    GTK+2 Version (Recommended for Solaris, FreeBSD and older Linux Distributions)

    $ gmake
    # gmake install
    # gmake register-gconf-schemas

    GTK+3 Version (Recommended for new Linux Distributions)

    $ gmake gtk3
    # gmake install-gtk3
    # gmake register-gsettings-schemas

    Then run

    $ gmtp

    to start the application. Feel free to add a launcher or shortcut to the main menu as needed.

    Note: For Solaris 10/OpenSolaris/Solaris 11 Express, the default makefile assumes you have SunStudio 12 installed. If you don't then modify the Makefile to use gcc instead. All other OSes will use gcc as default.

    Note: On Linux based systems, use make instead of gmake.

    Downloads

    Current Release

    gMTP Source: gMTP-1.3.4-i386.tar.gz

    gMTP Solaris 10 Package: gMTP-1.3.4-i386.pkg.gz (Warning: Does NOT contain libmtp, libflac or libid3tag).

    Previous Releases

    All previous releases are available from gMTP Downloads on Sourceforge

    CVS Repository

    Anonymous read-only access is available via:

    $ cvs -z3 -d:pserver:anonymous@gmtp.cvs.sourceforge.net:/cvsroot/gmtp checkout -P Hellfire/gMTP

    or via web: http://gmtp.cvs.sourceforge.net/viewvc/gmtp/

    Please be aware that the CVS version may be horribly broken or may not even compile, and I would always recommend the latest stable release for general use.

    Usage

    run

    $ gmtp

    To start gMTP.

    Toolbar

    Toolbar

    The main Toolbar contains the main functions available for using MTP based devices, or alternative use the main menu or right click on the file area to gain access to basic file operations. All of these are self described.

    Working with Files and Folders

    The default view of the device, is to list all files and folders within the main window. Different columns for display may be chosen from the View menu. Below is a screen shot of all the different icons that are shown within gMTP to signify different file types.

    Folder

    Starting from the top these are:

    1. Folders
    2. Generic Files
    3. Plain text Files
    4. Playlist Files
    5. Multimedia Files
    6. Audio Files
    7. Album Information Files
    8. Image Files

    All file operations can be accessed from the File menu, or alternatively, right clicking within the view area. Double clicking on a file will download it to your PC/Laptop, and double clicking on a folder will access the contents of the folder. Folders with .. let you move up in the folder structure, (go back to the previous folder).

    Drag'n'Drop support is enabled for uploading files to the device. Simply drop the desired files/folder into gMTP to upload the files/folders to the device.

    Preferences

    Preferences

    Application preferences may be accessed via the toolbar, or through the Edit > Preferences menu. Use the Preferences to set upload and download paths. (Note: the last upload/download path is saved when you exit the application).

    • Attempt to connect to Device on startup.
      Enable to auto connect to a device when gMTP starts.
    • Utilize alternate access method.
      Enable to use uncached mode for connecting to a device. Note, that when this is enabled, the folder view is disabled completely and moving files is not possible. (Use this option is you experience issues either connecting, browsing or performing file operations with the current connected device).
    • Confirm File/Folder Delete.
      Enables the confirmation of file delete operations.
    • Prompt to Overwrite file if already exists.
      Enables the confirmation dialog if the correct file operation will overwrite another file.
    • Suppress Album Errors.
      Enable this option if your device does not support album metadata, and you don't wish to see errors related to this missing device functionality.
    • Prompt to add New Music tracks to existing playlist.
      Enables this to be prompted for automatic playlist management when uploading music files to the device.
    • Ignore path information when importing playlist.
      Enable to have gMTP ignore file path information when importing playlists onto the device. (Attempt to find the track irrespective of the folder it resides in).
    • Always show download path.
      When downloading files from the device to the host PC, always show the download location dialog to select the download path.

    Playlists

    gMTP is able to import and export playlists in m3u format, with some restrictions. These restrictions are:

    1. EXTM3U tags and information is ignored.
    2. A "#GMTPPLA: " tag can be used to set the playlist name. This tag may exist anywhere in the file. For example to set the playlist name to "Good Songs", then use "#GMTPPLA: Good Songs" within the m3u file on it's own line. If this tag is not found, then the m3u filename is used as the playlist name (minus the "m3u" file extension).
    3. All paths indicated within the m3u playlist file are expected to be relative based on the file location within your MTP device, and NOT relative to the location of the m3u file itself within the PC local filesystem. You may set the "Ignore path information when importing playlist" setting in preferences so that gMTP will ignore any file path information and search all folders for the music file.
    4. All files indicated within the m3u must exist within the current mounted storage device for those devices that support or have multiple storage pools. (eg Mobile Phones, Tablets).
    5. If no files are located on the device as indicated within the m3u playlist file, then the playlist will not be created.

    FAQ

    Q. What is MTP?
    A. MTP = Media Transfer Protocol. MTP has been adopted by most major MP3 and Mobile Phone manufacturers as the method of talking to devices to upload/download files to/from a PC. See Media Transfer Protocol for more information.

    Q. Why doesn't gMTP support my iPod or Creative Nomad player
    A. These devices do not use MTP for moving data to/from a device. Apple iPod uses it's own custom protocol in additional to USB Mass Storage and Creative devices use NJB.

    Q. I have a MTP enabled device and it is connected to my PC, but it doesn't get detected by gMTP?
    A. Most devices are capable of using different modes to talk to your PC, so ensure that the device is in MTP mode.
    A. Or, libmtp doesn't know about your device or how to handle it correctly. Run $ mtp-detect to see if it can be found.
    A. Occasionally, some devices become confused if you connect and disconnect them in gMTP, but do not physically disconnect/reconnect them. In this instance, close gMTP, physically disconnect and reconnect the device, and restart gMTP. It should connect.

    Q. I have a MTP enabled device, it connects, but gMTP crashes or the device resets?
    A. There may be a bug in gMTP or libmtp that is causing the crash. Please file a bug report with the gMTP project ticket system and it will be looked at.
    A. Some MTP implementations on devices are horribly broken, or have known issues with libmtp. Unfortunately there is little that can be done about these devices except complaining to the device vendor to test their device on UNIX with libmtp as the connection library.

    Q. Do you have a list of devices that work with gMTP?
    A. Unfortunately no. (I test with an iRiver Clix 2G and Sony Ericsson K800i phone, and both of these work extremely well).

    Q. I get asked which storage device to connect to when I connect to my mobile phone?
    A. Some devices (notably mobile phones), have both internal storage (non-removable) and external storage (removable storage) in the form of a micro-SD card or M2 card, and gMTP will treat these as different storage devices.

    Q. Does gMTP support Albums and uploading Album Art?
    A. Yes. Album data is autocreated/updated when you upload a MP3 (or other supported audio file) by using information contained within the audio file, eg. Using the ID3 Tag information in an MP3 file. Once the Album has been created, you can upload the album art via the Edit / Album Art menu option.

    Q. Will this software work on OpenSolaris, Linux, *BSD or other POSIX Operating System
    A. I have reports that it runs successfully on OpenSolaris, Arch Linux, Debian and Ubuntu.

    Q. What about SPARC, ARM or other non-x86 systems?
    A. It should work fine but is untested. (If libmtp and libid3tag work fine on your platform, then gMTP should as well).

    Q. Do I need root access to use gMTP?
    A. On Solaris 10, in general No. (If you do need root access, then double check your RBAC setup for your user then). On Linux, in general No, as libmtp should have set your udev rules correctly for libmtp known devices.

    Q. In the file view or playlist editor, tracks have a length of 0:00?
    A. The length field displayed is reliant on the track data being set correctly when the audio file was uploaded. Some file transfer utilities do not set this information correctly (and earlier versions of gMTP are also guilty of this). Simply download and re-upload the audio file using gMTP to correct the track data on the player.

    Q. I have the same audio file loaded on my device in different formats, but the song duration is different between them.
    A. WMA, FLAC and OGG all store the song duration in header information, and this is set by the encoder used to create the file. It may be a bug with the encoder? With MP3 files, the track duration is calculated when the file is uploaded, so this information should be correct unless you have a corrupt MP3 file.

    Q. The translations are pretty awful or just plain wrong, or why don't you have xyz language?
    A. The initial translations were done using Google Translate services, so accuracy is not 100%. Please email me with corrections to existing translations. If you would like a particular language added, and are happy to assist, please let me know or simply email me with the correct *.po file with the translations for your language.

    Q. I'm using French Canadian (fr_CA.UTF-8) as my locale on Solaris 10, but I don't get French translations?
    A. This is due to an idiosyncrasy on Solaris 10 and language translations. Either:

    1. Copy the gmtp.mo file from /usr/local/share/locale/fr/LC_MESSAGES to /usr/local/share/locale/fr_CA.UTF-8/LC_MESSAGES, and restart gmtp.
    2. or, create a symlink fr_CA.UTF-8 pointing to fr in /usr/local/share/locale using 'ln -s fr fr_CA.UTF-8'.

    French translations should now be present. (Technical information: On Solaris, the gettext() call will only look in the current locale folder as defined by the LC_MESSAGES environment variable and not the base language folder as well for translations, so if the locale is set to 'fr_CA.UTF-8', gettext() will only look in that locale folder and not 'fr' as well - which is what the GNU version of gettext() does). This applies to all languages on Solaris 10. For Linux/FreeBSD uses this should not be an issue as most will use the GNU version of gettext().

    Q. The column view options do not appear to be working?
    A. The gconf schema was updated in v0.8. Please update your local schema file.

    Q. I'm attempting to install gMTP from source and it's complaining about missing files?
    A. Please ensure all dependencies have been installed prior to attempting to build gMTP.
    A. Some operating systems separate header packages (needed to build software) from the main software component package. Please ensure these are installed as well.
    A. On some Linux distributions, id3tag is installed without a matching PKGCONFIG file which is required to build gMTP. An example id3tag.pc file can be found on the Installation page, under 'id3tag'.

    Q. I'm attempting to move some files, and I always get an error. What's the issue?
    A. gMTP uses the MTP function 'moveObject' to perform move operations. However only a few devices actually support this function, and if they do actually advertise it supports the function, it may be horribly broken. Basically complain to your device manufacturer that it doesn't support this function, and that they should add it in, since it is actually part of the MTP Specification. To see if your device has this option available, run 'mtp-detect' and look under the supported commands for command '1019: MoveObject'. The other method I could use is to download the files/folders to your PC, re-upload them again and delete the originals, however this is very time intensive, and in these cases it's better than the user do this themselves.

    Q. I have an Android device and .... isn't working?
    A. There are a few answers or explanations with Android.

    1. Some device vendors have opted to use their own MTP software stack with their device, and some of these implementations are horribly broken. Contact your device manufacturer for further assistance. (AFAIK, this is primarily Samsung with Android 2.x devices, and some lesser known Chinese developed handsets often rebadged as Carrier own-brand handsets).
    2. Android 1.x-2.x doesn't have a native MTP implementation, so if your device has MTP functionality, see the above comment.
    3. Android 3.x-4.x has native MTP functionality, but is missing some features and does have some bugs in the implementation. Some noted issues:
      1. Does not support albums or album metadata. So you can't upload custom album art.
      2. Some users have reported issues with Playlist support. (Try to get a newer revision of your version of Android).
      3. Android 3.2 has a nasty bug, in that an application is unable to call the Storage APIs more than once in a session. gMTP 1.3.2+ works around this by caching the device storage information. (but not the file listing or related metadata).
      4. Samsung Galaxy and Google Nexus devices have connectivity issues, that I'm hoping can be resolved via patches to libmtp. Sorry, there is nothing I can do about that situation. Complain to Samsung and Google, and try to get them actively involved in libmtp.

    Q. What is the alternate access method?
    A. There are two main methods of accessing MTP based devices, either cached or uncached. gMTP originally only used the cached method as this worked well for the majority of devices. (All device information including file/track information was cached in the application for performance reasons). With the introduction of Google's MTP stack in Android 3.x, it changed many things, in particular that it only worked well in uncached mode. (Android's MTP stack as far as I know is server implementation that shares the underlying resource with the device and the host PC, unlike the usual MP3 scenario, where the once the host device took connect it was given sole access to the media storage). Because of this shared nature, using cached information in the application is a "really bad idea", since you're relying on information that may change. So gMTP now has the ability to use the uncached mode as well, which should improve stability with Android 3.x and newer devices that use MTP. The downside to the uncached mode, is that every action now requires getting data from the device, which may be painful on slower or congested USB busses... (I would rather stability for users, and have them wait 0.5secs over an unstable and poor experience using the application). (Alternate access method = uncached mode).

    Q. What does the "g" part in gMTP stand for? It isn't to denote that you're a GNOME based application, is it? (If it does, that's lame).
    A. While many GNOME applications have a leading G, and likewise many KDE applications have a K, the "g" actually stands for "graphical", as in: "graphical MTP", since its a graphical interface for MTP based devices... Sorry to disappoint people.

    Screen Shots

    Default interface gMTP Device Properties gMTP Preferences Album Art File Upload to Device Playlist Editor

    If you don't see the slideshow, please click refresh in your browser.

    d when the file is uploaded, so this informatigMTP/web/Help1.png000064401651440000012000000266641157712272000146060ustar00darranstaff00003030200010PNG  IHDRIFH_ pHYs  tIME8~' IDATxy|E$$#!" V ȥ˲"[AE屲Q<`YQqup A# `A8d G^ztUug#ĵ*Ç755D'T4M766vӉR*`0t6[Ah4SNxvЧO!g|0Y~aUV-]4!!!g)!듓{T* p86W^IIIYg?Cn7B7_BCV?,vdXBqj]|EbccShɒ%FyW/^hxy u|K,Gb1AJxm'>VJ B@D0uq<Tkb))GIHeYQM`ϡ-R ;ə{Q![GWk4S&YsC!$_N(Ϣj\C2PA$IQ"X"KR h}\.Z !(fEEE=/rNNT* 49aÆbeAd2R_YYyZnE6lX>} j/\BG)ʂZq_9O n>۴]@D"EJ6Mo ,7QAH3Xj;T%)4pTk; DYVqmeO3!OM wQG2hɲYa0Kveee) VgJhn$EQ"Aeee!Nx5Z,O]bY9=]v{ B\eew}g4?%K$&&P2 b2dr: lDү_/'B0 wq`޽޽{ EUy˲.+>>~ڴik!DQ0²lXi; |`aC -l@ '߯#Dp@k;|gn42v1bH2=&Yzl!r0LCDTr, !hDKjBu~amm E@G!@LLLfffi;:푤ЄIA!6X Pő@,M3nrK)9KCB!qwŋ###=ʲ匎8Դ#PlXBH!h6v֭[u:Jڷo_iiԩSRSS+**4ZWmsүs3L.7L,qE 1$IT' }{< "0gp*azt;~H)zR*1c4!PRD!C~J؝:u[h;^Ɔ_FQzCL*0߾'EsT5j svĈA:u*^xH@R6J%RDA%fE)EMTCc刋=.QJF1V><33STcUYYY$IFcD"tL&Fvi...NHH;w.q?A._NPZ^L>?;wnKKK(ɲ,k4-`h޾$Iz%%%M<E1 CQT.òi[Ew/p6>UuH%hnn裏B#GlhhDGhnnDI0L#G:ѣG{ⲲPLD"5{'œWQ?Uf͞bfddl޼_ e޽OSj;H 9@J  `}Ndee] EYe ---ɡY(#H$gΜqqqqqR'G[[d2111NA.Wx&I2??t8qB#% A#L ?hhݻ7b4><X՚1,ˆ Ka j16 ^Λ*ڴZMÐXh4m۶ >\>|/|7osssyyɓ[[[nJ%J~\xҥKCL;}N2k>**jĈ~{#F5?h*.hh 6cNNb2-K-s4#ehHJJ Q8M*vhZz}II M3qq}5Mh_UUUEEEi4X#y.[oMzzzmm-߅t:rA9~@`!D"Hkk+ PTTԄ <d2yjeCzM|0DEr }Wo?YBaX[;111>zӧ/'i'zφh5I m B)u\Ia0ׯ̙3)))> E~4RĊ) $1"i|jvuYBBoCEBaBBoP\\l^Z-Jb='Cs-,˄V]DDıc - 0{_0 >HXN~>wA{1Cβ,+,XpeVqT*իWaa!a$Lp8fX  8$[k\l'If7^ۙǏ>DR)VB8wܹszZq#g]wk Š;jؑU?Bu4m5U;8u2:u_"cbBzBM6\SSs}$©R\nXBDQ"(Nll xnmmF`[[[5MnnAl2i$ h8@A@a"EͽKP(:/ 87'9rRcb?4]+(a I0޵BPlomP(4MODş5 Z ԯ́n@ҽblCO%I;O>/Κ5+\*6&V'Wʥ2)IQqQ)!CiC: +HB J:266^p8h[ ԬVRiSSL&zdX]o ϲ!R:pgG;< *Rti4hw}7mڴ3Ӧak3#Rf׊ 222wp|˜|zY|aZMK.dee?~?8q~p9r玝 PpY@(Hƈ$.H@H@L8pD;uVuLʷf!Nן>]k6rYrrfCCTT|_!J ;H$ZuuuVرc Wo콾sϦM<#!D׷m/ꆱWFWYr(Ѷܳ+u#(`[Z$S b9<ڀ2a}}j]zF.x\,%"n]+Npk1d7=?f~Oڬ~{f2$t75!Æ vرs8Jr6t8j"iahrJVB;լP1w`BB  D" ~;a~A\Tk׮ &4D"^߇,"H(ۚoߟ|pIjr4ϷߎnmϞ=nl6KR/_v7î"HVBȜ9so ǏϺ~AdYHn8[o ]a?_5NX~Nȿ[TОҒ}&&&1,K42Dh9Cˀf_wb̘1/t\!VPPq5..fk))IfpT|/RP$EEEݻ7&&&!!{'+"(+++rn TUU,B߁_iz„ b ?^!"A@ D dy{ +!p,8]$I\^VV9a"##NSPx<?k\ؠ}6 xywy#~0H`LL|phs ?$rRdJIIIHHؿ g4Q12"$, naFDGN?Hd2p\=T  ?zhݎ:uԹsΟ?OQTSSF:U˲Z qa~;Xj}jj`Y[3hic&i/$I$TUXh#@8 dϷKNNrf{DFFF\s΂X? 8a{N8`0=+Vܜij%%%W(4Mh4fd6".K"8ʮ YNTfdd]RtÆ %%%}G)Sdž7$&&ݻ#qK$Ç'&&PmjjDZT  <| WW7nLH J333mvRQXX/nF$EaÆF~੦(Jф>7ߡ!,wIxَ]O>3-mw(?p`Λn  &IIIIrKG>EaH/ʍuCϷ`00 Dt|; `0LgA,`0 gz33//^-{MNTxش㏇ ﳧ\sEg K.ڵ+77wԩSgŒHZ]QQ " -7R 3gL:ualYPWW7'x"x .5*<~DM;?|͚5׬Yjժ?`fҥiiiyyyǏ\hQFFFffU~iNguZe'|2!!KkL&{Ϝ94cǎ=x СCi<biӼVC#< ګt'>IN{-Ѳe҆ r ,HAsuU^5-Aտ}$wފ+ $HbŊui9w߽}ɡ GIDATC!K,'Nؼy3hѢsyK,~PQQnݺ^x!Hŋsν>l`0bۧN:eʔJX%kٲe?w!rwh&>& nKE-Kqqq\\͛m6[RRRbj|USS֭[_|h 7F7os=jz6l(**:tɓ'W^ye˖.cÆ ءc3fرsΙ3gwMO~x7jxuV۵^߾wEjI Æ pMDZZN9{,yhZ~y*((j'N,,,TTjax@rrrMMBp8999>WSSSJ)))JKKW\_&MhѢ1c~h'أR$Il{?)S<3?ʕ+_˗",x1x^{ĉ---SL}幽)|۷L'~~]]OwyGPe $~gk:spv%r|U3|`ubϵ^E|cw&⤤ӧO Or233;zN[,ܹsĈ>۶m۱cǮ]ݻwBƺ,˶wYBkkk#KLp|3FӴ{2E""tO*U^_ZZrƎk׮;vo(<0o޼˗ ׹!opX,8IqMju:ƍ322xaO]C-Zt ZkYAҞoe;1 #vGךc?QZ6I"G{СC'?~ڵ/o=~K={;槞z 0| 64n:uxUƌ{ﵵ577/X$$$TVVvxJGox<A}4MܹS0H4Sp#z,ϻ-˪^_vC=DDaa djfyذaΝb(A 7.\p\?ǎˇ۷r}'[)|)4 K/?1o= k… \_- Eŋ4M^ZOّ X>?ǮIK ;=m1_xqEE0/~:Ӻu??~|yymc;}t'O|r>7ػwovv)SMcWXQRR{+V\3[UUUz~x]^|ɓ'gff {n^/luedd|B=D@37ɂRt:?j4MwP/<}9r>H~_G碢">^xGrss!iƍ(ꮻ [Og]jUG*i}J什s>} yG2*`v$-/-o{'xǓ iӦuħii0 WUU ?&31]ʀ֯_k s `:y _b0瞻Bq`0L0 ``0 `m`0 `0 su|Y`0 Ǚt7n r Q% ΖLKd[ZZp`q!bp)i0 `0 ``0 `m`0 `0 N`0&RnTq5`0 \%0.o&L(((2e _²9wQtn}Zmrrb1/!J^^<rfK穧8pܹsׯ__UUrvԨ`0=ٳgϙ3g͚5EUTTۼ '~K~ Ow777wƍq ]***"##BNٲbŊ)S=:33ԩS_~eqqq?&+:sļ^}̭[ qF!E222233WZOt>,1n*JP9O?wZ/£tХ\cОgltFڷoW\GwūWx<>cӧO6mw]f&xmٲEק̙3Gv2-,]4---//N/]\..\t:-[J Ս7 nŋ{Ν{7}Y!>455ٳl'O|ꩧvܩP(zxOuRaĉÇ߾}{llw۝^__-/zjLw}wϞ=NGJB7.\:uRRR9QZZr/0iҤE3&x Zĉ U*vב-ʺtR{tՒ iVjammmTTTE ^sΈc'f3b^?16ׯ_ׯ_>>>~~l3[o=z߯sN3VGDD|}}ϟ?ׯ1^Ǐ^6">??s^| +4\3#ϟ?}8?e9ƈw[fsǧ>~)>x}|}}_~}>mf#Zbf||Ĝ3k܏?|珙g>z3]|~~,}EcZ믿1?ψs^a>cxן?l}Əg-nw5^s]ϵngcW{~|+5gkm3YO{-\k9vXd?>>~^*_0Z6~w?>>_ k^|_~}T~ZEyُ?ӭ]_z?~ϯϏ^c|~~_Zk|~~-fvk?\s{/|X׏?0xw9? 5^c/[mxs#l|~ֶZï_^zǏqŌ_:GOgZ3;|_]~njx׌LD|4~"bm\l_?t=n3#bxxz5^_99i f6sz?~;g^"_xϼ;#,?}Nkg]f{[_W[4Y+f;-,"zf3_ן737?sfnZYA?z~=냛qGc||.r_yX~G91b>\oFGd ,?fܯ>ksKw-9<x\N8g3îσ??c=]}O}f778 nXGAVKֿ7ذA/7]D3v/|\cYʛ[pLγ붟%7s'FyoYka{hDs/d>}Uq/jfyyRW/uyItW = ;l?sR!8q5EO!udz|{7CQ\RSV|^ Zyp J7е~XZMaϏ ;ab=gD z*Y }k__{)#w~9l͟0u[rJ%^CY0P[鼱-ap7_Q|>/Jͺ_mj?츸4Ξ_{0ڵOTv}WB*nEٽ Kx'Hwx?~rZf_(t 8^cׂ  sѷKemv?̲|;\eH= I É?ZK~>hu9ٟQ6a{^~1O%b\H8~E8Bzh#`!^k~lvWi:d,6qaqVu 3|=Ӊg9|5g D|tƧtvYjE ^|:e3.p=n{iw\~ߌ lS熻L-[~sEЬ+k#]6nXRf@kUQ` ȵ7"0yaes!.y@ӛ&4cHwG]Flwg0IJW1ү!O: sXN(Pm/ Nk;Ќx+[ݱBgo_(ise?X"voc:vsN YC~;1].Ž MݹekX vL6G6xdm;hPJ#FXu}qY['<lߑ@# gA7Գ0@->=PyP /=ll={vځwv6rsw0䉬絇ի㫈x7c-V ! ;&a";R9o7$2M}SnkE>ԂuP^l}C ';_!0?.`o h`n RZ9Ȃ ?r98b幌wus#;GR8f3z ƖuPOgd5N#]LOX;?F}N>Mqa9Z 9^TFc"{Qu˰K0ȫJgpAv8 >jO\tkN #"Y;G~ªGk$nrZM"YDE%Wҹ3ȵL3#jy$R2hjGeR7 괐Ocwc\iـ}^0y/$)a{8 T|Hu}9wrt9}h2.^;J,^!"64kwQϿ~NFC! 8c&aF2aWΊsW3ѴzcK(o~;LkRy,v?&vthqJ_Z l2l\X w7y8v2Z>o{m,cKQL2AXŬiD ;1D(XbM,7!=֩*܇r*0: a!Df0XQy)9HHP?W1gB'Bg6ߌ.iZ.oP +mXݸq؊d&96I#:xuZvUk^3_-]nP V~"&:̕i׎*Z/za P4fLsoG:aoyfA gnu9;K'k:˭wGr R$FǕyXqҊҹ[Vj ѸP{"ZUkSZ//,ҥ6}*yTCUk3 h 8"΄k[S@YK3 [^1@~Osy(wc5mc*$?ÕvJ-k`^"{N?RX p kL2`yr$ceX5!a^HAa [ƹ+룐e& -䰃:%,+򁻚#I0cw'ȧ[d~p z8C' jpy2 D\!aL$fY0~:^rZ{H&:4Jonι?wcy|EsjNT;nxKWYhȭ 833t'Mܖ鰷I==pAbHŒ=R#T'\#REJō.$S#;n"\/TgU#a"jZT{Ӊ:g=Dus0QV$(U($!׊ꂜE"FU ,Ym*9~7ʫ smB<ݮ|Pdmg- qO]N@dq?ob(Jtn9rR֛s5@B8Q_;9O.;"&=N祍C$7CJXJnHfd#7duz19\ -=$| N$u)k}펳6p]Oql@ +n#^ |wӤ:Y`-uWNueGݔ3ٵV$FX*Į1,VL v5ћֈTg`.ʊ Oa锛ԗqFU"9-eC:Q]\dÇ9a O%xR:uFV$[ZzcS/p=ulrj{pcW22~SiA=ϡ ƴ]y'"ᆯsh(_[E'&KAP&|XHJo&%51P4rwePS/0Bd;O }}3`cS3DByMʴB' !uY{( ClRy6|LIu`'ԬA"gK署 U"yg^[V%p+ԎT8B e.m6F X"|aQPC!%Ѻې 7Q?;='G "ǚvhgd% R܏%QUa/VXBmO+> 8({2nį'|*dr]dmR|N 2 D5K|Uh0 jȭ2mr͋uѮC9d|ZRB#[1JCgm|:h)ib3{X7V4=>{og(\_[=MxO^O"dz=lSh8t3rP(l+I0FK @TC(Bp9 PJtW^ L_1ICMuyՎ_nn_E.*W'+Z)"HC\ 5ƎЬC h}{\?MH B))iG_wa͉6s.H"Zǁ6!RKnI"NyE"2reWZSilC#km{kyo47:G/NЏ^)Cm([@~îz GXH}xY??-|xr[9xf[`3Mj݊E iGOAqrUXAO#Ԇ2ӦXkh&'m2;-SOVN2@7Ŀ _$bz-9z62c(*M D][v(m0C<Au4IU#恋ù R6`ZShv %R*§;Eu묐ͅ8S!)R~h.*ݞ7AcO,6hܝ՚0S#A9;W !.$HI\]ЂcHyxVfM5u7>]or|o/w% *]qѶ׼3;}%ޛaŘ#E䎕7Q*p/I-Tdd"cۼYln6Z AvAOz?7ʷz_>]dTv_KD YH]L4(LISz@mTnBW| i܄6:URbwOytZ]"گ>oGvn8as!pR*)rcž+Z䏸y$آ2*v5.LCM2 1-atfp &@95+ܖ3ؐZ&. A׮N|NʗV(?" Á]Hq gǐBDAН`6PU)t>2%{dUX1~!}k.3ɬ'HE偓W-:DoDqCˇ9$XX*R#[fCBm)bvdh^* a"!v\0@|ks5Y Feg w+\_Uz/QgJZD뿸T(1vE±Ś@$Io8`bsרc 3;iH 9̾-\E}0WDr}yCn5M[f4UӉf{Cca hǕm1Gt aU!ىݺ22$Z52l.EQ'mm!?J:δRk,YS{OQHsiU=LȖQ--=2١'cfKV<> :1dYiƗm,]xȻjNzdJ"[:5SY9kWЂusFiE4$^`duoDlSh: #;,}3 jkd? 8QN hqq+9ՒY+7PcQhF1,.C.,ki՚m1ڨGV1k__O8_ҌON?U/4%IѬs~r}Ew 1@9yz!*|F?a5位;pq#R=vrfWmWXwj|ls(@Nʄg5gҖмjհ;3fcP?V/F3H >@0J*IQ Uɳ]:"ZE·=oVw q JͤɅl$lrkNF=8W!E}q&xBI`5DLŒ:ٙ74xcoFTwLb=*{j>Ba5wr aC IDATW~[i'9J=1~p׌) NI#l^+:+h2},Gf3 Wqay-UK핓sDG{dv`Shkƴ|ęLϧ2:wh  QYٝ*v/Y=ƓUD r_%馚-JԧqQ57V+/`歸OӅc(E`c<$?ɓ\&j"Fޱ9GɞpQrNs3`RaEL>?j UDᣈ^vlv8lΆ$O5cad~'0zR|'e/g-Μ'dT/&/o8xKmzK%ЩS(i4(Xjbވ\W?T5$жl4ާ,0z"'=Jr E[]&ZBb@4G('#`/~XsyGY:CD]\knyYR#e Yډ3g }S %)rBSOBU$^ca=V,8O,Dˑkq0c{^A7NCUH| ˥+ao~# uugp:79VaVTBiTmr4񒽀H(SG;uWZξx3ǶU&3gJ&r&=5A)iguxWiMjFc!qMf״~=^wG:@ao.w/NBEn wWْԚF-m%QYC֮?}`{nG ]6<-I?$m4eX FSCQe4 F7~< .%mu|ccI6c =[o\$1mmwmQ\K+06Q&P6hC *4=f܃8lWꅘ%< CL@ 멼$`#+*XF6XHBI oЦv^퉭.1e[l%Q1A̜܅wRioRH Z-vi:Ѐ2ACfQrÖ*G}-M+RJ&:?l Bvhk^mr|>(hN*(<]| E.;#N_-P^߆h'J\ed<.h'ǚ!剠;Q.B5z/'=c1څKye )&z19~wnf`ŹR " PtdZKJ!j1hc GR `3"ƀ9 %@T`V)ս`Tg+f/ȖIH[U8ͼDoT5jL(2EZW]%.mz{Sgt}h8P5?7i2!ryFoss56Г`:&Y7A 7*06E7j`pE"ԦBʺT@Fp–CFbJm3;S8Bn1 I\\0܎#ߥ'6\-qF2-cF%ة@Yu!Db t (Br9B=a2+MxǐDV'5$ƹXybI0HND-ZПrñ~;QM2!KͦY@ޜ =Ee3٠h+$$XZ'u"wFb#^[C77+Gt}<:\ :d)@ 5I>&Awp4q'UhHGQmS t|qM L-괲,;|l( t1Iti6 ؿiYԌ]r~>TX W55hY}{_o6(Amz[-|-&ZZ}C"['IUmĭpYe@,o[FHG6Fz ý~?~([;fpй9mfHJp'S%61^MVa|4r_ڇZU@{@ &PJو)^)yVP8b4F *_T4V¢{\> QLo!SF=VZ͌Eij˧F (4*E9}]c-_ T D+"?:;7<ܮ{~9b c!&FX923&qMsYQTv~35@?xP3:&ۢuOѤNncFN~)Փ~9)`]y 9u>^e}'  P9Cr^s"P";lK<̇iSI{/W0TH5F8oJtGzbhLVs?a}H<%)s}uO6X+j- N}֜ьC@EhjK_jƽusBs-jG#5{f?YӑQuH|OYh2Ѹe%IklIBM<`B1dS:/e.o5 qKV..j{~Шѵ5D 1~b[]M)^0D'ULU}\g"WY;4D_0֐J-G[KshRn(  7t6ABY~o+]]̟NGtʮ2L-T{Ͷ {MU*t9H:'^0O[iZ4n A2儗whZȽ xo9|}3\E&McM6hp1NL1*:jF!R Irg;,HnQΥ`zl3adE(ĠZ)S<zz;PǓ[<YEbbi}\܊-HjUO"D1u[irכWT.F|Ӥ 5J?9`X2]\vXDꚅlVs1Ԃ9I8AQ*^yF2V{? I>zlZ|O&_npCߺ4*jjFlhdTcpxlćHS` ') |$ydJ-0;z$"? )ֲ<*ύmtѸ~gd95g4)H&*(ߩhbbҍYVᤆXnq$٫wmV2Y\sD\~c@+&=AoWJiäj8:+7⢣j{pyVY!:|(LK|jŪB+j צ 0@C艬P|A"jɖcjFߜWjZDׄfbWeK'd!UWqeTZ2B߼E$l2;ksZZZ< 4;bZ#]TxD{݆fM˪saGSJs0Mzι?_ -zx'aPU8Zȷ"-m=d Iz[}jW/`5*( Sns!$/"O'F;F,ԑiۓaDW7SXX<5"Ours0#D\l32ݰ cs4t蘞wL`wRܥvH ^]NUܙ ӊ̳.xM`K=$۲5fq ).}نˤ]'6lG¢U,Ukvv՞ʪtZp.Bs_. >ͱ7'9/l{m*mAf9da0wD@\fKWO_+*Cĸ+WO!>K7)@BN\s]E>P]Ǚ}ӓXTV<ֽ Pl/2Um[UNqV,!] Wd^K)~lݻQ9I" T O¥UTAڗ0-1v3_jg^Rt9N!qՂ MJڞ“*z&m۠A1${. fU0P(BBZ]:]aSxkw*Cwa=Z`/V IO#Af=. 4dق*"}9)xv 8tIhfsF?0a̫;N!pN頯T!CYc=ΝjΕbNEI8L"''/~vjx4]1b1ʀEfYzxj=062ۭd^̢~'xiJ"* cxfAF&[I{]ӣ xgwح8n^bڹ>U9$Nr{:B(l#B_s&*ĉK1oέȢ:D\O}q&Yj@8ANmSin!0sFKt8H? XZBٝ4$չMT|&{< r/sNlZR*, ~S^Z˨{`tCg)̓gIMuY+D$a:Ĝs`^Lxi4}bsq-',]3} @T6q{tkT|w"ѺPR&e ,IJGw)Džd^{#(b}pzȔa1OamNo%:P0>nzu; $U샱3na)ZlG/t.W@ȸ4yqVY-|M0`r .gl$"DA$I4hdT;n-s(ו9|\."tVu޾|Nd^pi  JPo+?D~Oq8U%mQ2al+> Bӝ #΂$7, WEnxpqcN^\aX,>6WDvU isq;fO쫽v=Sa}D;۸﬑k}C~Agxw9*^b(J`BIFb~0=ǽU -bN+T1Y=y]wCd7sx9Oȳ[cq Kq. 2{wD"_iE\_x"#g{_l I.9) כ bgZ)2toG5x(dݺ5э`!;qr5w`㣺TBŐځDBCm,'hJW!v nق0&IBcɥ ;'?&5"(5ƀIcHiV@|֥‚nNc?8YDZEab+m48aaQZqHX LtSkY?ƒKlt wKdMmFr}Jn#Nt!/nG[wpJ9RE@I!Hh݂r`CvP&\'鲝ݛ:91 N|ZHG@R),Bkn.D4P[|Sj:|NiNG5a!!GkhG DEfٷ.er^R!v6SczPfoVLI@!0I݃uw-@t6D5a"[p}CJ$ZqD0=ޅ\^|@b,8ބYEP0q\mU@إ]{ΈWC)M\:NSWj[2(?=yV;w*2w8` &VB~hPAomO4̙NNADO6(i?xaA|_/ cP8eDGQy#EqɎrl)Hy. :j$,8fD1c8mEoi0<Na%".6swT[KUc.V=+gV5 j7cد1Rԝ.RJAE ^>++K$nj1?)?2 2qGFFXǘՑ~}餲,``8R`tӹQl)lj^* HC3':@YZ+sfBt܎k:tGZ]Mޚ5mq:Xl5iSRT 2Re7n%E=: "TVi2 G/aqJ69:f(0+ƵJQR4x!AJ I:9ف3e'cMf*jn5)Y|XmJ"wuOGi&5,'h=n\'ҩ`_ 'FZCJ GE\rʃ*Sc1S~4vq * [y<^ømR&]FS'+m,2rw7s&H󄍀|G۾K&#^e;f]볗Z[];ԓ,.]~׷Y͒ƃ-C[%0D:ƹViDsi[RI3-\HY %<7J=;ROeT擠+hJLhUm& Tkݜ-̭whDxny_jc^,ruN QBX3Mu@t*JYPy'ZhfkjA&b' )8XV}%Y_FC"zWIktXG\ !v>C !AB5-=3ޞ>ئidy&nVra7]`fkm4hP+]I֏ſ xNV!Hu5ک;'^s=ب!&C( qxeq0 nt伛ǡ+v~ q_﯋ܚ/ds*ٖ~_v`IK^޴IY4L' C&b{S=iUSA6$śyd=A&b r NOc: Y{N䬭zZv9{9fuCX` 5 ;hGИ!팃׉t2_֗lŠ-bn5$[Kt\Cafey2N\.(M25,$^<בY]E'.BtZ0_ }3/y,0 -aT,[vF{+rlnY_[ mc?к$co9na3 :Y6f=  }^0\0SoIbb&UwxVKơpU`%+nvA&S@pXE !U5iA>N&.19n)%t{JX0X-֚8xZ^9&1fqH央hZWnI(ä)-nY+j둒?DORφޚZo\ՂNGJڠBNoGNN?'n^Q#!^ 19<5 0Xh4Q-\ߎRg"7ŁC G,.$`N{f; 1 {StQ's9+k4p]ᘅC|s捅>0fBf\>O~Ni.SiHz !Xa1H!{nXWVvug:ãcxJ5iI/q_x9sLbp;8"Ey#HsYlF3 BU4p^}s84 EVIiM+l˩T9/eЙ.%mImM 03(Qz7MLȞnhZDLWɣLcaeVW3\ǂ,Wz+2[Ljʕ8`293VQ齻`탙hv1;NheSEȈZLm:_̎|skMI\&ͩIl,s聅d|1|~c=tPl ko] ACp F])!c@.c<^B)&bKA%#\B* ' 1:=p2ƿZF!kyrDQW|DQJ/838Db#|yvM ٷR5*- ٌAƻIt*Y$`d Cթ{QG> x9%.2Q]Tto'xך&II {eY;}ܷ`PvZb.i"bƒM)}mzU޼~=Y z7xw$#4Wcg 4.u,,\ H9 7H%ёy@NeֶZ-s.׹zbhN9lOp|Ge߯՚%4q' kL'~5a9G"kSh7m[Dt1w6EW`yn&\VUz6 ִ,AaVĩhqvuj؜0  1#GȎqq\0uʷ*B~Ծ^ D$. 8=EJ> r**h `sgOEPU'8B\CHc؋a-+vRm֔;'u)i9M' La |"#Qqt_#'GfmPns>yG& ]^;!D?R׎M .X2jPr4/X]Q@=UVʹd9|&^X\#Q3Aȓ! <:^Sf5YP.;4;%*a"ֹ2C˖kܚ-%YU>=lfD{`sn1ok}]eyKv浙(ycbZ󨓡1TؠĂ)/_PBP +qIGƄuB@< ]g\v yяB[ Ҁdv@isMp9gAF@ zmEzB~¢e|\utkҀ\lڱ(ѡBH SYIR * t£p¿y4۔ړE$lH,( y:n [fJeWh}by69\8-QY$6(`q#W ӵ8Q$O:s.7Xk8.7xR}ĸk= ڬg/DųBG _N9{ ֧4k.+GL[=T_f1xjA֯|za*ɈՂCSU*u(i3m1 3tc1dHSYo|g['u< Xx~&{?:$b$4" .7 q'+Y u6Rʁ$> 2SEDt6?BKl+l,M{k}D~OynQVҳ6y j/ {)VMSO \c+'1S+ϚX-8:>EѨ.,^oFv5G ؕ2I]-;VVh[5CyΉ݉{dָWaD~M} IDATݶz8=3rv7 @8OS2AlXwj PZn9Ogg |JW=Lʦ)SC}?K_JjoK9ؕ&O{x%[ƾa {e UDݰ $o<ʱ'Cmz*OY|!CDy$ŬS`,ԓ7^(ZP`]3ݻޤ:zWgo#ߘiqza9p( :aVnj?`儇;P3h=8KxBZsޘ Mq9wCP 3ѥn*.]8ժD> Ԇ]TK9hI.¸OBWǣD.7(eeS[T/ w>t8NmHyiSXl +ĿMhm@q=^w;ȣu氿x5KZ]ebc$dLӔKy4WraOB<023'l[/%c]baNL8*CvxBBͨy+se"u N==aYW*bkY9Mh"q\ C~RteF0v۪KZ w{{=՗]olӟD(d:4n[m$~ gn\f̡M,PGk c_IDOJ ~RșՁi)4#~ghop8,Y*@eXMGV, 菲xw: b@F؞B;0nlW"7&ֲ̼Mm%VȘр Y[KS-$*&!GIӝ6O>7;SqU3E=mD)ij0X \̪G'˱xm'5;okX82蹑, zh4aEgZr{Z"N,Vc“IKMJf)P)VLku8aKk]m{M4:thBp,ЫD|QZfdP([dZ#C2#PzV3FFm[&@N"tUF*sPBHe]g3 2cWx&ƣ'58GrPz&{GblBGʞ+2#Ug 鐎zm:JJmg gEe+X}lo25^0t$KYiJWIWW5R({ ϊaU1nmNsm+r{@O\F<#}PdӉ@&bLxMa Wzө_`Ƥ98{KXU2 G3mus(o{pY5Hت뿇U A9B5 0ad2$N$ejZ[0~"thȌD}/khQs.9OҝdTdq P# |V%Wg^B!0DφHK`q}&FCe+69Q2Z7C+-SA1aͫيv#󭷄z '^J N$RE5G\"`Gu0jLgS# hb擡z99cM9@KvMϭ 1XKq:^-S?$Xg>Qmk`:bmO>Fyjuu|4[θ)b0$^mj\v_Tx;#,ÖSOJL ~5<)PwC _U$$sh^=.ER$`mgz8k@/$ջ~҅1D]w&)r䡀}güAg)]oN^i*OR%nG擙\$ir7ϿQU=(׼Cᖷ{QȍcZ,|H2xdvF kv*(1f+Ě xkh8Z|V B X1ΘV/+n? T Im9&{ )CC xZ+fnǴlXkUc5iC>0^HJYFU/vH^hP"P:arC-i8p>;Kk nH_T*nX]"V&7Šp%T4R%h޾J Vt52C.s!MI :Q!_aʹ7_@/T:BQzl)&2bڤȲ)PA"H5^,BLfZXU} %\C.L*%Oh8iuGzD湩9y =C M ^4-Xe.&&xu 4B5 wgyhرOȷ'`w]=D5gzju}IԾx̵]q{ d 2DV"CFZ//̊56PWs@bu.-YIȩDDk9󞷒`S'F9)X1kվ @1LYluL+.*RP\@AD#{$ vZԀY MO6p&ݓtO⌠YՊRuqqM2;8F:(cf.&>lu kv̩y[Pwﵖ#H{ԯn@ 4< ͓f!9,ځ6T/fcM#_TIӂ9(LD3م o;!s$װ6=Mdt|<(Ã/~\Z  $2|hrk|G!=e5P7R̤1' l>\놪[t|ޟj%ڙ:s mt@ Y}c,oƴw\39oPz;AI+9ii\YOwaA\X,+ZGKk,To j!v-#!v{n⢋?"["4@bZ fPS0>L=ٿpJ266@2l1lNM VcʑDsg4*MU"!_~d" sEP$Fq:9ЃNyUہb%IC"V<5|` SsztnUtM影xcÙ@U ns[Pr=:*tĪ!< H}R2aDK蒳ּM5q"(: Dun)n`񮇾!iVs0$OVu&.06HQ#tB>AU'qD,,Cą]y'YY3IQ%88U#- , %>ҎwMu`gE-&ŶBSiޖAƇ'P cYd>Rk2NH0LDG}Ț{Q(Bt9ƿ89Yp@];iȗ]<:7 x7u=s݁i|Ơ($0o 3 [Mtߣxi(9x&fQrڿE4u،ιRV{otmDq1'|Av-]|e(,AHfbh~׭F̒mF4z˩qpW$K7AjiaXxM e\ICLËy/!d Vf?HtlI[5Y_p1eHz̡s缷]{ ~ӫݦ?UeZ4){>UvGRpQn^'O%zD~eU6+iǴla^6൚6LWBCגuD?*>C0eDP/TF($2o4f=D1!!T\< OC:čI悛˥ b*+\ q5{4xq>1;Jƒ7M01p7%) uSR̖ qyح$SdU D,%g =j{Vl/Tw K3_#E) gڀU{ѼRg8'Zh'2 Hxo۹n|k0A9ں6κ8#15(|ZإC}bA& N^62^,J{6`'#F9oIFRXk*f>tp; 'ZCu] !R*y3ٖڤObⴝxPo. ">m俌IVx4i^x\exKӨZ ;X>6nyL\Ov];(٦`S9a%}ںְ;m߶XiL՜3&Θ֒c&XH *Im F)9f iF-F+TgKF`)gwpm_*ʘw p{H%%[վ"U~ſ'0r% upT|mS?N3DŽ|]Kޕ\:{$ݓkUdZ%dtuc%jqyC;ls-N3W +pC@Az,C&8d#;%X mV$2//>,]-trVC~߸l[ ڕu^( 0265yTaS/ouT}w-!h 2xc0et!GHʞ‡]"Ep}p{E1^t.Ew5I@Z w\'pԿ~e@LcVahL\q9Oz:6nO 1XM6ɜMOJC*VYmWcdݐF4,vVOت$-L6uS[W@ &rTE& tۨ/~1?2cJr'k\0ܔ\N-_OrSkQXtpht@y7Yj Anv~8E1QO?HFX]W~m, nJ4xP:Y?s"wtuצݭV-·"L1+]S IytM W<|B 0غ. R-NZeFB_&[>?4/jPȍFt0wԩě0:WE}sq.Nªk[u?yy@9S'{Y QwGUk;pۆ.W؅@J,HT5(Qx#C*aɜhFO1Mݧq=Hۈm4~/`75Izx^3>&2~!n S0 D`EC~ un>8r>Fg׬we|/p+ؚ %ӛڤkޠ4[T̅PxɽѼ>:AF5qWId]W^vm ~>`ʿT)YTTUZPvu|LC,d|#vק #޲ ?9D= y~T CފH3-UH AW(NYˠZE#q?GΙIGHO:kjA5SU:[e7{U10#(t8U%Xw"i˂T4Rj~ 2^3`(k }0ӆ^։.b 7ÌUG~v콓 1==u4]:բx(`Xlщ[ w^=;4e܀DnAZ;;+j\Gvh 6߲n㚍Q<,;•J< C쎎#(FNAguZ Չv00|n!Pv=&0}.r08p!xI911 9!! u{-HAD3kpʡC!!&ېRPJdsC?NQTނ4$6s9ni2ﱷmGlja]`bĽiu&y6]@pG?MkAF~[PNh1oܳT7mta5pHӻe\rzģ`v<ѠjBFQ0Ot*9 ߽RxiQC[^&F:'7;8ܐMSܤP5 P-)c 'eK͑ 3:OZftO \K?b 7ڻSgJ U4{ZmVS!~M}$PvDRUǛ6 YL?3\ y2?VIni2,@3J\k$YHahH*0An2g =C~^냑Z 8+b ;^mt;ғ}97G^m=+GWsOm ^O4?&ڧ7_c~=~X|՞n. r֩yDզ:NK5׉`tt9I,lugEj}O d7ہ55yfKՑuQudo  klOgoA)aϲFz8QGoQZ}#(s;!kϟI17&4QƬֶ}4EeVΞD(by%PtiN 3\"d7.0[xC;Py;Y*N9ҫ0)E%@blWn2S){Jۿ&A1w!4+unX IDATnǔk˯5n(Q.[IcJj  㮰 ԎXT_U- ~nRqhGL)9W5jg1l:K&8@Nzn>=]  :AnkV^ïVp.UJs**ޢv rZ7@5Œ[3w :* -$HMM؋WL3$J?a 9 3/-.eJ"{R>>C芤奺 $tAa:NL(pHfI55]j'Aչg ASHlkdU2^'LVuNZx/qHGPLbLXp38z<k_yyDڪՙ{VtNT%?g" W/)vW 군IXaR*p)f+PX&qA^ՃRЎ;6؆av<9hu٤t BEAY>1P sٱMvj9R(Mo&FfDN{WENM5S[Mr`GOZ'ZBu%BI>/Г3A/_4dQ2sWɻ;WY6&~T2E)0~qȕu${Հli/ ǛX _Nxz[ĢwۃZUa .e:8ic7HDVȒ;eg3sPMؽ3͜Ld )e=zN:43jZFR^{Z@a%f_}_i(aUvq{R営2K twZzMO yֻ~fgN"=ܷOjrNO;m}m]bM UJk$nPP)~ %zUߪEުi!!Ao!ȌVQ|a ̓ZX"0,ư#rUu=[حNj&%d@dNSl'Gu҃/$(1DAGqBI-K}A e}U{N5OĭO:ԲdJOFsWO)2jUg6^ƚL"}] `ApjdX <힞u#٩uhNe .K|xj5Z.t\n_Urafl'/6~r \y'TyL2Qc٘8`]?V=?yр靏 2PLr'z6II0Y:[dYUKв&(59?KQ v.ObE?nI{ӀmxJ WrdkmS)saLb27@&cx& 9TL?l"Ǻ]♦+S!H[*eLlo۶UCAPnggu-7ڢv-;&WfZ# ʧP&{5srp 2&WsҎ kǷ~, ud+u~arj/` N2U'j<,gBE"p+͊!-ň ilJ'p۰z0`{̅u^e>]lN{*e )tHW+e|xfhG$gsk𷀄͑3fH z@!ʌ{:{Mt" $0 RُT X2=+6!SS}PA݇NwCk_KF*έ%޻ԼqY% Yb[EEjiЗk%2ډ'~Rs=L+15ei[*U÷xONw" +xpz! w2tC&Y`v>,=.t{=2V+DeF=ن>No=m0dƃjH/ !$Im40aSjOЙ<ް@:MMإ *3FMK; K?\#-aE0]y hwK6Nd#ڬdOSrٽ읡%'eqJl{>۠-Yf ZHLdhd&#c׸1nʖFI,wkYDZ2AC4̷: 1j '#%}{ގ7nivZsU=yeDNJZw``-]Znc]?g.\=b>"m(J. cudtSD=Ng>L~R:DjL;|!۵weX6Yc7P-_nXؠAV)ڛR-&;JYt+aB2"jr Qzιs``ru "^ifCDi4G #77;)~sΑhns~]ꨦs9 6Ya}SC_F^^8>}rDyi86Zg-*a^cqH V?<,eR4qD#y"䎠:*k)rBQdxE9#wݖLפ)We2XP~L}sI\pwhnY-8ڍx]W&Qt%,$@$X(hhP~Z.6K= lia|W<˄WFTjY77'xܓڄmWi-gYGi"J9pJx~וa6+m4Eg>x'DKԦjn!4SVF6 jlDC[.N'n潉̐Qt%2;Aj^{}uc=;Z- [.&$)QeD۹}꒵5it{¦ۣP,q.9 lF9ZN4O"{]ɜtϕSA:A;jI |jݞMmLCVQ- = X* hrjnffl[1ٟG5OFϟQ. PS~x&]¨''ԟ)Z lԙSuT" c4 rK$mje>XjG'O:6@,Yr{{G8*1Q9"5 9?=ReC!< /뚁Hs~+۾aϸvv$mGU; |ueSVQG8m'Q20=mve%ʡlL{IVIV߻ٞY4h};yU§ *D&."{NGvJB0\k`J54+}BkWَUzt,n;FLG$9hI#w? M=ƴ­DB{]Vw=V8p yB7G@~ * n>U/tؽw] m/BXHD]-cHY+}A < Ч=r&+VV!$F\[4$ҤdyWD2,R鿊U(kr\f #.k1 VO/|@btm[&/Smcb}XFށkûHK:{kAwV`I5R3Gv8B®]?tǝ-/ $Lzi2ӄ+ʪ-|tk ţUORa.('7ۘrcm+qDV-/;s?_ Ӡ]ad;e;UX=W|YEm&>'iMv<۪ gܩ=\> ktK:GYˮH@.UnG9oV2>ymsJ۔p;J{ٖwDzZw0O;o]w`5ƍdNhM)7N?߿_ܽ0ŏXuć4tto\"xռCu"ΊQ#o?XxhF C-`h/F+= nTx ݰ< c'8H\$HEQ8Us߻Ɂ^̬Z ,giin^%V"uS,4K n29F+a;+W{]~/Xכq}i2dCGTh.vk4Q ؐ0RNI㝆eTM#էW [gׅ0SF~ ӻ,n`!9L+]8Ac$0&mnэ;zG$܎d<>CBE|/$.KLNSj_r%:_1#摙y#_`y,KWPBEZԊ8*6A˅'W D@kjnW@\voH?cV")#~8GN5$D(k#KĐobnWLulVo飇$ຖ tl\yw'7nƭU<\[Uu!*bx ] 94g{yM05~~N(doZu[/ɵӴ,$q,6|[Wݥ4fP0&\?a6Ptmyef板Gi(;kHn>tQԐ̷oGtt<F ̇$Pt<(DD jcwcaR<[?pm4Y'+>;6(=SQ .{`~k ]ܒw?0b@3,*rJ4xt+P*K]/i q^'ߘ1g:2 ?x$-%:b]:bl:hHoMѦ=KO,%: jޞGvm<n?k s PـhgoW/=z>jl(ccB)m[`16>. Ѭ$W޲N'tmk H.7c_[6id7YCPj7Gl/Ix;ټtD],)yUdsR& ~ w ACCg ɦRQ`2+i")ґFPIdw5mqÝ<)-NLaԋLVɈ8ۍc>t`9A& ײ]3W>ح@PlͥmxGN/|-y$ J;w p1#Ɗ˼P{49ds/PPڲVX5"-z>y袷jvURh࿴3 tDEҙyaүj ֎BUigFeqWԇ꛻~{fB@fQ0o?+V+򱽯abgcpMX,D9Ƒȝhc=1~rYKe3ـ%cb6h!8 5)!L6{bNgf/nHǞQJt*&,{ mReZDhEy`Z '+EQTt WJK#`aҥLY½3zB零7|*0܌YHo&x=&\IE'X#WdZi(p$OpQB"2ZiǔdC%d= y.@O9"fФivI H'/< #Ĝ[!쌏ir[Z A,Y]ۨQ?/]KQ)M6.kd^ٙq2FȚa(K.rTD]D1ؘ9-Ζmo*A 1u yFP6>灅wk:Bm.}[_ 0vm} 2G,ꛯsu/ެōmܶNޖk@2܂'MRf%+Bn r+[ڤ$v!TІUj ccvi&jV%!?jJ`L: .@2w w=Wd!24n}-Q:*q\).JAtQbLv[? u4)(jfY^2W^JHO\=PXwW$) sQN9'@DьFn?kKwB¾τًt2sB > |8z 'p {{MTx'nu-6%pw5%}0!j7ǨrW6i*5,h(ٜĠ&ZR6V9nd', T@ūxT+D]B&cS$$ /(ѤMM[FbϾn|ۆntƲTLt37µ!0z$6VC=JRkf@JnhPZQ YKgHyڔ\]mu XVUnJ>gz( \d(*znz`M- Xs=Le%cs+'^1CR] jC}faJ g pRo~Tf\N^&GDw3bй4J$#9ZzCr}?̽OL Gt撮`QŦ=~2cq0cT[B@ ȣȞX'8I%jM4Sf_zAozu __yr@f B_EJ{:B? _K$COr\ u4ǜEv'?I:+5JkB9\jMѷeE6 {qǾh oƹ IDATct]W(̨35`-k <yg(H?}GMʤ "JbYh6'2Lw&l㰃OX7 `H_ lS[ JpYj="2v $:l5![ћj5rX/-ɞbwU6k]tGX$#ΡCG5KᵳJmQcv`v"u.}q8tg߸5䵖/1& m0g^}W'z#KJCG34^«`Vl:A15th>ػg )8݇ (c,Ĩ Sp'<(Ye I2%ED_-4Oi/fE&nGvJma[ak,ptwttٙwrId wf/u!MV,]KCŻŒWk}.uAx{IgnKU+CƪNɖ wo:K!:5Ca?ui~, 9,U bcV;,WՌ36-0n%?"$ Y^ۦN⣌S3-"q9j2UZt5b2dZN4QugLZ+[ʟb j]8(# ~ [Y9Ĝ>nXsRN0jÄ(tmDۈ ``hvEP:Ÿ8_쨌>SPRVea:1m/4;fg7}W=/ {{Twu'16~Vڽeg2ڰ?m9 "2_؉=byzOg&^muR-: }ҤF4yr9m*Pd/ïa,\g]nWtvy M3Ίe"#wk~ژH֌H[ GZ_#i!Ϛjy=cpANو ^ }^v*H84h-}P-$vHF\qD[+xRda#oT2Xismq𚿚fEڍ/ ;ug#mpu=@^Lo5hU ,P/LD.*0j `BˀdD@|4o5aԅr)f HZ%PHO]zPP2GhO5"D[ĤT+ N6.MkJL6=J֛=PmX ug".>B\D:߿;Mhf(N]-~.苧vG4݃lhȜ 2:?@L]NW{g!ޞe*S G^ !{^#HH|z8}]م:7zU8Jȿ"#xo zB9y[R4]mSd-33pCڼt9y !t̼M/m&;ρ3P `D_*;arN\!~m.2f}ؾ#_5/ u I~5f+{#ڏg%#tDv4Z,4 xA~/ACM@w/#%қR7q*y6zrݻ#G]xn +0z\_,)KM8f=f.\M 5v kތa %"Ycxd|z@:YX\Qp_|ekƴ sJ Ë=I~:I)ee.8 흆| S*ӇOh!zW;.Kv]qol}hrrQ~!71%o@GhdK@*jYlz]!h!^xuJ?vw1eN̼0HٳwQ¤g؊vf""~`-ki%1~=k=kFcʧrJ<<+[bוU{]Z`!δ5Nyˎ3I :dl?r~fB#T!&1MݓfC]="|"oRX10vPR;nBL ƍ{I&PCb(IsYn)]:ТqJe/ 6h PDG4)3 ~^ݨ(zє-(0+MWnj\/T/挮a{tݳ1T0 ߝ^MO۪P))/S'Nn [peV8|Lꚺ[fTnt( 0Mҍ-YwquJ X(0`9ߌ 4B(qj7]O6v 9Z%1Q(۽~ݪ0dԛP҈ - VAnïO$7V[Qt 6&F8D`Y{8EoO^=_ءB=2"Aטj(od,(NGcY1ɴr*rhR믷MmG@ŃcnifK*Р7^48ԞKI 32`l$+~zPgvXZ|*o_q2w eΌ$,wװJp1SD t0!zAhĚZ1KP jctX;Do_sȥk|R=/89&ӫ*two'i}ƙH&SA#~$ __ YhtR )CzQ6!L^HBxhB$Cz( =yālzeU*ڙSw RE*Bgn&!n%`>FkJ#Vu D-m9:Pi|Z}xU S ua<**\qQh"Lz0BXdр>>@ Ó 0cvc>-6Ѳp$8)Zڞ3-rϹ3Nli@Z)ĕ9^pmHa3ΖZSy qzvc}(MIW=%ؚ4&=Q`Ts7k` M &{a"'-$}oׂM^-dw4Vߏh&IbIb`l4<={ b/lY/,&˩c>elTC%(Gz&&߹#I8hFF{x.;e$ {ӽ0[DIAٕPk-د>ϕkmwtkz8D/C\U_*AK؝!>rqC¾Q'FmMjC/tַ֠²i_JY8*{غJ>pəV:n*!<} ;aדv\82ΐxwb >m AXblNB{)ДO3x|%IP<)Tﴯ>[ ܑ_iPᵍ>D]!t;mB^l e z-hf[e6& ׹r>!|J{;Uuq5ӈDBUh ̺{w 6Wٽi88&ĉ'VPׅq$$/WbZdeNr&<([%Kw^)RX!Z,=9;г:QnvHQhl>$#B&du"/֚AprLM1dJU=dpCqna_F T\~3"Fk@!ռq.F `ӫ`yU05f%C1TP\Q+!1xⓇ8+&T j4ݽ^)&G;6y%JswERWu⡏H'DPAΛZG;(O9TМVڶjR )l)yc>H]u%ȣr=}am3@z-Dz5mLM=(@z*O@ j it]#J #@zu5; b(e7obj׆$mm=_a;x/ MP_}+bp릃ܮ]#OO] 2!$'bŭxς.:B"/Y=El؍vD.n +>i{}[/ );Yfn3DީA۩)y12_ǴE`k\aj L$˺:1 jMԱ&3=g3[Dp:NOD(#L%qy$ 1 K f}+\-pl}MX##[K_C;{ʂH%!f TFŭ&M.3o=W~j]];T-=p~&H5ٌxbO"6ٯtqdyƋs^2?l&CtLO2=oc)۪ClkMfS FJN&jBghرs5d- t,E~Rkmذ@Cr$8=q;\&#uuM*/W}8RWLY{#Ly?[M2D.ٴS;PhCRQ{ WUõMĶOF@*[U/} 9HFJ m%bi)Ou!&W w8GgN1tYiR7>/-Æ%"C΂,6#"T@ n& hCj;Cs!v,OZTWgƸK JV֞,egulMr2mm~7;.ouAl#X02u!Om&5 l+VS MYu6>4|CkaF87Vg_M*h[Mií4%uZM莎:FDDAߠEbW}!ܛ;{c|mr*0@ufZ2NC2F;\$[֩yΖhz|vܱlx{^榈? 7;`GwkibC7~zR'/>#׎aJ@"W\O? gZȪRGnH|jR*{y'4 .g fehc~'?58|'~:2= 'd;|o'!MD4ժئ Iܐhp⏹Tb (_쮵Sʮeoӹ?P/LVȢsۏTֱ[R҄G:olBg/A뗜U|ۨrjE=ZNCe qݹwۛDu2gz$f7 . t[ܴcz_gOӕJ̧I:\`nkrn|X(I)T˂h ˑ:7 juqv_KxXgDY]Bg&)}/_ƴL;#QJ;f#%qd:P-&B[o% < m1)o` ^L$NP* 2L%~(xiWcHF (_OdwOȎ1XDjkRK"IIut!1OH&{LvTr3~`_ Y;p 3kbXjciTgh?F7=˚yC>ӐQMjD% Nz$߷bhCDUXrE@p1s4YnGmeu>zSR$:n%MWFwP'T97MEuD,#$Սtm0h pAߠ `8ikJq5fIb 2< ȄbvjؠdopM_V1(sC$0V4ay OՏ+!ϸoųlXX!\>=i3xٶ[fwHx  Rp~M9 :__j1E_. 3;t,~-Q8p}m^l` ~V;~De(QZm/i,#T-R)!Rq,;`ĈfЛdm5qk{g=h_TovnchYhY&K[iU]qC^zґkw+m|քBr~bcY!hb=ݭҩk Xb >g)6qj⌂B9яX3aԵAT㭴hq"y+J;2ڞ5S EF}ٲlfN=#((EV{M3=B:&;esQ7GR]bx< o7_gFA5U 8ollѬvaQwmԻ֦o:醻Xp޶:{RdAz2ٻ}si;] 1CLr!q"br٦ Q>9`4Srض͐t56#;yO=I#n[+.pH *c FL u rq9Dw ժj1NP^fp%KDa4cEk͝&"ƅFspԍPn;7ye6JVK^?v4 =ф G !cȰ[Swe(nWzۤ]0cH5" ZО"2M eǒ9v#.&ɕ 8xIs+c{C K4-$e='DQ{o5bdXv*Lϕʹ]E(0d)ݦT/e,KTsi#{TM&h:(Z7ŭ$]MoR(3,<a >~Knܪu#\ ni&n(Rs5Y5=El HpS9˰Fmҿ0aT 5MF@4%!VѢNuMaŌ\g\Mr2Z 2W*t)l<{T|Xg:oV=ܭHn~5C,n,ı"-U <CVI&S:)WX;x9*$ k)(Tz`8Z*Ԭ[pk: K|nwmaR4vʅ({prXU*.< 2G>B ={6< p.(5?.t38hZPgg1P[$eVmnoҳ^챙4nS跽/_.QxVf)mő! 3v2@PX ӵj}D߿e>e%]A/s&lq3F7: &lT8C3 ohޅ} |954`|܂+R~;Q"sE%-@[JDHt#hiH4A>/I@aOK'psMy sʯ$ύL},2E^4?)zTp UaFkݫ!&;Vɴ]L&gKAlF80̉P[=.фu_.ot~ԏ̞TW?S-$Az}BoA^Х.&(AR:H27BovVھJ 8Ad?:wdNuz/ ꗶr QzPTޚwFC `~}íџVG=:dѳ(K.1 {.1,JKcE$@YSm.~ncާ>RGZ] 7gKt$JQT1C:[q_] ]OcWqY<#]38&F@˕4wVĦOfnU9NvnlH/m7Ny(n:{À^_rO^9h氋 `wkcbc#mu##i͑;ђ6.Rsh&э"SӀc`}xOBuZ "DSa:D *Z߰\H|RntB)TFvأ8 v,R jKriWQ UE[߬r*Q tQ7| Inbk]k+Hb4U1<УM3C(%>7x ۂn,ˉ C!-a\9QS>k~T9TpGJyWN+$ˠXQH̓¡Z$7 ު0SL"n~TtA)CuK1mq}r[:D>6.n/6|[X7?yxhgDB@t}W1J4T)nN~ʕMtuwZ]sٰ{dS%kPDZ+G~hATndkH%HYN1<$JZ5nk_Z5a9*GMwf }8zX|j^ [nc(*w% #JD5߉[( m)HiCux#:/k)+Ӈ=-ʁAϪq,Ztp@~Sě$؞tԮn,0ywy'~kF0*>9푬jJ,pQ{R|;80e^SڪٓRHNƵIa\"QC(V0Npr}a^3$;"Č;3S|2'{4Ug`5)|[rbEPCұKt'%D5pŔyHy[ &_ ~h/KmD8 ʖ?a=fMҕRx[ܒVh-F_>:^9XOYӆdM'ٍJcT5?̩Kt&8D 4P)sC>jDٽokQHFԜ]y].&INw5.e3]](V!9_l`5F Z^y$Lw+w QBQ qAՂX+D"l 4|!|+ kVy.wbU:9` BA7X`Zx{'RToAq;cH TSDO8"}M׉rYn!΀uL{^Ӑټ8P:j̝KZ )~znG:w֫\_{Ļg : uE3 i ݼ0rZz+4 $f'6c!Q_F!l6Cns tل@>P*j38En5!7f;cHiDRYX4K )nu9`aί"yE\+-/YZ]*՜5G0.,Zx+nmb|sd[\z6+ȧ2&L QrFr:&)ӯAQ9[cs\6#)H'=OىBr_ ,BV'ba-N|5٬3/*[TCdc"`K> XRFu]yptI`׸~:-AD#//3:ֈT(5ս" ow4"]WEMfSUu0 \maC%tOdYȍNk4:MAF(fM'åK=}zK׹u]ۤFEw*xm څndŎnQ}$PM#:J&&?.;/d}7\V0\Ь,/ql"NsK%gWrٴ2]'^\mW#vaVN ӑ7{äxPGryMZTuU`Ɔu7L jJ Վ aE2ٍR,`g|_]ǭYk 6<["-kɬ%Hܙb/rL&nA(n2 :0hlkrLInt>{Œ4mUh&eLg\N#fPw F̑hߨ-< .*^J40UEr`;tEo$xm2u)!;)'3!@t{ IDAT#t:ne )lHmUYW4b c2EHkr4}`Khu!)k g:HkLbNUrIX|Y#(Q:͛.G$U+ ,J"q$I;ItOLn|}u'ZfM>8ۋ>H%M/wt)Hi )V[dik#J1Ux@h)sL>v_SLN \c#UmCg8~Y_CbK+Xz"&n V%z0 G&YDF.6Òci؃ la'u^=!E~ip64ѽ־5pqU |F5/S'E)ѧ ըZinuΎ f%<^.I%Tǰ|.ANt)6f[}vgz;Mۑ'\Hz M@ JC.ALvS5[.(@.UYo\0O-Au]ykȐR_D}1񳄛ժ _S,w^>ᖊ 5W"ik]{Ǔ}?(KC( I;'KZ\]?N󰗑 ~]1歹fͪT'jLs]{bUY`bl ԹL:1]c cqG\E&ob0݁0b?ʓkx)f?#Cb \j߼t*Ue)n\bђ1Bq)!WѼ;t%Sm ĞmH9nT)ظPwtj=2,,f塥h嬆oF_=QZzwg9f< Z|d)cy#j҉B8jzez]WB<{>PM'Fv8b9SNw+_r=O]9%rR8"atÈǔa-ϝ\cȂveA#B1ӄ[n:[Z}wj>i L% d 6W4Cj?˸߽~NYIRT}Bg 9Qf?D`6HvMs $z+S˪C8;Dj){I|y gzS(<2DL2Nu3ᳲ,"̈́Sde*tj :;Xmέ&$/B: Xݫ #Ϩv!D&'Z4N/=wKc JmVUܴwl)ow=,(!An\>x8$j2 vUc+/ H RqyPM!VVwޘedV: Y݅m`iw b&ZBh}++$u=T`jc!vEW% Ypۂ~و,Cı8S&OA$~+~do%r 7 3亮br(^I3`! V0GsnGx#Mn :Ie#i[WlNEСr+$h [y5³fMU&Cs"&Al elū"⏳> ùVn>Qdx fBtbosk+!C~/+S7ݷ&@n- >/l7ZPZx~y4q[2RwmIf, pҾK oA\VD$F-R g]^&3:߿GÔwu#n_S0 0dsh? &(0/{ٖ@JЉz$D7 ,Te5BE%sMdg |]nי7{'Eޝc,L _^MgG>!]w%)7eci];VX!@D98ixP6v|# '83"oRh\R^wCJua'Ȏ@Cj] HD-j}l;%@AK IGBDzN+krs>3H1ۂƜZQ~A20K{c`2NW+.=nYGS| - \;䞤VBd:V۞ITЙz{ݤcޔ4aLgiZڶY@F'};G3$Nɓꊞmvj{JWGpbŖ){@EV%ehQMۺtɬk&5)e$PƊj@-m\^'̓PR){xSn\4( gmKuI~VsP^u1l@D5ڑU[=^D W ѿX2FbBȭ]Ϋ2 7_ "Q?BAMc~J3J1m^M_>XٴZ)e}U|T{ Bp;[>`+-g:?)>txHZH7էVS11*#3H~ճ:!ڻޮ4&QX[#/TZ=Z-s.m6Pa-k_T,'E *lW32/*۵8T _Uatiо"%?"O$7yA?|jm <(I;t[ -a-[&SЊz23 1[(ԊSTjukyXLhB˓Vle*:D8 \H#fUD9jb?iѧ钰<=<:q[𩔇ߩ1l{y|JNFθ#O# t_6nEбy6*5RFUm)8mc’( Q &,?`|ڎP)WZCAp q ýJq="C(\9 #\zWc&M핺S@gB<٭|yN c.<nSQ_~0Z;#LyL `-oHGJVDDK.cjNV]7D~>JT Ť:ic+z#Cʎ{U!>;|9 fh`w'=ʋ8{zEw4,oX]aUDfx]d\M.?cDAB=̿HLJ.ZX.6Kըwz co{% 'y]QDr;naCx{EzlK7 C1"Z_ó}>:'p呫#%J /77xih[Wvo.m,ro"rOԠ]VLo`[BZ0 @5xqZ?vg/ҙc0m#Z.gPswħhEtT1NQ@8Bj$+3_4O)UZf GC‡*r~gNx#jo!z&1`lкY癴h՜[N~jlhq`43 3z37' nCŞt5kzA2#O9=>D[FJR5TNYz[ 9zC6uXi"tGXE}?5j` -K/gct<(!1Cͱռznr˦?Et !S_smPhD{C%CM[Z@vr3p*A6Gus%<_FaܴIc "Xx 6HDN;HO馱F)geՠȱ#%BRnU7VeE ;GҒ! ry]oq"m pYNr@;ML3|D#=kNj[GhDxբ+\B_K ҅N e"UNHDzнb/9g/@ЖqH{:M:TOs"E+uTٰ>=;LgVwII:sdB{uS9 %"UNI'~T^(eO7kcJ]fݼjkp;H]rB/ԹMk E=)b17\]-8Hys,* B@M N9#V}orn I{C8\Ѧ?Da:^^`h!2Vg'-RAa ncn{J0;jL 7-(bFfjNJtiM9 /6]%OryX[a b#c5c0*"GTV^y\&>b ө3Dk|[`DvU*#JI'>ڔ EtdwޞLȀ (O.|$ h 19V>5P6lˬeF%Rg 6@#̏Ø^œRKCȨڣُ&m>Ӓ^X(-x ɇB_ʷFa X G/|QCobր%c!&TA'xļM+nvv<.)cfEsU"ݍ*-{ŤuA>n摭c_/@9$>Nlk_<<,4ͽHw%%4D= ̯[ywI.Ӭi-׈;YnE\ S ;Dv= ӌәxUBU2dfiB3H}dq09ơxR\ o_dL/`ѼxXL>ܻ%/+x&[=:dfwLo׳ߣd*Փ qԃT0>7Uj'6LSFJ} nb>R\C0ޠ[OG9?մ$J4fZ FI^$NÎ Md K8s~+jHV]%9Eo1:^3گwTeNnV;f>>{ I3NpE#Qb=mBlvEMޓj vTϐ[Iw-mW!وOԓ5z˶X(9l28x]Kن閍gn4l>LE*DoCzlw?}T|V_bN;u5Q"D SiHf=dYVx&s_U,ѯ\`{v[}>i蚟⚔Ym+Jߟl >c_eo\]޴NК[ v6 4D/uE({Ut|զ -c Us姉 ˑsBy_vRsJ4OF -I ڦDRmti+odXrC=Nς=o {$FW~%zh]Q: E5u'y>K2JЖKajtԒx97b3ݡ+2e > Ql$snJ4{v\{ (?bh˘ N41@&"' ugppGeg~^",;boM8XHeء.БYL ^#mpNEnzσ T3*r;|;9t*_49jcf|LGP8 !iA[}`s!)S)dgfgwi[/i7,f_I"9s jDUL4_qPUH?w&-Z9d/+J~G]wDumȝE{Jm=P$"3QYUc驔ag6Fvqr!V:l^06s*Y/pk5̻ 9*LAd^tlbGD,4u*:)|o7M~ͰۃFcrZ2 ت=A+4̜C8F_Nj*scWyk 2!sf'o.jƄ~1 ;nջSN./J+Ѿ։k/B+tMi/*,\r@G{Vd^avPCdBCdf1xCwe:-M9ͬ du^ k5?0u[m+ |.` .h6j-6g}k8nWuMX^.h Dt$H\RX[HPT>b.޹qIZ}DĴD," 4%p*YG@dA-l9fpgrFp';v̷<@v'nPe-B5[UnYXpCÌ #tar:82 YƇYjV\w/2#~tn,zUөJAኆR<˲.y Ěܥ^6MbhF;yMa䒂u*:HC_q`3-K91kM}8򸏱3'f./*K9j05Ԉ iqμhw (gy6]k*-[MTl/wru 7 @, CWݖ1F&T`,D0,\c#X[d-./1hbTSϲ*gSQP3_/MT7Mr%OXQ1-|2^ŀrBBp1⚨ER ^c.=V x3Yը dvpگ}n}խIRu퍆ʱL{<(V9gԈ%vwR:yWʾuN<\BY-/>sY"JC W[w/cJ=,jr@sc k.gOs/Ck `>G*X^](`R \ /W{]&8 kI`6_eQ^5x,Ҡm]N,3}fGf13!_(JC{2~`9ZCP52T6-@/!<]i=29`P K}˃,gV8RyG1M4z[y`* j k~^"JKs"̜E s dNߡ)qN [pO`lopϞ5Z>Or,Osd G' vꮪj¾C̀ͽà!?[3Gn?9823 &[uBW뀱xJE] `%<Oܖށ27XS qT &Y; ~.t%yW.RSa QUzFi0JN)ܷZ?4j`ͅN5gh0nL.FzArǝōVG$/AYڡg3 cٯQ&\v-^BJpx3@b:)uѽ[Գ`L޴SA'v}}{; Qc+<]N.e y4-ʄƬ.qӫ܂.ޒhثH: ~)8oOpS ʅ32l7hT2ވ*>~YDCDR%ΔY. ES+m]im$l_g] up#B#0­P=*lZb+S`~W+?뭍5%GUY=&Z),Η_ܔT#X6]<{R mAswF-eZ + ;28,r fjg'ƙ0䔢 TPI_ZBmOia+϶iq%)ܧL ?$!Sq7kLxLuC %7["] r+ @T|y}d>&Yk%_Qůa℞JD<`,PzќSTCkXalkb ]my֧Yt07^4`ݘ#3j>J:gZƬ΢7ԬPkT5oQ׎$,ˬ]S4A)@u8 o dPY7Rmɾ7IiDx>3+c'}T#ɃUHR{J_ ̸̅v8 Zd孚o9Lc8ۃ}R>]FMZK Ud> 3זC'(W`]xݵd [ d=rMLàń V[꬈S%v5 6^ NiuzIʟ_Wq׉wqYZ;T6Lq=+e UWŢ3$|.3m=8OC\ztqv8_UEDd* ]:5O c'ʓ!X ,_\(+AFkԴ?֒@K? E1iAW5KGtFyJyJT*Mv>nĿPxŎ""3iQsXAכSMۺSO V]$2,hi"S k::+>ń%VƷqJf}yd|/MHDhE-MG(ɨjab6 BϯjM]KTkRx& юTwV MWa{Y^s 'S a_B_/e9y*ۻc+{Rv.j]b!vGe ` BBV{9_`|8!:=]JU5*PYs\vD M{)ѬŦm[z<5S+'* ݰ|!էMNz ΪNjR":s׍w{U @:SrEZGY`o -I:<{I 7R,jLTQ7ZFc{fA/w4)ٛkpsZ}=QΥ ˠ\h=z=dԟ"A-$<|B T*퓹}@H8QZXku7F]4'KKN Pa2hgrꍮFb x".6H ΩI9ɣ3F(+2z W<VyǸ9fڞۄ@hlP.@e܅2 .x)=3A5Z6=ZHZi:zZ #'])EER囷|D1; NƂm'-G?0.(l;кAV$yeA&Ӎ'B?h5)/ӑD#ӄ=LLD͞77zFJ}E?LC 3Q-3Y^HCHGipl`:mzq^&RG}Pu5a-=6 2syQQAYL.\ٯr_Zވj̝ ke)F]' . )q2~Iѐ (e{-S5(t!nY+{$ J.*_(2[֎M![D_P] |Hd@9yN- ? .l4l/UcB&ż6[E1 3isu{L8A N4g[a߃K_k˂920P"D_Ᏸffp̉'φ(#)byuV&rtue' >HIIYhsRGus"8Gw40M^R\n\ںPIa=7&DjV[t5]S؎m7ϒI/,؋RW*4)eԜ!:Fyl|Jů0m*emX-r+t$BS{ȺhXVYA?0oԭ]pTK+ۈm3r31v rUiv믍ЈO clE&ꬖ*aEZ6fɄt0H^JzP owrw]f^sKnFDub;ci$c0PΘf='5r+ňwxQ>EI @G]iSu ˿05CO& /i KkEK_Ps ൻ畡oY,3g4fƬg8CTꙊԮ:tOrlYp:H?j=ܨM$ck/*%ZU75$ c넠Q+y4NaY2$v% ٜ%0e2sUq8? zF*2:fFʌb1WUdDx1 W EʆUӮYR7yhYUnwi{N7Uj*Yk.u0!-]x­.)hpec2#}3d@)-Ve⥎5aJnյ6O| {MAilSB=o|i5A`L^Q~CKƌM};Љrkʝ g)Ez mJKމ5wݵ uOnב8-t)k!h\P.zJ8fd0% 5p([Fm(za3-NsMm3,K"V6K9ajՀ.!}f# r*O|9 a|h"˪C{0hў>9TqcEQ.[o[ꒅ@&&з}ć!%vxl[8yZ.Z:B[ z;O)˅Oyg^[~'=n2ׅ\JlAy۬{ l8 8Pr/Pz1s@WjT]DA([ ^nX.lR_=Tt 9xn6.iHF>E/,ilD/~循j 췂&8hYcc \`Q4J\FܶTRDP4{ڴЕ]uN_tB*&5h"v[-ɚĤՄ|}?!eZp<S&=3n{￿6dfeK:dgPXw>=5fK/f Menf̰2$|gksZ49L#αɍih9y_(XXE0iݘ?$q0f/RN|5 uk1bz8b}@3v8M:daĪr!^"NPƤ9 ȧBp F7y} )x.l |Ԡ̚LX+Ĺ\gNlht:-(ޕc$kdiF!#܂}[GЂu8ʣЙ _RGGHL0d5AW,w+n2?(kW(I]n2P!/!s! !ި!rTf*R;-݃ aGk{E{`^ Y c-Om$ʵAOѭ*Sղt>xd9>3> d̹oTP0h„EEbB`^؞8)MlJhJor?_`_WѬo!͆E}p i 4D"R[JW^1iqfM[ 5f<ڇ2 tULƈ scHmnFF0@*$,=Ng>_U5;|%̿RM 2.Pb=C[j2/b}brΰY3(Tz18 h>Tʃi\ֳM =phשkb5ھ$9o bnpWos0OFdQnw pp[\+Vf{݂ K'~áEYIe Z3"GzzUahn|0z;6D8F_EbU+J 00L73+JϨaYz#}2\c ݈ڟ'%5F$Hi=ۘzCu3K]9 FU=JZ`|s@U0RjD$Uɵ$hDQvMq֬b*~K`K$cK4$, R+XzCPwݕf3:՜#؀t/Ռ$g1!xoT|XVLsf鴉* 57抻nC5soi)lԱlG똵! i tNjyPN=Sf+!^ YGb+A#IvTE)RB}Rڥa]{GeO-9[ࠓ[9 ynbOaafзٸͺy3ڧ [m>}Hfk m"c%?pˀO5ifx/7H L n1D%s]q yuUJY|@)[qiHuXg}xjf L 7R^Hنڐv: gS@˅0I:dn|S0$;}2m]i7_MY[{îEG[VV-;)/#:>C\VV#M(['izåowFQM"i2/#H|gq/Z sFP"Pg/WhV cy,zt=]wHHreS/nSr'*̞& -m 4jr6 ɸtԅ%˛ 0=ԋq:_0Z=D=`k!2t& 1ǶdDt@- 6i3>hVflyۇ2:Gӫzv{뜡VJOXd{YxR^A֭>`Uf~Եj4lJy՘GEl~%`@3p L_?݉謢VYem2nhXDhgmy5Լ^7w6C~5Wcj[2>dK"K)u˵*;]1rn5fOMLSfǾkzZ6$~)yU+4iK2r`m/Ln"{2 Ӧ.5b*+hˊ)M5'_ːr4O0:͞)iq͎9bB $(y?dGI |巣5=c%JF! [Kw 6F3cNڜuS5V$pZHJTə)|s0jX3:<tv5c>c⮽{a"ye;8l42,=5;/QJlX94&?YqHe me Vb{#P1l\kfo0jCV6O>dX~eaׇ|˙ևŏP'lzJۍ0Vt/iT]S _tX"@}\kJWn_ξ'2Bݴ|d,2>XTdHgzv0xwU^)*MRw6 |tdM' JaXK2.pwZTs]TnГ|[]8^;%G:t9w8nU[$ՑX4OE'&K(K `yp,M;+tWdPK,A(SyzqeG^dP36KH:~ߕQdL>LTӀ <#Ư"VU3\XR|ٴiܣ|>NTt*1C wEb4%AF.'QZ!#&"Y)]X̲#8-sgY2K-!ɩ$(NU #rO~3 E~Hw!{R M_5cCgcA/ +x*UEe)pA̰ոfFyq]WJ2o,ZηșzPwB9UůJTo{Q6O8樿gZ)@*5SIhS\MCRFQq͒ZD/x"[9+ csg=9^"^/ߛ71OH:Icl}3leFIT c.+/U^BhUҤۮe%XΗ&%}X gdSc07Qό{SOMIWO4OO`$Y46J* L"drW&\+e_~{2unaBEbO%y "N*¼m-ԢZSw?nJM}T-p6/98.|weP:w`켺>83nl)/_t3WgOhL&>.*Ȑ2fcmeO.DD`nw.茲rP2BN0w0R.ȭU|^Ճ፻P*t_^Acs ƸSg[#,rj ӈ5nAYN^[Az-k 5;)_R&H0> ]DGi )QTlCS8E?*M6K$:=LHsϦ0~C_m/]pEoɐĻBnfJMJӴ \C%*kbm} ROwD[ųzJ<({0Z34_+B8K hpnd%L@¦ޠM!< iZx)HW?Ih^+.4x .@?2]uk:Rm)݀ay|?%#xKlZ  O`GwV:d &,- *2(6r%R.unXiةhX)gڅw,m-\nNNϑhN">vr.~T^Lj`W`j9ZD¸n%nD[/rgGqDkт k۬>t.Frh[y I CzOdaX)JT8ߴAQE 'V.lʆCo@DQ$3;yC #Ҙ҉psy\q6~Nx "Oݨҝ sD]Sڜm Df#zN#(#A4sZGOpe/Oug}pIֵFܞ`|.es@a4$ s:@tAojdߴFB 9MFqkʮ̛Ym1¾UgIql==iBekǖI!YcawnǠ܆TC* :SQn(9h?-Xs[* u_ IDAT+xҘ&\,; !GHCf FPw id e5ޓilDzEAu^2bԗn;%i𵡽;l>ph"# ѻeѽ)ëDg~jht18z|)Rh[t'P+vUǣTxƐ-`DɂM̗Ld-doS{bil٥cX}Zɋm.%ק[+3sq1YFRy; 8~{ת(V% ShVw|K|#{7Q9.Z.%tKEnݡԌ+P+IȊ_nѺ<aj#0a3ҀNI;=BmmOT΢Ϭ04VPuEG'`C$>Ā;g˂~rZ("婁 k{yJ߸ffq˸RV'/1Q&+&uW0롑$[^p1N+¦MGSv`4CS0>KYJ=mê|+!XeQ4}",Zr[+ fyA[L깱:/ #34(VΧQa=쀁8|%rk *t\l% xMEskㆴP>6Uj]5T.ȏܱzT3֌Ϲgqz!a6)B"tv0OJG;^fapڐLK=Hg{ɬn`O_9+ELyנ#&\K,-BH/%{d~63X( y~O= ٌk/oIaY~C1/7fQL|?F# F\VM!"j^ zu!,0??M/WbRH'S;k.B9ZeqИD.VvHWU{RbNa脞TyneKJ+7=gZZ MZ{7tGwWͶ4q5=_5xٛI䝦Mk^aPZUl g([R{WBA+| uT I3jG=.BP'}8C\0E&vZnҟ]4?%Sצ*FUOw9`s#,M44m$e2_tr[ijcSY mAM@eW&D޴HzFtg6,~Y{Ȍ#)+óT Lyzj G~e5!"Z@d7݁{t VKXU  7a0/jy=jgWz[hHm5m@=~4EkY|(eN ۨSmNc Zb\XA06Df{6)UsmzB6N26.1#-P0ee?Z2J Sgf8v-BA:jwq{5vCMPSO+'_»Հm8kY6U4B,I{nu$֧bפH$4,R`bnHmeALid4g`0s/!J/`a^ xNJQ>3Kƺ3SV2sRDvy 4Ԙ0P_LId6"Q 0R-(GrV(fZhJ7y>s3d >3a;4}a8y,BlP)O|GhK1?-2ejJ P>Wuq!#Cv@ [X),3 *_9i]Srt`2Ǭ+,4U9.Ԇul붕sWV']ёSZ^㜠JHF,C;A2s|8gu!)e٤0Hq4W5*ɶ-υEkdQ1ՃAHً /K'2u%r}eWI#E&y56Ɨ@CZ:VgE|.:a"$&&R'q$ٶHV[ 3MwʐyM9(ʲNi:Q (rxhcV2s%q\y!jId?uQ{Id6,%Z0EN'm1 KJ*G={mHOKnOՋKMиhŇ/ 48Ւ>vBBvmCxoI!&vIFa @ (.]OƿC7'~8XT+xU&%[Rz|cTcG_=1=q" Q}N4vgNSf|n'PHLy7q j܆qUv΢ ֘e6鸨 {Λ.ls?0bՠ-Q~1kC.K(YeIҝ;cRc8ݰJԩ68_>m oEOdK|; {XA|c fr`JzK^WქnmXsD3m49A2F89`(>"bF#@T0mAa=!Keؠܭ=- KYQ!ǠT∯8iS</.,3}^` hiHEYT3i1_pw5ADVC{L?#G-pS>:^VYb:Jp+t}<qjRK)]pa(۔>=9}00*jmSD-E(ƶ٘<0GVQq@F ~D]8Vp*[yTQY vԛ t:EYlXRҰQʹL{z#KtȌSḞk`W)Ꮧ {<eǨItKnR7ՌQ_:Ѥ tm+ ^#GSDT?S3]+ ƜX۾B}rOn[~,]hE~/b9pq9/RVdym'\/s5+nvh }o2_"ӹm>U mX NMݥ]rDX%Z=)/> ltjS% <8wZ7qC=†+a+0oXMTeQHQf7E.A`|g5CBta1޶MjI[Z@cXnu4sBIU%t+&Lrf":D9j/$ޘ;y7&,/7VvBJpL-]3z߫A A:<ȥ@/J Z\xǙSQT >YWC6@hh\GЕ@>dpc 7/>JZA)]Cݟ0wyvX9Wͮ|!@L>>$}] ^8"j[6%#ĢS)^f?E|+f[\վ qx֬vHKšEc(b){͢-$-eT3ZE*L8;RQ$.:rd~z*o`L##l1ZA'ը&^|zW(vgclav}Tɸgv5vu',˯5lFuyDhϵNsL28٤?wv!w| kTD4[!1 F~X**#L IX.}dݨ%R 4vP3 7 #ۦvc K-F\5KVjdn.d9B\DVVxA@Ĥ-tPS E B<)IqOzeK90 |d/JYbmtC-:̦9$F m 1B\xLa'i`iFka(W(vO{F:AN2PW<{?pk5 49kMg 3,H#6H8RdmѐݮkYۙs9|&[x`F+y}ڗc7;MH %Nv FⷨB~5TJt wW1{$ۈmƾ=6pdobj> o:0xs7j;֓ާ'"^6@ݽC洚[Pn*ʠ,>/.# + ſO!|!k c} ٹZVb+7X[l,KI-87NdVzUGbUeF'l\FtV[: S !sU(}k4Țv,ZjbƐ=K9#_MUM\ow1UOQ *Rlu+`ܭ&Jw \ŬQ K@,5q5u( OJGQߩ.dK۹)$$ZR.e{(7j N|LC2 ]Uӹk׼M/,Q f˰0tJ7\x2@1T+1luk+I( 7FtzJ gvjySQw_$ȹEcD_VtFHܓ8sͱj%ѵr|r%WP2E2^2xq}.}P\u2 bBvAfTh.0#(8p$`f- TE]psߜGÅ/XbHO}@LџOO-sFm%TdyJ+$zwo@-րrz$B1潢[Z-O '35"类pQh}O0=]=CD  i`ҷG.[zY# 2a-=6NEӑr;ڑ*.f۸7׫?sDALȪ/kn_/M|8aܜ:[=\=+/X uPB4mL?)$<_$2F̗Jcv0^4;[pp*cfjMcp ? >|@EXE:4@4$IlUr^ꯋfQ**q2}l%p_וh6 Q f_On%.dA_Pz Gы<%X6YΎ8>>B ?/]"F2HWܱcFg҄O ]@_MeiPZG:ך}OԱ>M<%o0W#pȱeG|ՙ5% 'ccKiMxD5Fi9K@G#I!L!;I9~`j{&Xcĥ#256q79O`*1PԘ^(Q)h:¢4| `! J=J_P\|f[Ϡ!GA yُj/]S57u2g,ƕƑFpٯT]@6vhq!dgƺ]?Y:_,Bٖ3WWC-qȤHJK3mg:&~@O=s:/Ⲩkp0ktRq^9[/=…seM~{{L2P _, iZٖ&P p;,pSzc:)E ƃ+4Nwi߰h5z<1|b.c$C'@ sj\*͐=͝K1[sbz(f*CCFf(kl26K!eȲuya6x QDFJ9-u @([#'2aqj6- g~ +W:tT$;-z^ZQ.jfVՅ˔}-&4k/eƝE s s՟I2IYF֤|Y&hڗ8qN%#75an*lJQۆO)M͟ꐉF%yG3Ҵ-Op?7S]luҧ+zGPܯԎHOfѾEx J{Uw/:Ykm)2 2#e6j@ NwƅU70@Rn2.hRcfk1LJQ^S2: eR鄨Byzݰ}} 9Sg$Ft3c80@b;QX;E_m>#ImP ؙ~YUW:1O\?ػtmV]6̎ t J͊#V IDATasvf ' k =*_ 0?RׇN>EpU&9Vg%GiHf9.$TҡGc} u&1ksx/[Vu:{'CR~~)%<8^^uFJ. Lt7/& wW.XL}G=I}^#k-V iAfGh>b|:泷V'epB5E Ld+uęjb;ڲjcvrRŨ FF2ٚXxJd"Hm1Mo @0&y$A&v+?ST ȶw/P aC^~+^=Mexw.Ģ(쩶;7u[85͓ ZxFYB|Z 'Jb#?-flKv6\zH:'C#SeŃM&f@mT% ɹԚw}|P6&F'-rҜاvslaeP#d) Tcv!kDΐ,FN)$ !1PkwAN+s 2SJZ#*}G4[0F5:'/X-MMV+潳4B{f\I7ʲ:*GSp〣ClybU:84֖ ->\8D/N$' 3YyzJF,{~nGXLWuH}RsD_riդO#{t 5zL0Ds7§o633,en)+Qs1zNʏ|2q:JJ~rpSbW(]ZSB@HYf. RVjj*Sg!( M l-$A]~op!ոNg+N=qLMC8khO}dEp|̕ VJ LqkH}#-?RU2>o$e=a(&/;U۱C]]Й0MX*Ŭ )+p/t:̪͚CHC ʣog\(k ZsiG`lʪXEWWb˳Ng򚰸PǸls %|ס?,˄0g2ɧB JJJrxJ-Vړ(9!4_؟b,Ɖ ~)jQ uoD64d0C&0M;#ռ~JgU~o^zmrk9MfVw\[v.my;->?yaP/@FӿT(+I+}qi\AO~f,SD9ޝLf=eK3rMN\V˛!)$]c77Ӓ}Vo)倴JgN+χE˚飢U_RF1E>Ôxt ?܎QC(<1?OյJ$רlyR<>~̿H2>+xavf4 `j#ȫ,+aL<[Ӣ&2uxJǓ tOaSN-`t\>,iZ /E\`<_h+4oYnEO{ߜH*m÷c d`YaaٞgF6c,Ӽ1L+$L\×6@D٩H,ub|,x~ضM gl| 79#'1\rnE`>կթrNRkp5l~5vS0#;CɳaO՗5Q(`AQ!Ƽaιy^ʪ.C#"uY8$$gӘN,+mFE\]&v1 AjnZӔA DJ#~vA&L/*՛rk զ|:=4uźőm^߅@P&gak"KdgM,Uš&rSg3āP͗s;{`'wd{A̡zi5ljb^W/x&ϕSz /< bF;@0"QX FOo0]/]ӓt*t\|9ّpcuJ6F s*;sSuԜV|(pP3؉2Nyg.[&UovgݚlZl"ܙ7!#=]^J3뱣P5d\S3n1![Z ^k鋪.>4$^l)#WQ48:i$l$}S+z~4 Nv8\^k%ML߃9i{Ԥ7x,ˇgz) bhÕmLRx]yF\l:juƽ5g+@%~tEfih֢ +_˹ѥ7:i/g  o9SCځ! *ۡa}4)x>rE?_b\y2U%pj:oz\_><2QX_ɁEU” :w|j9S+?z.؄o#.ri{<F3TYSpji S/gJVf@Q͑ڏTpU{Z98ٚ[˰6*r\yetmCrJ0Ra)ɍ* \R (nU-Rsأ<" ~SaHsπP&fLPJO?Zח mfbdٗZ{R}:8D=TsI*"4?T@%G; c ˠĐcx,`Z_7swbPv`cBjnk~O㸰ʍn)V[#ByM4@/zHp H:GiM\(A= (_iR̫[(XAkPADkJƶSq Ĵt"Pٸ t)7{O 5=j,8!=G).Uqmȑd|8f2y(GI.v;R18-a{TGPD_=—6n. a݂ g&`Pn&CohMr|fy~y`?Hn^`yNwu?b]kѴ[R %J ydy#%FQ/LJ;N :l܉!YsIRT_l|O=~kp<ňl{6* :`ƪxsC3prqZG6=IN1a_q4R@PMи)*uУ4G?^qG.e-,}*Nk:cZk⩡{~g{ Ah#Vȳϐx#ݜf?Ivc:|kťm:Jw\*}GӴUXʟt}]0ƦiqIc)Do xk eB73d @^.SYmT_! HRȇKK }훅rTx-%lq-PO^ҘZIvp6ba L*nVz,u# {D7K@ڐfnN+20X IDAT́j%_T *S."|UPJM7`EC#Ih<,;s|wu ~M={CʘEŭ16ڋ%(a&r-ankY5#93;PJa@/E2)=ϐk Y sG+0JB0|@nz&EّUS^%1\';gZx&>%8.z@RvFܶ5ǻ}!ن%{έNY4I8vx\ ǔYG Ipgɬ,5P>fN;|{]=M`T;^9b= @%ܧIv8fGP j:PL"H{Œ1Ț=:_ȬY_*}Ը1 !SE92_ڠD7sNG"ՠYMtt#4Oh؄yzW?q> (b'7n7^Ζg}C,ɧ Mv6i}c )(+SJõX)MBTpt2Vd)m9}T7| k [LO)6C^㫅;,0JtƁw &" |l5+6AjyJi=܏s ie/B̴R`@TGqf E -[Zø(&ڜ60Gz&1r3ְ,oSmq o9H.H54(%&m "2{œFFL֌n^‹_f߸9*)ȑֿl3po3` t6xx#7#8 "䟮H,]s=ޑ6ӋԈj+nO[Pփ>{%ި|)=,iiԓ#㑢|?F#9js'Amhayw3ް'Q;KϪr/c؛Qt lbi1@ࡑq^eၟ|o*F9q; iVcS=z )ڴz(>QGcoQ) 1aMbq Dat4qZѠe^IJ} 1]ȨSĦ3|if/W.4{ TRa$e} R';Sc[ޣtxA*v3CpKS;D 1 } !5_X`b;vjR+N o^ou ,Ĝrx]+; +Ƒ [&A9oyD߱#t|2`,DA( F"J{\8WIz%h+@FX41OD<}O_>ab#( n ($5fOƓAu[4c$,#YA<"ЁFw&vX(E R74}Pe_o6Dk+Ԭtm˺əWyag?d;~m+XDOt{C]hay&Gp Ko: Afy%ѫ wkqIJFKΡ4 3w)[ؤ ,O-hA?zySG(#TJ4aSѷE:O@bA ?=[i%­Zе1yjlL"xX%[2W{7Eimv$' cNt: } &!mrx$[i78@JP:y(^Xp Xb ^=B)LcdfIým/Y!z%  ORAZq^͵sK"~H lKnDz5w"mDApГo)%Z=i }A, D 2IP%vxJJ_P-Z_xL=x-Q' b\yI0q7 5(Zjv r,g!h['3\@5C6a-6O+b^fT4LGEtE.88=,A(.8)*H Ŝ6&3xgMy:$,ND ϵQ>:%nǍEf[7[DLb,n%܍f?F A.$'4]V2`jH1ꓰ-t$N{io=mBI'S5'ꙷ&0Uz=xY$H0F?!UX‘5 YA\zzᕤ>[b-l4yJs@q2 <hQqx%;Πuäub!J9&gѩ,3~U:oHD$(/5wۼ!ܹH#g͑2e:TO "dc Qzx s"Ao"֝":FyyUю9}^z;M<06qy#7_N4!HYH)ɶtl֠7D!rc؁:ެg(VРwK@>{1\c6_w(-Î89mȻOok]DDvֻx~6n@dPm²{IgQ;`pzבJ/=" F(qs%{'MOjm~2kj3ӖSxl3r确R7{:=^2VVpH Qz SL`\9$\ mA'j' nTgaBu1jZahzk*T "΃= 4~8cO0 ?SZ`BRA]x)x.xxď": Thd^ 'XC{48=ppct`3Ze8N<9˸F5iY d<)x T7K~JcFجcCSg3[FŲY.#6!V S4w4L?ۦ7xlmazY/d*XP%O(,EA< 6)h6QIIj\65{2b(!̠Rŗ%w(̆T4)AB]𕧦 ?oW4b\Xh)1$RNr&).غmAPr=2X!xX"ۤ9MdO,XsGSkn4n? 6Ԝ&8J;Iy][ېrW1 J[BNvߎ {]慢 ~. NmP&M4bxJQLʒ+y%$tqx/ORӺ,A|,$nF/QנxRLƶ^rfөA"+0>RIdwb&~ ,- 'bmA1; r>ns9 , Չ!g+lfPXwN-a<۟INJ28B0,{fHIKtfM7~hO&&;]߉9Bb@ͪ/0WZ]FOBҡ2$ӷ%CV~|K)?lãv6})XT܋Z={= ApNiFb>tK-;y}2f42a7:jD8Qܧqv8&5nPWj4HVCl>Īz|0ygEZ)F6mǣ݁ޅgmF ^q.ܢNTļzqhfkDHs" 9<J)g a& 2DZ{l!9خHUr6^CŤڻ "ONt9Mk$yoV4{#ǯm{b2pwc]̔:~ÑՠᎷ7p|҈_R: F :H;F6gyprŲ(H)ICQIO ݚlZ[P[ QVCA⑴satZA/+'*8@9u6<8 xaáҠT&9Z8lq K<6{zJ~]"ҖzFӵ  M':zu.̝)4rlM1N=B3j*HQhfLZL j O x mLuPmh,[}Tr9zPnث ^D}`,f+Kϙ4JCbe@[ v~+ԙv3g QC&55W6D&na@p4t^b9بv!!pZUܞ+~M ]{[iʦesB_byua";c@*cbi{@o;SCO{p,4Y0l]яWttۤ>}2$ASF^Abq("*F|N^e 5ls/M=a n`Bi ?ׂ\\ݣ(Ec\ ;#|Wjp&zŋ,kZrctvbakBr%yF;:L&G5j Cmq5 aJV#0Ϙj { ՞æBfby@(nGD4nѐŪ[Z3.Dq˶nvxeNbͶT(m f1TX5!'-u( Ƿ-'o!c9ǔ]NKrIhDZ0%>^xO/FE}& 6om Aգ⠇C+l~[54@jG:{i9O`_S"Y`'D4lvSbZ#^h IDATW^W 7+!n Qo#$8xSAJiW?DPNBf9_lfI ~KC >a;&Q[cR 8t]h(Z1,j=w# U֑R7&^# Y9*6`jyH9⹸$3[a@P<zg!Mzǝ{H\p`͖oU-F$8TaTS\ه T[9w_j$rꄇ>>ެh_^] u_~~N68WHz޴ wϷ&.qW4i|IT7 2|3 (vDe]< hh,xlZ1F !b[]5h>CԾ"Jbms<_=ẙߕ09Eڎ/ ԬPw= F0n0o Խ)/~7J,m!kf`w?X\ĆQ2O*n5m{X N( w8 OMu>-Wx(䴽JD *}?Q(]LB}ldW,~I]fRZG<[XV*u{yw sAȾߍE38qcj8;ҬK&{;U2n$M\t]i@Pf"hy` R!RQ$F NЮR$P5ᢙGu0t9Av)_4 0? Q-M 6;EysgqJĪX=n6v)=@uR:l.rG?=M% @8mc @d뵤 |efxfxR.Bj_g6gyP$n4*?Yq6Kb] [{,u`ߵA3ݪb4TdԜc[ң][pw8HcuwǶCpրʁϩ@Xv-y"Ʈ妟iOM(0ȴVzmB̙1tYr6žÁΆ9}vw4r`I63/d%Ǚy,#mAn,WzE,r"~P3N`I|ApKU6҇C 4l0а{P7RJ(MjFw{D\w?'XQZ"_ raOul4@)l|4cfoB>+r0(q'\ftjasGB͍:6{+oR=[4e慄fy^4 <}\`FCW4Iz93(m&Kf^f/T=("`2Z){ɿJѨzEІ竄r4/v>ɷll&,g@!u/<*#^2у|ȃ4ͭX`L $ҖFyO& b'qf߮%݉>Ҕ+*$stȍ_d$7gYj;: r9b߁f|aopO}9]s#;vtf q{ V1u<H/Yc42Z+amޔ*UW}O,xՖ.>B̐k+@a4S7~'<}Qsl[=_8AMrzeȰIy$jkĶ]sT? Q/B 5"jĔmxՃ gA@.5IՎ?/Ncg ̏H|HZ%P g-oB <NO^B.G5{Ѽ6$Y7 ,Z.iO4*K璚3Qj'3]:D4L~):fǰDIz# U.]q߅x )9B/h75WY7I[ar**'/{/O"u*+, 0b%\ ⒟hAI@o,-2% 怞)ROfV@[ k$^ULkbQF%L^sWiTlđj'AH7!Rڨ?(t~iM{ aMNcQfucFymJOи ^;S ZUE'YfiO5-/1x{s|o񣤮[9kUJ&Bբg ?~{aosP|O{Af00|.)qé5i 30=eᖽA\TH#mxtS7gU`{dT|AQ#7(Xt;&p{ـ\1*د}#<X`[4ODΌI7Qƹʀ1A!{G zOVf,:xa% aYb|#ݞl\| ^5i5L!m7%{h/n+KAnzjB|-wjaN'\3 ,ti.tbk;1E7  ^6,@-BΟ=ˏ1ވxU@d ȗVQIm @ec3C7@QeqɢiBٟeُ`lU)E/=(W2ԫS dA/8TCC6QJĜ%}hSYkY\:ǩPPB6ͫr-Edtr 02T}k4hthheLA>D:h7]WR'y9p cmGڋ&[ Q*E[&_HQ84)(o&BoDCP'~![bSHl'ASpqyyxi=7@Rw1:yE3*N{# Q,0HZ(ƚOkOMDU_#AJg8p:=t,{JR_WSŹm퀃{sAЊ*tyy5}IWa8|Pbo*J؊Յ:ջq2 8Ip}8۽)oI=LkwRi1 Ҭ T Mry–U{׳PH4x8N%0OksGMIxg^Cb{CF݄^F2uCKr^V_9BFSO~q x0<,y~Q7v|LR.3 yՙ-+U" WW{Cqs4.ڄ!Wt5Ј\{drc)獚kqc,<7aR#Mĵ"%2oRü2kP齽52{{u>azE1\c^0Q.]ȃ"WulH?hP F>ٽ涓`z' =†#$\2l0N+ fczd/ICDz \9ySZ˹9omq͵d;h_K&D;(ҫ^+[pn nd2&.𺴀Aq^_1;ϗjVߘ#e6iC#i9ͥYłW${Ԏ|SQOo@\f=!ږ(->Ո[6q';I*V,2{)D/3 ^gDL = di 4yHpP?YA&"T+ըmA9*N24*xZ,j逵Ք- wa{"/i0ɛ:.ǯi<~f)1-yt̨|%vkf SPaݎQ"{X+r2BV2kHށO i/B茐tROdĐ>b/0FXqOJ0Ҵ_[pŭDe:ܧ$ex{mӑ]bv"j 6GZnf3ؤ|xM+z*6J.g[3ZeG%kFGWiiQ>*9\#<,Qo)L6;QwcW#^U@uL"1S@݆ixz&S☯7g +F2Q.Ft c/ʦCnb7nydLmj p>+9+=@ Iwj ~ΥkSO z5_c$L:=_ݙNl!<sBV ;)xQ !*C::WaUvI C$tzډqV *?Qhaio߃mdًp$7A=P jofiNsryc4wmT >t~`liD*1ܽ .3p\,RB_I $nnHVyc/ >sa۫K/)#8I yUi$htˊga1,FW9W~~-- ,lfj|Y}_QW>*kSQ|M5tDJx&N!i9IL;z#^'5ЌdUV.Og}ܢq:?cT]%*WëdU_F}joZ#b[h*fg2E܎)yGLʂAYyæ2k@ N>Ky'w )6DIB"ZW srA͚ߗ P̖Ftg Ɨe&hsD arOfģi0߂smJٖ f<>kܒ>_)ّhGJd IDATVY>y-G=\|Q@ Fl YIn/hǷCZlTNNj{uq"'n$8L3 `? !OD W8 K b?B5|p {a7Vp 0oܿ!UW |U >GFȰg9M!b$ҭ"*|!T=01]{MeZƩ:!^@9{k> nyE"d&GQĂUP_+&d1?@kȏx \k11 Gib4,%wM.h1Mًצ;B6s8%3pg Cnٯtvl!ǩ{谵vӀ[3Yu9 Ia8A_9&tS|R})?nnpyqLM v9?| 4mY+%gqDk<2M&MQPMs{/ox#2h|1bP!J4Q{:9Vi! T\uۂ :riW%C/PgؾOK4 Q+rѝ/1)Ih>lfDniV!ONkj8 ?d2xpz ГCyk HѲMsG$MɮjX;Z^'RRFC%D"=C7ʗ"/W;|IK@79J/{i%! IsC,rƕĩt5,Ixjײۺ1dbX)i}1/z jR}iC Va~kۈ˟OCHN| [{vG2Eݴ?Fʑ<5Cp|`:Z Z>F}fDq ?T X(IF[0 9oFx4q?-PTnY<{G͜+:/^$eôCw'LH!Jpbʥe{BZ*|h9 !s!ZUD(Fv[_ϥO+3#^Љ.CG=5UzW7!՚U΄0wLH M=*ti5U[ dtq<~0jmco?ޚByځd &;j)N /5"L=gFvCG\{ ]sݣGηϔŎZm.S"v*C$ tA./v[Sdy@t+T>Ja9OgLJY8cl8iOoі>6kϳTY>jWcA\5qP>|Poг0k! u$ _lST~30_\>,pp?B,[4n_fu#CiH,l \+DI#e5 =VΩH炋ҁ %V%gkH]„aV~)@'~ <ѤLXV, B3Xܵ!qpP#/ys)L>T ;̳=9X=;o_ҲUϿ)Obm޿ׁOGhܚu\MǞ{C.GiALsh}qGAE#L$ t}*ǧMFI!aER^(*Y~V&נf\C~Dž Oà>"QIN<N#&qvvub@a q%#H tO8K};# 'Qcoc34T P݊}L]0ΓfRZn[7=ȆuPzjf{5 0C (SeMMU@4m/'w9}"rG>@ӱi8$/J3A5~7uϮG02g/no݆& lav@֙"o FUpHVU?J]Nl[[j%:h*$sa0 ~L1JWɜs>mNk2.SUb N[WL q}ţ%FO0`}0USS:GK#2vÈ&0l0ؒuCHb#_XT [NF-y,y7rN\荐tK ͐KL"շJ0]e"Ug@BƖܟ-mZWu*.Ki[WZ#QA]`}ߥW.qZIBܣۜn䪵D~;:6CXhdrMdF}]͓L|ϴUV) E>'W67QNʎhfuMs$NZg,]"ZJJTR{ζ>^fiN,w\?3 0 Nq"lA;mr=N'< Nc$y8b En9Čiuu>irwtJ7Tg*ϩۗNǺ N o] "zNaKR̆8쪅nB]Ve}zWYGXnɣyLbn / ݾJPѾ>,ep境N^'bO֧a)َjx %+.V'n{A͗1 `⠀;G둥q7[ޔ=q߳xhÀsPqs1Csz!dx+ڏt6-Tz>+LHiI&ኩ/㑾M c}se]nFQ {W[ \3x"<4?ED@E$;nۛ̀DvUlK)X=)\Md#jю+xnZEn]|faI'W;]$LE8"ybV%*qs)`fjp>fԶ$8p9X!%ONNӗ~1b PŰ+\-Ǡ—:iUS"޿q,v QgamjRXc k>=Bd;Չ&wXFJZTOsVQZ C&^PII&}h^U#>|Ҕ[M%kY\MUU0^z;b蜻 6,z+ [?e)%V8Y|FS}, 6 a)(Wpm( TI%<Ś+}Fm⚖x]=thJXי(|a\Hة?U&D*!pqe{A/G֙be1Li -řS+H! vƃ}Ƚy#ļYI=i[cQ5A. +ǭ/ʨkf5NmY(/F}ZA4WM]}b̽;V[~42AcIcޮd21Ɂ|txL^@] .]QwUvL@X+N}NRo:} n AX*e:|"@_FFk8Pmd4y Ivd  ;485KXMCrHL'= Ef/f1~QKu>IOGf#;o <( H%H=skEg7<ቆE ܻ,߸B@[Vۭ7XQ_Xs>:'v㴢PZGcb@PR32p9CN%RꗭSG5P;Ϳu L=Ķ۠Tpw5?>|ATά{iXfu zѧJ~ih^vldACA:U< BFP ǿ 9:N[ Ș܃ft\+ M^uõP* N r~.KbF6-|?W\_D҇Ob#qz8?'Jb B;!NI4lW@^43[/6eaS.Kx' -K*-h~5 t|oqkd"iٽNT^Zzx$}+^禩Ml)&_1᪐<p[p[Or}ǰ#nMIKorז50Gn ٚ}|)<'FD^7@zh]qڵ놟 iu)Cʵκ)TS.|A1TLao.P5D(^g0eKI) 0`ƃj)vZȧ/?vXSˆL <}"EԙeC8z(_çIs%a:Pm>޵Du-,o e}C JrV _+Z<ڲH&\;R 4eWx\aIf}י JkͶAmO2+jr 9wM;JC:=z/˅ұRxsB=ϜMzaZgɜDNq=Ivu}$l42=T!=v )͕ҤG1r N%.![f,u spQ+Tˣi2Ak矈VgeZN/):"83V2c߇?JEg*-5.eNoJc5N6#4 v?]}e >ts98B7}%9da'LPW;y٬>a, ?n;D]Z/{_i%;MQ 7^y-CR,lڷлR{&],_%2-Q}Wz*VS~~ e¤|t|*)G;۴ss~4K Lزsy/9iS@tԬC:&MX&u_CJ[T<"<>|`/. (S?0V"BZe⹿jcIop20O_/dLQ侙R彬I]'.zw4`GLQ?d!Mra]z\,WRuRD-Qc8<|]tқe_he_kcǷZ܍0*D0R !O)gSm2O'6U$_ 14#>&>k|GLssG^ht܊:|:>~&XSŤϙ#2hpb@7LO- I 7+ZE](] iHQEH`}.DBdrR ߚ-*mbLdO;@/hN5|:Ԧ/Qm]C^OTel'Z޴&T)S/l˲:,ө}֮ͯEv[Qޔm % ij^x}W[J hƿޠ Ϸt?:Z:.+]@M((ኾ#)~=m9opěu#UhqLDRrWˢTFBWN,XHJDzW=| 5Z3Oj62־{ΧڼQ8x:ˢiD\WX1$=z3> (Mߋ鉑һC e!$iKO)2v?Q]z-AY<F7d.ppvR?g  'd֋JIDAT@Ma+|f[VjQė~bWUE)St_)>#kyM]ߟSZChi,i+p M ($9tIlQOi"[uwznQu}y˕`M={Lˊ0xIͮFݽ} (=5#E3GeEyl|ڶm+C90)9r>LKD$IjI*Y ytxIf%}0e>vrF5k-'><ƹ| Rĉg; a| @C996"({zb%cΪtv6.^ =_XSO&Ss{6ېdc &ziiF LUmP0}@:<v%FaHl#yMww(q AK{aq~ salb#-Ǥq9VBU:h+f?oT>DsHWWZ+?hfW7*Ь6)D;&7s% TJ>'p`:(.<|P]}*.=\TRv|&):a_3 rY[ y6m鑃}Oդ>%YOALH#~)R- e6knYb$)۷ ,q(?KYtJ>5B b}EJ-ڨٛc;La#i3'=TH\Z[V$$ٌ̤FǎvHzgm܄[Qv (s!}J*Z.qJA;YR?1z@q>ķQ;qz{@,sS|>QlwP||h_\ƫ@H{('m$޽yy 6G|ΧI2i/:M+Igf҇ghtvT' ]*8uL^;rKzLr$|0\7A6 Q+qۅ5Rf\ .iZAV[:e{C-}`^G8v8_h̺`)F/X>M޲<9Jꝑ/jÜoA+kf싮TIys]ǰ5y@Y,:ŧ FG ?a i]]W{aBGWYPƅݴU0ݗh?5ϕ{jhF)I)[;ij8 $zka'M2(G׃`tɠfM\o9m?ѣ;S]|}v&Ĵ|逼wH JG_tNl);#&M.TmjH:nˁi>*=]{إޱqw; {%` -ȁ͒[X?rh0=&'|q\*t w.S(*\B-m̶:v_I5x)ɇw`e9kS="pOr8\>7fMx +:MZi1.p9cZ6&+*")AlVv1UϞհqE\NSA]ӟ2G:TuQ1Ƿw"Wm>ŬحX)n}ch|yεQxS+ c-)5u=T&iќşva`Gّ|)NI6 ]H,[G:PW :NE 럹b$9fJow'ktw%}v>;QFk"G.C`N뤝N^@o줏{nsKg-OJ}э Npq9_)/~w?x? WFݎgpz\xƷ#riR9uQ<t] ЇgoӌF5Όt*>fZ󂗲V x×9t??~)?B5PoO|raɟ~mr>9Fx5BBnrU'of9y@zU$_ҍD⓴k*'X.(?d-uD6xn[sgwB}wg}/Wz%9%zk$f. ª oiUrk=^ͻf+1u]Vui|o٣Vk v?hv2L̷ V HW;sU|f-m;9ZSz$?,WzVa]c_-T汩at/ nj:5 9Tڎ0њ뷱:cCQ?y<#sӻ<ZCo[H^ZPi>>Rᬸs4冚oOY#<$ MhLIV&m}oNc Uޏr^۵IkqƒzKU l/!tm?QQBz||'u`TdONlw J߿*3g\Hn@~Ucϙnko($zdojJ,e*F{kh2N?zEHMֲ0)u>*Do:`[`5 qBY^sY$,+] u|KbB^Aޝɒ[S?>ܭe)/\h|'Աsg([ϪfB~3ޮ&e84c7%S~  uȽ~ ^laG\%g { b0i\&2]\typwRywaіioʄѡV!l1+uwbML z/O)G'́˄(]xv.wMs`Ty3`Kd$!·dry4k77)T3O%IMDOTpyLыܴTd۪ҍ h2LRwhRS{>v&>{NKDb'˳oh( Gg穀ΐyIn3AM[ >hI^}u FF~ x;3[s>wk8-Aãɾ{ /5JO(0kzq J ˚^akz,3ƿZN1eF4p:H}u&wFJР'.X秨׾MuJ{h/@j-y6j^njQjM9j@kWaבه{n=F6ދ|>&U(▆~>!wލ烞( c3MHU]@˹5p0W!|% Ed+3֐ݱ'Ͻ_[,ꩦ=yT=wSEAdx_aЇ>*L܈ .MԶh&vŮ6xafW{P?2z!{ϤExAE>46)CJ`VӪ<k%eiR'b#GLbOFWk?OpaS웄v>Lw$x,>׹uUDSucŷ"h&d>&sw1;.?kbZ|(ЏK? k;~U'  `+Wd`ys]kOI(8}GRw-[:|)P;E 2l6Vm]kGu3 U6oYc$ -6I;07iqg.8ho6?QT&ޚdei%˖##UXO#'=n:~aɝ~x+ЧHrCclЏ o>U/CCc#u|5/O;¿vxoԿY$./I.=\Uv>هTq m ԅ?3StoFxׁB/Y=2X1g'O,Q՞?kqF~Yl5F7a sW>#i#Rs}ў|wjhݭmVg~')}3iYSݧ:rӭ;`7NgX*|r/K/v#F?bٳjT8pp.kᣎhgZx2:»ٔo U`UK8߼ȵFIZ|'d?yNtaWT:»~.N\7SZex _`;kG K u]%,a'ޤ*R򣜌{`]Q% [1V(i#Oػ}>G񭉘xHp/g>3뮻:e03W:#KN? kxg?]skwu]5Ӹ.|u]]>뮻7뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻뮻%"+v'IENDB`uEHeXk&eA#W`gMTP/web/Help2.png000064401651440000012000001612571157712272000146050ustar00darranstaff00003030200010PNG  IHDRHMtY pHYs  tIME_ IDATxytו}߻w!Ď8;Μ83>1s2&srvlǎcycc6hWki7յxIG?BWUO{}q6mڄ $=SO=3`rΆNnd2966mwxСCD"<j}>h(jff>x뭷ryyy3|H$ׯw}}}{Yz50pСCov[[If߲eعsgYY/~ ü^].ז-[0&9b,a,{ͳQN'Njb\.`0XVJUjuff@ J6M"xYTH$Z|>_&a. 0Ph j\.ј>t: ՎD"FIAqeb1*rrnTJt: pw8k8NJJF$E,~ś Xlw{t $H$DHd8.b\.GAt( $Ir\ jH$BFQT"|di EQEYV h|H$Lr\XP(.R)-ӺVGT*r%I*"Ij"mi:D",r\*|-P;4M$ǑTK @$IMMk>G*E:d+?Kҫzw:uD( BFsxr\._~p/^GrB$^&ymu\ ~aZr=Ӄz&**\AG_$ 9k9_$͒6-I$ Κ+U/lBB*$ k ]_4CS\*)rq TaK.3?.Ut9Uߐ(}_Qd3@C)2* eZ֚I/l,C4rX8䝸bsrݘY.Qpš,lT" {&?2_h8\g(t"EӃ-oH"mX`<:gvcl._ U.IÍdJ6 lLّ'xz$ MĔ& –bsSb0,C]o.Nt*n14E%4p0_ظH%4$Q(jvvvppҥK>/J8n4Fdr\bVvM(t[n5(|̙3Ol uuuq8 4vJ R4M 44F fT*ZZZ<O2.jffft[,lsssK233 T*0?yUUՄ71LHz}8^f 6f\c M3 az---V}].A$Iz</_` * ٩P(-[Zs??1 3::p^˲404ԍ0KN:˫FP(()J׫T*1 uuu.kƍ":t:7mڄaةSNh4F"O>DվK2vOOODEZJ6-'''LCOfdd]pv8z>H8p^zI*NOOIƲ42 }l芤i$IH6n*v' u:djhhH&i|###YYYffd:rի =9shN&4Mu]$Ivuudp8[o5<<&Lt:^TT~ɓ'O >zh'Pff`0 ^t@0 XiiAifXf  `H z{{كaH$W^ <ݎajdhit*??xxܹs@ 777//ǯ)V\988v5ҥKP$IRohOOOTe˖D"LFt$|^/((p4M577700FFFP8{9ߟB-6o"*f(flJR[,\ j ~۷KeYI˝_LT>===CCCR^Zss==pcccGGG4(J(.x+WDK7m㏣Z'?Ɋ+x<4)^tgpp?†,0 {Q,D.bs:˖-;s L&BP(D]oʰX,666j:;;cRggg XbF9vRkq8>jժg.E4Mqqd 2K TVVr8CF1J]x(I$5DۇV|c&v;EQ(h4a+***j'bb|=AM/-x믿,W Z|bbN3 ^S$ϋDB٬jbaf۫s\Z$ɖeKJJF^\. %,,677ݻwvv6Lr\FoߎR=S__L(feeT*AYΞ=+8][[R PVV6>>>55+\3::oٲd2}#G|Gַ*++[#GX,^bEUU:UTT "''M!qw\o>6fn"xdQ|>_VWTT(JrM,WUUddhZZRRYj@ X,W^:ަ|P6e .h SdG&RmZ,H.[]*#6B29KJ@?|t4p<\ @7-] @ @1c3zFBޱD$0P-Ws?aKC#mGg Zq\P, d\ |%N%<:dh9?Zf,d`39W# RGFBnd,0Lą<ab$JXŮ$#>l'U62KEdU)Οs0 ø"D-T*z2X naz]EU|=Rd"fz2X}  OF}1P8BP(Dkcc/~+ ڑ#G ەI%c"#Ftzt*dtb h4Jt:M)r lذl6_N xWkjj aie1a(2F4.V7Httt={vzz?nUX,*))7f 6FaYikRԵ qF ZZZ^hDF[*mmm:rV 3F-6ioPhfYL|P(Pv^pxzzznnN*:N>zj%eYaBP0L&\.wjj*L 99y?m%Im޼b p8Jd2aƲl"p8 q.P((B;bcXfbH#KKKN>KҫeYqH$"(D" ?xjZͲl,j]N$4u:Isss8_QD"[l 0 #o\De˖8~SN4]QQ!JX[h4z̙`08119ʼnN/^b` t:xCC8yJ2 NܹۗY x0 #"++ +H$ׯORi$/l۶mܿ'|>a2 6lhkkkhhJ&h ;Vo.*Z |+8Vf0j.j" [MMM<q8vpMlDRWWvJWX!]6Juwwulk֬)((wN6fbn|}\ ؼysyy9ah2l>sE8n6kkk7mDӴ@ vR*P(ZjkkQX,$ a\.WVoݺjAx'Xa $)74MS$I .]FU*J)VpNtW,rB `JwV/O6BUd8{MS4CP(Щd29==4==L&juyyyAAGGG.]u:C=ȸtѡtTD$q:hollD<% .k.4w>::z{]V"$}{jzzz?4Leee566?~GǏi;NZ/^xԩ@ O[,VhZ[[-ˎ;z}CCؑ#G~a@DPu9z6 :u C=dҧݻv{"O>M6!^vSO-6\{޽{wUU:u W ]$Igώa\~$޶ j+vJbwÆ :,յa^?77WWWp86mڄ>pЫ0<|xbba^׬YT*Yܷo_NN\.xŋ())tsss5k ;vde˖a&Jd{{;X,T*׮]+JRi(o~aغuxɭ[^.lDرc/^D"_|A:ٳrJ6#)//G?LLL jJ0i;v`Fpiii:4:qͦR|~YYفƮ87779D"JBK1 %%%VWTT8q%J ϟr:`0HBZ~E( gϞ BVZvJ$$IF"~dp\an@fffB99Vu% [{{Bc=|x +G}#D"ŲjժUV^mfVݻ>H$999;wD˽1 +++iwӧQ;vl޼tطoѣG?#X][[8b8H|g֭뮻)) jڜo}[ϛg||gEG};l)#hM&KErJ=XbE,C_ |>nZJ*J2//%J+M&Ӷm۬VkYY|0 ODuѨALxiii"(--F*0++ r|׮]V5ݲL&۵kEl2ooG*++{K*++ݹT*N"??a$<. Tueddx[Y|>gZCQDP(EvBd0#IbF\.a~A- V+ϧ(* ED"AQJ^P(R+^DQ͖6Tx<(J*|t'JrD"AwukzW0WթT(~k[lHD"Qaa!aЯp\.WӡTyͻ?ݻw'{/rqqg}[o޽lbӟ4??ox?O?, >t~f}}w*))ٽ{hGinn흝H$YYY{_X,@gg뭮0w}7! 6TUUI$[և{<Ԕ{͆DwIabK&sɘH$Bq%DqdqFL%"^@"/e߿-X)'H̜)F힙!IrppP(|&RFT y> $H$ yn@"{,ׯ_u .:u& ۖ-[lْVL +***++zc~_![X,vO:zu:իyN3 ׿niiK PSSSSS~hhhXnOr 8D"1Lg†\ p8+pXj\.hZe\.2אrqaO~r`0avp8n{wIR~/339񦦦O?t||رc/BCC0Lwp» ---ws=aX}}~9V{__| z/?򜜜Ǐ&{ԩSvGFFZ[[1 {G^x# ;w?sh{ﭪJP~vv@رcrf' B,sd2ɲ\suݺu7ndW,0L4UT.jve˖ _999|>Isl49DbIEo URMgȦMFZV*.]jjj( :}٣>zuww:tbdeeq8D"p$Ɇ ~ss fRT^b,_l6rm߾ͣFѳgd˅ TSScrV .( X]bD"ξL&֭[/@uϝ;RVVV]]vHR`0hJ/H&`ptt.**B#f3an8a7n[䢑H… ###eeez~A_J2LzGGG|pĉfdd _paVuA#---=bq{{hT*?`?rtqqUB! uIRNSՒ$z_y˯q׮]ps׮]VBonn bttt7oF&n?{lyywx|dd=1ߓ[E.:77wĉ p\[*8p mdeeQHn"p\Jy{Ju;0`ѣTjE]+2 ΏAӉ4Me qL8KwE%$8n0|JJŲl,K$W̔d###!aC7vMp,;xP(ܱcDze49q2( $\.wAȟ977$~?"dl,d2;H,DEn߾=-lxfRhdffb@ RySC ܛ$Iχ=!J(${[j$Σ8ɴsrX,J@-Vb"L?wΝht>+W4L+ 2l~WX,VZm0@IU)))yQr~&Irvvȑ#A<~T*-++J8s8B0N/!??W_pg}O<1ڽ{NyyygΜٲeKOOOgg'RpD"Y|홚o[\\rJRl0JM&SEEE"|z)^770D̙3gQ-;vbdggze!liW$I äp(y?4<233>|o|`˖-?~8rw'Lfeeeee[onnZo@$XbrrߗbF.؆ӳNŋfBAUTT*k׮5 "zdd䗿JDp' &ǍFサSSShrGVo޼ѣ`[rGNQRR@Ь0, 4m۶kfqp8@UVVR'NOf,F"2BU Hx ;;)5ؐM0 EQh2R/X,E+/a[nݞ={m6ڵksrrbq]]'&&JeEEʕ+#NgZcx<0MMMx4W9996lسgOssl^vV^0+7nxcǎq8p8޼y3jY}`phhdٲeVu===+V@ڹnݺ8pMA4͂&tQXR[nhmmjk׮K9b8''ܹs/^ F|W+rE"BV+6d9rS@FFFyyknKgDT*599DJ%uud? IDATٛ6mxPnn.ʪ isE!~? C!K6R̘ ]NL& a|>_.\Eb T*X, \嚠ȍ  ~a BpZ /6a6a,I1lcIsM(_P؂1r97 y,Uc橄סO8b$ nF|#Th$-UІh{V @L.k&@G,iN.9 +n{ T"ɔ/BUDLX2E^$P(d>q/lI:Jq1&10̟0)6bI&`q:/kd8ǣiih 2)$Y.)؂}S4;b|Q}5$IoBdj)/k v:aXSP8AG|ěRX*KEh,rE1,ˢɶ;O~/X\\;c1 M1b4FaKQd ,-c*O⊸p0L"=C;2 shll beggb>oZ}>~f vi0[f]r񲲲5[TTF@ (;w?]z"z<ǷmۖAE'H(S¨酐LJa s}!3HAY,dd6V+1,..޹sF#" ")H rI+v7a s W8>Q6"@(m>\.ODQT&􌌌,a;;;/_%Jok`85?EEELKKxo믿NE/^^c20 |>+ cǎe+ݸqk׆n}|af2`C46}GEEP8S5a _ c'B` 00Ax& [Eo7'X\]]z5۽gddTTT-6APfffrrrr\`mǎ1x< k5 ]cvvvǎ999p| IJJzt:ɓ'kjjV=C `(5E?IAQDEQN[Ի)1hRW&brp Q (c@rb.Q( 0qdddrrr0x0( %999_pbI:{jMbX Lv\/҆ "†aĄP($I 7bB! G Frr~G]v=5LCCCapnn.ZBkH  byV OTL@9 ߙA b8==hq(wm0f3sss &.**2 6-̃===l6ӑx`I+\y7z-%kjj߿$;w?_ Vnj444ks BQo۶mZ>h$8y%p@p aT1d93Qb.Rd+eLD\DtR gZsϝ8q~9ihhh@Dxlt$9I1>ZF0LL&3J NRs:\VMGY~<60aI+.9az+aPvj bbhG ž۸4ǓdjZ&}n4444W–(˴! !JĆۙ `' BXLD]M44444_944444FCCCCCC  -l44444_beEz<:99iۡO"0Pd?H"I$) `ЄO$==]P(> TجVkx:;#&f2f0@0 QD$N8N%)פ|Zhhhhh>{aۿ`)q\@ @M$E$APN89w={fuMCCCC f79G ɀQFQa( ")$ 1 (|ܩP(4<ɼElV*V$I677 Zh>^`OJ! B!ǃa ǗAP0|^7 P(rc ~?p8(<b7" l`0HŒJ"EQi(r\qA8D"xufpp\pp0zN,+  p8 bD"@ ^=p8fA ~[LL7o6?f"l03QE!@P ) 1XaM6mܸZ<3g7YrPR;v,''_pᥗ^zgKJJV+ ǎ+++ NwƍY׋hGTTT"<̱c6lyj&44{z{{CPLL]wUSSSPPfGFF{1պ䬟'?ASSS͓G.oٲe˖-+jiit|>_֖8/|j{پ}H$B$LOO_pիE ddd:t ))i|7O8|e۶mj=~Q}gj̣>@>RvڵqƬ;_z>99?Z CwwwKK˖-[wkv777t:$2@X@0@$0C (jhobb"kZ6GGG ׯ_,V JIIINN&aG6 sy. mtt-p\8::*6nm6ӧm6[vvvYYgϞEQt5aX,$I\.@@fK jzƍ஼^˗R6 z^#277'H(x2[ zN3 Rd2e2Ϗ8[d{{{sssBB¾}b1㳳]]].l6GG$ B6m0 BDT|A,C|T*e(R=0t ˍRnd2oGQT&ǣ(.,,E,,,( pDGy-ih>UIHHعsJ"IW^B2<ȊB!k.Zb{o~`^o^^ן|HghhS$&&MMM%%%|>p /b^^ގ;nt:VvFc0\u]?OI4 Ϳ#>maXfggBB333 \.WAќ["}7n4'e(` LbB Q &A $f[͛~xl1brss[[[m6X,>xƍ#w񎎎yDRSSo߾T>oZӟb Le4nD"9p@uuunn.AO>tPIIB~233y˅ \.߽{Mx˗/;'xcǎ.V'|ŋ.'2gʕ+]]]$I}ݥ111$I|SN8կ~u,.\())Y~}II AQbo۶-666Rرc. T{͛7kڟ.lǏ777H$Çb \.{Ϸ"mmmGB(v޽e˖2$;::^~zqQ@_diu]6l(k[*b`ޒJXb d2cccW'&&~r`FƿF׿4yiD~䌐<iii"(XX,޼ys8nllLJJ,++X,cS~ZZNftl(NHHHKKH$#W,:833C Q=9ԇmGH.{n"GR#G0 D.n777D"38_TTv>>a4(((|6E&i4 6|>y Db1XT::: GnRRR 555 ׯ_QBpOplbZlْ,@0 KNN >ٳj:&&&777$7P]Ml6޽{srrsr JZ8===|>?''l6UWWq8٦&ϗӧ>644LMMUUU(J-RgϞȷ x2dP`0(ʸ8h41 ( |#c44c"(.\hllt8III \. DTƂxH舏/++[-b%&&ɹ]vE}`HfLV[[kx ;u\۶mbt_EE`OO϶m&''?00D끂vb@^ ͆axUJQN߷oT*ZϟOOO< |>xllgݺun~? HŎ 0`jԇ X qqw IDATt:yK.-Mm۶EN,斔GFFF.\WPPhfff\ѱgЃ (&& ðEQค@5ta$+**V.h4ÇcfZGFFPCyއz|uuu=6m|h||]wݕ555uĉSN{zf+,,$bllԩS<СC+ާZr\PDFˌFc$R|>AT{0699N,s8? ]v+Μ9===n۶-33ohXD1 KLL<|p8?>p8rssbic44 ===.\p8iiixz H$Vaغu4.pt:NWQQQ]]%:4L:}ERCP(r`}>E"ц N>׿b3bEd 6x< Z-A@ ~qFGGaXpdH)8}ӦM<$IVr&8rs#IIIEEEW^pxddDR[n5a#l6߸qիjlnX.n7YkkŋAc BN3??ȥK~iH$:vXee%Isss &DQIR8( CD$EMeH pjllB"GLB8x`eeT*=~ dk9}E"Zׯ_U%Pݿqq1kjj@EQ p_m Aaa+%7 Bp8A(L&<W,...))0csssPr?p~kkk[Y{($I ~~zR955ӣsss#t:FJ8KJJ1 koo_,ω,l6l6L؈ q:Pd0 Lue v;qqq ADl^ŋ2l=m2R9V}bbb˖-)))  b+`prrTPHRLfZf-TV^t)'''???k0;))d euww@ pňk4kjjd2juFF( g?ٞ={ˆP`@@`PaXqbΝO=Tbb'.l鞝}}}~tW3Y;l68?^REy@ Fha__]/l6%^f1(bVWW7??xL&ӧ RSSwy鶶61 [8NFFFllv݋+M6 J]]]~ruuuO ڪ֭[wwL _׻"f333ccc~?e]`b@}}k655vAMJJ/hۃ`___WWRX׮]{wAVVVFFƊQW_}oh|'&&r%$$VktSSS0LKKWf0RTӵ\.0LfI9NNNѣG:`Q a|EݞZk3v??~ĉǏKJJ<O$VFGGoV+W&&&l$B@#0 "?!I AjWpQآdEܜ]vMLL?`NxZm9l6`0̂`dk׮uww'$$8p`yZTUU555&$$߿j֭=== d2 Z.]jjjb0ؙߴiS*))x뭷b1S [j5557nR(OKK;tD]]][[[?299yM].͛9a` (ZTT4<<| P \FFF;wF 0 3R"Tgggoo/x(ڳgOd+WbjmllJ#G"cfyzz0\.Wcc덏7 iUUU{t\\.l6d{9w\[[[(0l֭ѳ"#!Ϗ ~&>Ar%$$dgg1oLc0`j2X$}dddvv6&&,+h|>߭CW^znFX &a ip"&(W_c(R./q5"YL&355u,+%%%rDxnݺŋ Q;w8q#66zڵ ٰapd2YŪFQT׷8P((E"Qff& mmm :jbl|ASRRju$&6[ EEEw}ɓ'[ZZ%IAAAMMMd2 2L`ܮȑ#` f06m .\a4hRIOOU3`!ML坝aFSTTyf 9 ??j&&&ٳ'Ҽ Q7`68NLLLLLDNINN–IWT{ٿ\-HXT(+\? 'N}X,NOO=G)))xԜJdKJJR*ۗ?}~_Gmhh82 f06xؽ{%G-p8"+xTœZ"Lbbb0\,2ZV!Yyӗ_|xdf2l6ぉ vAfs\ap8B<qokBm6  7b7svL hjx X̩L1Gh>+^/X X,D( 85ayKRBy&)1@ WD_AP xEI, H `dyr\LfW0>t#z/qaEQ0q 8z|>Nj %Gg}_ޱc𰾿5m,e1?"Up lj0>m =~U^XXxů}k?Opfyyywu5 FH$z7^J(8H!" `("I ԫ/\áP(JJJ.$AN Zhhhhh>r:`5jX$n뚆3p8 \.Ɍl[~f0\n( tE|F$ADPP  ?.䖦 HMCCCC Ȋ\1% M'l^,.l9k lQNb r׾pAz6{OpfJx4LJ˳6 Jr.$pd([ EQJ"Av|~@ AJta{s|ꀻc;>?߁lgǼx'^y啿{<~j.]CRX__o|?Í7>wvv?~w- }g>0 .-|Q=6*s0 ")|an[8LDi\+m6[[[N2IxL@FFFZZZٓI}/I'O A.+JKK5 ث^R>|8))p͛7u:]EED"O:#Gdm~z >"(??xxvs_)1>>>>>n7A|>?''(~{U$Cq;p^ghhtD"FSQQ!Lf+͍vttlڴrqhv455UTT/FL&Skkd B2$''NO^p8bX x޶_WbX.|y͗^z)776N8qR Nj|EAi_ZZyϿY믇B۷/6`~]|pKZ"dee(&N(?{od2#@x_|["l6̙3z>K$`0(j߰;vdee$ɞW_}v{NRX, l7nl6?$Inݺ5##_裏FWR\-W& #90@,c"gA6+xŋe2C=а{{\.l6?}}}@jl}?я(r8㯿\.߾}gtww˹wVER/b8##n dvE"ܜ`PTVLP = J}>OղillJJtE|~FFTWW׺uT*̤c?B Irffܹsϟߺu'c~J/yW1 lK:@+#G9sի]_|~裏zϷ_bb@ X{ゥ)p?jOtΝ B"%% 0h4ͱlV[VV&J#=KA<////###..ɓΝ Bz[[:rHdLf@~pa=85228N~~~FF9yÇNNV|'fgg@UUUZ$[Tfdd(S0 ;r 3337oNOOaYMOOG$ x%%% ET788;iiiFɐ.Jr`NNNFdbbɓIIIgffEQRYQQ  &77W,nS\\ 8};+ܵkWuuujj*4N>Zr M&Ӓk޽.knnɓ˿qvvvvvvϞ=֭dA\|C<ORuuu$i.jSSS=I"p|k(ǫT~@ӃaXFFN[\fi('A|bQDA=ynLFr ۧ AR6ASSS͟xlAgggryLL Ar [ZZn޼zA$l6:ta4 I``(ƚՄ A&`0p8SbpOZZd:tPDH^:00t:y<(G}}}###0 Xtϻ\. x< o޼aXSSSWW@ IDAT شi NNN vw"h0H4 cCCC& YYYmnns\8왙m۶E4Fimm)--#"~://dyɓ'V+ sÆ @fs]]]{{N0LV|[_nӧ~?Rt7x#666))7x#11fsOO{ƶ\(*&&&T*t X+644޽[әLp8Glq*$%%X_7nLJJ'"-- ErZ Aкu tll" APmmmmm-֖gQaJ%h4sssz~]`۹sgyy˗Q*?!qŋ_jRd2222rss;::|~KKH$Z~}dP-bUA;::  ~HzzzAAAGGGllL&kkkp8UUU BVK02;?33S&&ZPf}`dׯZYYY###Fqݺu Ahccc\\ZXX[΂dHɴ/JZTg:ΊȑK./F)))IHH P)++ n&''!(={Ǐ_tf8b۳lpxݺu%%%qqqVvtk(xŤ|~\\\jjf Rimm-05Y,օ 6lPZZ 3;;{X,YAyC'DevCA$H36k{rsp"H(ni]( A1;Վ8qu(VUUSOv_tdiF#,8tb)n A}hh:P( fggGGGFtnqdZ|0 %R׿L&S.4蒒KM6}-\!z=lÑe&;PwdǏ{^?22bٶm1fؘf~6h d{:+`H HTTT$ Īh\;[QQo_|y۶m_t-66εk\.\.GQᬘDZp̙ܜq7 JUUUuuu===wV;󄄄Gy$--$I Ξ=+ ?YaCD,WTTuww޽[, ]WW֬H,nq"pm,``3:/IOuŁPcǶac  B!6x(Dwl۷oo}EE"QyyyOO|ʋ<׿A&&&}>-U@ ###990=;;s1Levww(**ڿzz:;} / }OA=B'N0LEEEqqq8ۿm5 ~0ot/DT0 b^|AgΜ7d2oaÆgylT*]͋j_NNΓO>9444::\ZZcXKL&S" @Đf` iE>"t0 ŝ,0q #KWK?"J( ݸH${GGu̝^4FuTQ/H4a$-8kNxlɉˉz7qINzS vl` X5HBHB!ͨMwnl7"I|Нgy|>ymv钒GyEv<I7x4=z믿N$c+8-[\M#JEt<8 H${8[t˖-;}%KvI($yi4K.Zpjj6WLhB::vn1D-T((TKaxHTQ-̳)i2222L&Svvv___4Ͽٙb#)d,B!tɡ$LLL' `H vRFf3իWݻAjSN͛7f IFN`0xիW\2$ T'N"pny"(eeeeeeE\Sj#*s7əlkk+((HOOGQ4##p%;9\.W<Rnu?T:Ć\N:U\\T*ggg#H0ftfys Jf%kfǁpvvW_e|jF"`0gZA8{l4l6`0HZκu>MC<D"*jhhp%oP(TYYjA˲`.l _R L]]fKul۶mɒ%YYY<X\m۶|W頶X,FWTLtEkWWW_1L@Z/YEQy .:'&&zzzREsrrN8USS(; ,Er766 > 8p k4pklllk{z~SLVv:G"jKz&JJJ~ 6̯8C% ^|ܹ_|1++iرccccԩSΝcYh4ALf̌]<IDK‘Hɓ3330?͛SO~{S@fggy/կ;wvuu! rI3uΝVN]Υizƍmmm7%Ik*LOOOyrk^0d׭[\)--[o  ^zYOOI?-7o۵k3K.;}IQTKKˁ~ X,|oQQG&>nvvb YYY999Yj~j+WHmmm&uvv}ݩGV\e˖O2o޼P(TPPP^^ZrFy߰,ϟ_ZZϛ7on/ugϞ'NnX[[{=$#M&S4 -ڵk/_زOXN$<Iկ@W^ye۶mfk֬ijj'|DxW^yeG?c@呍7|&uAT$EQKoXJKKd•G@DZ@EHa5/SPG$n1ph,//߸qF://fժU;??pq\Ph.z衇sgggAv}>Wdcc#EQ333(jZPT*333g`h)\zfKnnn.Onb`Yo۳Z\pzzz}}}9g}`0F- piNdccF)..FQl6geeeff&-l6۪U Q*V***J~~:ȧLApBj/^O/_<aϧinOYUUӟt޼yWлk[+++KZl aILFl 6LMMb1 KJJ.a3K,ٱcٳgBԝwyqcYYawqdXzdd ##^噙D"Y$q722v-[g}y뭷Tչۢh[o5Z ph4fWTTPU^^ *d H2A;P0gMM(*j޼yV yE]rL%ټ~p9ovbh4S lO<%~KYuj,OYuugXy$==~ee/Y<˭VkZZZ8 dA=O< ]]]& [^+([RRj@ rD:22ޞO_yxb?|49sIJjgϞ׿>j) @"rTDQ 333$ITB Dww7P#Aj5p]IZ_o})Sq:ZZZZEE_\h4l6Pnjj ,rEFQ322.:pIh6LEEEiiiGu@biffA#/0Ѕ@ ?aP( @B`  @ (l@a@  @|"D"rϕ 9q(J)ʫ @ W ܕXP9ܖܸeYe06PR<JM¾@ ȗ,lPv4q0^/ؤaQ= E܂QA8ma_C ˷"Fepz@E^EB1LN,"Xd8˲**++KPFh|&$Y9x=N״C**B&R4% ;55%IR`D">ei!L+>h0tM9y>oP,-6YEQy><3t)"R""H A"b88,Y.۝-u($jz=EQ~?K/k׮M ۂ = |Ea&j2|\. gm`Bt vc㦧9S(E%_bYt+ 󳳳\<#e AQDcBj9 EI$ pJnEt&{뿀) պru4tbyς@ _?~__o]͛=S֦b؍It=3ɗN͛׬Yc!W\cy>lB# IeR˱* MǢX$*(tE0Gj,M6eddo~}}1bCn$ ֶgϞ^7Gx<><<\]]pB`|D"ݻw[,(lIr:NðIL$cYvtt499&B<׈"-&DQԹVeIbbL< < $0HJE1Ljjj"HOOϻnذ!;;;Uh~,yzB"\NZNOO/8<NaYj^W8gyٱcիWSNIpϟ?h4zg}?1CMLLk===>3_ IDAT/''g͚5v`(?EرcGV~˖-cƱe˖X,FtCCCkk… -ōٵk׮]57mڔV-W_}kz;˲fؙI&&&dYeٳ#l)BhQTdB4Wʠ8ACEI! I)IF=*RD"p88I]9~(UUUxڵ{U9h G,XzCЮ]vh333uСH$RZZj=ػwի322xq~…`wttt9MP,^833aa; .)l}nnnvvݻ(l (,vvv"Y/9WXceq DDq0*IxK0\#|g8 qAR8q<wuu h"Vlp?&//oll죏>^~}]97AlիW{/n9<:?~Ϟ=gΜ(^OKDYYYAAI}}}8r#3==ꫯ|ͅvI ~̙3'N~0GׁD"q9a222flBV5^peaSTKD*IZT*$h$F~# LqU0\+ZI\ch4WìmR+8CQf(Ƥ Y mj$`ZM4 aP(z=D!cs>좢"A:::Ny+.hƆ`x\ \I&&\qVHTs:GRQjZ `6I<}o9IKK9qZnhh`Y{ppli4h4:88hgff=7fsnnӧFceeѣGnwIIs=WQQSOse윜R944t:@  1 ;|媨#ȍLYY͛7?<_㦦pnn|:+IR8[`wީhpE1l۶ 5q6NFID)R"O9FY>&`0 J+Qh]$+**dY$ tܹ`A5ǠR,Yt:?Y,כ%%%Wݺu޽{=hܰaT"77ɓ*>333Ǽe 2 vfƍV+; KKKS~ 222~={ $P(WQQ5/z'&&˫Y sppp||<9,:t誄 h&;8.E G>/\.(œn3MJlU* jLiRTTT*!"--l6H 6jz۶m.`0̟?1fv7ݻ|dl...Nƚ撒p$ɂr]QQIyl`ʔ$ݞ(..^nݎ;N:R.\h0JKKll2&'' ,hmmB _E{BLtII(`0tMs|:FxEEnOvݺ<b1BQXXLAիW>O< ȑ#GNٌ湅-Ojhh޾e˖E!l###;w\|96h4 }y^$ ZViEQY&`a$i6$IbeAD"HaIӓ;]CP$0R "HL= 0H$X,96frF&zD"t:S7G1k4fџ,:d!4A<t:IW$QBq\PF2H,⢬eNFx Tq^゘q$ HCh!pHa۱c[oe˖OsZ`nU>֮]ؘ ӧOLMM,k6W\bŊjB!B8gә|f1:|p___,{7Cc׮]v;l'=xoC=,ñy롇JN;::&''J-ܢǓO>t:AZf͚y}911y汱1?lݺ=P5srrZZZV^mٮ`IY ɨ" / dD@0!YR"+a(A( LmT &|ΝcS;Β9t4LKdjjjvvv޼y8XPqT^^~'|dɒKpzz+VQ\\|@!1 }v Ʋ>rݻO:z~aΝ;.kppjS H"8w\~~ҥK}Fg.lz~͚5vo۶-indzi&|0y~ӦMW6E0Q8EYF!DIDP\D1VD,"B(T FIZ/!8v$!  i`cCPVVV C!IRk4 N,CV$P(x Z`0( xw\*bh4 Dq||<bכRA(bVך~.Adgg4 RI4L[Y-L; a999$bYLݫo'N=zСC6555qFsrܽ{wSSӝwYYY911q?555V흜Gh4z̙'Oŋ/X Uz)I\.ב#G~_$m#wС_*..Ǐߵkׇ~r%a%E"4&!1VabyB1JB>QeTz %q5GdVJv<3/Bmm>T*o߾{_|19طoP(aXzzo&O>|?3*fmٲhrr7윜4˗/[ j}gzm۶ yhNOO߲eKMMM x饗cMӍ .[}Is=cX~_͟?? g?D"UUU]]]H$==klls.n믿r233Y8^jllC rrʦ&&FQ׃p<\~… 5MEEE(裏氢֯_h0.^eiT*333 Rx5 9s_z~_ Bmmm'N8{)((\KP0,Ehljj8u WZc$DF$$CQH R# +I"ɲ$ G q GeE'|W4u\ϟW*O...'uQXXx-0 ;vLӭ_~ժU@olh4;vx<vyjl$?~+W@T*Ւ%KG;w])+Vx^03ϋj^kooW(@Ο?6==]WWGQTgggooo,rCa4O>$deeUWW%40I(*==8fL&C%={ht\$I*4 L&ڵkoQVV&b(jii7o$IDj(ϟ?rVŋvF́@ TQH$ 5H"b!ɲ,"!(^QpHyPJFI E$TxX9! /Ӓ$Wc &ɓ'F#0F$I .Ue V+qnַ~7 ;wnҎ9~ݻ+**vG}ٹ~db8|iiie˖~xѢE p?677Us\k4<FO55%^_~}~~~"x<{oQQ $u}-[ t^Цozz:9UUUOI"~ZJu56$Q>d)$H+r$%DLB yY$YD%IH"de1D骄Ma>sʕ+n@cc#AF 333ǎO\.zsnKTRq\$1oijjizϟ߳gd !I3LOO?RTղ,;I=w=SWWP(fNr#s/[̙3[nh0<< o~ 8q߯i,544XVzO:e6y7kZBRz.V{#s00i/oz}kkkzzBZv]P\)Ǹ7ḌȲ( 3/"",\$b$ tEz244v C^^EQΝ;{, IWd4Z fJ#> $v/^d2ɲIgܹ;馛I$*^/c&6(*ׂ8ADaņ!k`$a0#Xi @d2 xrf~h ,˲,9RqZ]pj5 &ivveee8;Ñb^ɴnݺ9xKKK Z\<Y67lb ".h|eaCy S~EQ`y% ETBHQ"-JWkNJl2g+..~뭷L&SII P)IxfffQQb*Jx<@ -KnnnOOFilldYhY)CCCغ{_~y˖-k0Ν;p`H&t̳gϦWWWa;ueddD"CEѤHVϛ7ttt={̙3v=5pxxxmAl6l`&77,LLL?~i||̙3IQQ~e͛7}ݏ=j[̨UVtwyauavq7;; "e>\^+!R2ˆg("2"˒$H(0T2+dZb㹹/jJPN"U*Ղ n[oi5f)((hll!999zkYYڵkGGGw'j;'NsWv?<<O< Hgg Hk %K\84?G0 ` p8 #`@y$(y+X4MkZZ8<S/|>Pp$F`yPvyKJ8NJ#_7x/˒%Kfffo?s*d2T*(& 0 ˲/@\h$ (jDp8qAj"t4ϗH$DQL E%I8ANqFQT^<J&: ,({ 'S*:.M?z^W$p~ x %/ R=/w7JŞYc tb}9Gs=FzEQW/}~ד۷#R__vZ9E{1nlX-[a(lr –Ʋ`q-Ng玽RSS&e`"A ??>FZ'b@)gإr¦h^)RVukh8Xls؁rVj\0 @ P @ 6@A  @ P @ 6@A %Jj@ ?@ –Q]] @ F$h@ _Q`@A rB.7lw=tƍ7Kv`o VK!2˲X,q'2 AjFT*o䇇)>wCC%M)RiZ#Q*z^׃$h4fiZũx< 8+ ^R@$HDQa8EqZJqϽE O>+*l,<D@ffZUQy|Addd\bQ(h44}'"[E՚L`0DfjLMME"*]ZZ/`dd},K.]n6KqD{{{GGd0ظtҲ//>cG(++~p'ߕnذdfG~jkk/SNh6lX,9`0h4kkkW\Y^^n4/X,_ĉmذLn$IӎO>d۶m<ŋ/h &''_y啴'px֭YYYK,뮻4 EQBw}wzz M6\.𪻻+++[bwߝ<~cjEi4+V,Yh%蘞~v{NN%26$?~߾}p8333//L98nxxnxVV LLL]4888(IRiii(Rղ,sΝ9sJEIUdYXѣ<ϯ^gtt4===;;[ea"-%M$˥T*[ZZ@h4zqAZZZwٳgw @7&m \koox<ճ^pfeWeggzO>aj ɲj5a,:NOO˲qFEAH$8ϟ?aI$ ,HOOJKoie'&&$IIM$ B<8T*FB4 xJiZqSSS)X,|hz^V+ 0<  iZNGQ8$IX,,+2Iz>H8D"1Liii(FQA$ M$I^YA9ittaGF*B4B0JP(tQ8T~ p8:::>B EQKJJ4|w;sXGGǮ]a 4JB$|>^O4EQF@3B!38|wIvgffb i5199{np]Г,8qMe#o޷o_AAASSSssN:o߾| lBjmmڳgF)--ݴiSaa!pڵnnnz,766ZFպ^xATvvvA^nݺ {陙NwM74-I˲'NؿА$I6mݺugΜywfgg{zz}x<~ر?pllLevmւ%4YΩSnwZZg955599ۋ ȦMᶶ;wNOO+M6͛7\ϟR>l4#HWWWAA켠1A(cǎp8 WVVjZA`FPv+W";;> y‹UV-YkrK]]]VVT{{'|cUUUĉ;vp8Ǚo!77AcǎmٲVw7zE۷8;瞻$chhhŊ$IVWW{^Ht,]T z9U7T:tjVUU ^=YVt9ss\,{CQnzag<|p__O?vyQáj+++o`3 s̙o###hToذ)//.Av\:7T*UXFGG CQQQ<gYѣ(p̟??;;;-- p}Q~~VC^_QQ`GGVp8EQ,+++7A40 bA8>66FQԪUvؑ|rA9~XZZZVV,vvv^7''$ɮ`0x),,liiٱcGFFXL$ǏjEюI.\8UWW'ŋ{<5k\RxzUP$2_p={AVnݺY0`QrͲV^H$D<iAJr||<---##clllffF;@!h"QDoo/@؀ _9--n;[t:A.YEQyߗeYǣVM&afYVn`H$fffH$I X,vš޼y[`EQ4~饗GD"RܰaÎ;Aw&M_ǻ=v ;YCq쀝Q @jTU+szZr*&@),@'$v/cOYN/9f<37{s48|>t:Blh4%%%/_بbtٳ`ƜN@ hlld2KKKaC [8{6C;>|f--- >yD"aU@T9r׮]ϟ]jkkaq_h%쬫3 eee =vZF===8pl6|_ ڡ!æz{{c^7 Dbffof555d2t:=pɤVǁ)SfZ0lATVVu{?RRÐݻw9j/G&GFFrNHg>}:JA~ͪB0LsssF@.t:#񹹹Ϝ9C~?kkkv1plT*۸1t$!BpID"tRR-,,v^/666qYYX, VVVr9wܡqǣhh,MuuKnw<t(Zx.aX",++q^À.n??~! a}}}7o?|JZ]]=ma2 f=}0,ɶ~8?8Ii믿~fwfAt BNsllݻ</LFMH~3Ay s#LluϞ=ӨD"]d2hqXbo[ V***X, u3$) qqʦXbX:N&.%%%իḄ>Rܿ:D" I\YYQ^^~qؠBvn6As-4|Ae2RT/>|V`w_@ † tc@ <`q\PR1 jL ܸ@ ); ѣW^e0--- b~~>oH$qY|>?\~ٳ(@HjЧ0%K$?;:{lrZ$IR \6?JIR0\.7NLC$v 1 q>zau5$_Nj"Iŋ/^ =!@M:33SUU_!z/nz* ƒH$$ ǦV_z >|ߏaEQ %r LMM;Ԥ.Դ.,,vAH^@ hmm}g0,JiX|>˗bqSSSCC( 悺%dz.+oFZZZ|D"1&ebbn̤Rp8аo߾]:Mw?x>텎\<ԩSWڷo͛7IBH$gZ-XVFj!j\.X,EQR-].>Puƍq&駟t:n߾=;;dfgg 駟 ^?uPTǎs:}:22x^;/EQп<u\omcVBa^@nlm6MpE`B!B&4ZiiiݫBB( HAOsr2!> HçXM]e˖Ozv98@F@VvU#FuT/B@zX ;YPX@B>r|ĉ^qz/󏉾;0*@JJX`7G/|I^=Wp*tu7p8/+@pu;k3s*hsJ*Q=[f?޳EtMc4D:E;>));+ot:f048*-VWKQ @E/sijom[E$Lmltae#SQO-$ͨ%^JΘJHB@PL},Q3~S^ZRX*0/ K8e;ŒγxX٫wS ~ :ͤ2Z+sۮFdE9`٢E7nΦ|9c_~/-q 5+ןkA|ۦᗧ_9{5>Ca#+;q|QsIv>},:]Y!]_ D5ZZZƳf *" |ү:qIKBC#fv߉LFk/?,ќؗU .Tg2?{ڛfZɘ/lo@*ic'~]'`2q':[U B:*te0Sĕ7`0;0yQVVaõ}|'oٴ~KhѢBr9+U:U3h@CUزƫ...ݸi ^>t3_G4ƘaV Vü4^3Oݿ1  _BlU)XΑļ?&rImA8HtBA~CghժswNFEMUx&?_W}p-Z Zӭmlc>Zpe TYvSp͠y͸:u_7fpr!h˪jgRNӷ_qvY\v;Vpt/K=~;w'ت0SS?ܿw3B<p[aIZi+#Zk1yyy5 7񘛇>2nܣѾgM7ԭx̣rͣ٪Sр.Ğ_q E];r<%²Cpt"#hGDO>a2Uׯ[ib)T᧼ܯ ׆Lx xY󖖽zP 8wL=JP%.[>wL&SOOO];8 ~xDPQPWnA3Z_}# dF| ( B   ( Ru!gYhtvk uFC.i+W4Gnnf ev͋vlkШK_ϓ LGWĶr%f:zK?I- {QD~4%{Eo/zskcZY".eLkl u秭'o,:_8Wq4I+0W~R.l/m%as^}kogNGH"R80hZ(C xSDX Hm-/ Ѡ 1U4WE^Շk.> ?> ]bsnٚVLwɖI˻c6M"R/@Wu^ޯ[҃o?A^flS7k οt^{B E Iz;;wxYv{( H RyOyHO5{>{#?1FAAPoA,\Ph-Zi!GF*\gݑ`8\TX S*$LMM4"du]=r\#TucUJoo|3, kᓗX)[}P^2baIjjdc>d|zx~.?c|[+~ٶK1~| a)ytr3|bfߌwbv"d'}OLyޣ|HuMӁ~[Fq0g4DJRSouaNyՠ5J]3|NXj yKLJ̨%6]}cRO.RyKt4DtR|09MG[Hʹ#~#qFCʩAZGPF7K'%Ar`~+l$I([JT^VkGQiNw;$wO4l+\]}7גojdmD;t,EFq&g,5`Ѡv JӲSoY^'g3KuE\-7D yWu\"VZs#npJne=O6ޚ qeM) /VjhAAed[X.puv4Uvem5Tv<^?J=`Pj~vL[b㒠y}j-^:iթeLpc؄5:~l(E2$OU=jfON ;6ϠFξʜB ieKʋz]zd;, jOpa\a6t‹U2xѽmGʶ:2|JPLy$=dڱ6D2DM]4bkB/ G/]#h.k| ***]\ px!HE+MwA ~AL; EEE8ta B   ( B   ( B   (Hg uiyVPPp1^^hy&lo [,O {,55#0006.ԢC*]vpp=3Fo>bM]d),,3fLrrrPP v:4!(-- ჿ3}o!58H:6[@fEZ<{5y*B +))˘GiT?P[ɖ";&-g1c:gÇ@ttcz=gΜESҔJ/Ċm(9X %6%*BԕSeL#4EzNGi!'[%RS'8κV,:9'ON6۷7n055塰,,,##{ϟ?5&jdNW$1Lć\͙fŝ"lD&Lv6-[i1* L?"LASL˘T w IKER5-՟RRPFgY#iӕ<˯և⏱9VKy^R=TmqH6[ԖX5bFZD"FSeL#L6JM6nх]l B ҂pggg%O<Gpf]WV?yl(]$[KrJ%VثQȤd=ŔtM(dgNl8Ƽh,rrrZn-P,Z#@3A +hy ܏AAPA!@AAPA!@AAPA!@D* ^Fz^elǹk!M\'TkDI~"Ұ-,UF\Ri3z&\GY;ԏn? ~⹇k?NJ\zZwnsTf>Rީ>&pH! oڳP+0WۊOHuf".N0l7t!vG?} ^ku{?=cƊ:09J1m&{ݣ9~n-qx!M@\7Y3Ȓc+5_nCP(!(:2aUL7.ԵmjΠ.g" b}#&p7?}hpT!(Ҭ5TS,g8Kt* Nm%3N}ÊxYu[2B SHvX\6HZ;PQzІ]\9 iGPdyJd /3Q{0;DMtb?Zh1fg^2ÔN߶upÊeP6#|%B&h6:+!ݢyu:̱mtbfڍ0q < ip԰7 |seZv}YW}Uj[&;uAJ=1 B   ^#r8:FkJ!>4R!r?w }B 4R! "&0555CgϞ:Houk)[39jYittj{qX} jpA4f!@ cǎ}@ 8=_܊}U"T72Z&U&h5_4V)lÀk_Fh f+m}g. o2HQcƌINN rqqNQ[L y*)5gwUKLGm_U q(0cd|zx~.?GHJKKůCCC?|?c Nӡ?gLUUCq> fhvߠNldgjc<(_'݈8'O;gV-Xl;:w9ˢӲo1G^Y|>,C.>{ȿ nF!mԗ%/f cbuI]TH.jkS۱s^cи}uwTj8|[ZZDGG;vwsQxyr>1zr^Hm[nx^9Oi;Rǧ_;Cyozh y|y[0m0q>+.^שVkS'8 ɃouaNPJ-Hâ訸菝us`'O6m۷ooܸajj*tQȺXIW%v,{)Lc`;'3GV!GW8/7G9VުޗWį^S>ˆads+>h4f^n&_zs@yqe2nPXţ!7MTu(f@GGǏݻXh<^o}X}@_lۄTQ U(xm˯w- Yԍ* Ui4e RVkL(yGvƚ Mb`aaaӧOWf0s1弊/rZ`57v Z[?.יq=[v e!j݌,FM6nх])!}|wv*1NP&Qǖ &H;;;(^w͌ђe~bjΕ+ OU=jfONosuVvf~n>}gٺgQ7jމ ǘwp|(HM=C._2HFlccKiBkw QS/''u kW m,\g{Th8ƐF@ќ]\׮ TTTBu7 C 5,-ZiR bphaQQv+4,p?APA!@AAPA!@AAPA!@AI AG% 8&qEmz NU9{4&:=NdצORݘݓ'R ey&TZ.C6E[UvF 3q!?_ߨkn;cPaZ*+60vJݎ>*tx g)A9Sν_㱏?v}GA[ʨm?]^bͮďK9|qԦw%y{EYR劌4( HA,ny:SUCЍǣeoײ.f -dmW= ?`xEǓfؽb m'Q7گMi͙2&qyKR߅ږgFxu5n#g)~ Ō"QM*t>cHS3\X]g?x2e\(k؋63]~Y@DŽ|2^/H](H-dm ކtVh tiC nE^=^lMHI<3 Lt!5H=4R!r?w }B}iB@DLU!ajjj8!7.ttdM؀ߠ ^QjR5@ O@M]] P4R剳`T)cl9LH`5tt: 9(~{~\窆UrAR , nBJ_=# uz7drtyj>xϗ=`04 -K,oGgo,Ns ;ڜei7 ;'[X:|S VM NIɡ/E)1c:g#Msw轀|B2W0߳1Ъˀ+ ǔ8iV,L3hK+H GWv67 uHI1uWb):M꒑vE;[ _w.u!=^'@!S]Q'6wd_V\ܼY. [&KK@hnͩPf>up윭e5Su??f>O_'^Q>8Oaїb^?>5S.?xCg|yfq}3 3+ N'Ƀ' YqB<9 q6D)un;H7MMrRQ88pT5]Ic _}ZxcuZk')nɓ:t#y7)O>mEI]2.فaFܝt&u! wm7BCKx(Ը1ogŽp? jz*EEmz NUQT⢢?vf<K[OHØj-ۻ ٸ{SmUEv,{)Lc`;'3<WQ^nkq!?_ߨkn;t$)zwuŚ]s?rxXӍHe߰Qv<6,ln ν_㱏?v}GIc;)6M~,l{0wïj2BW%:.0b_mFv_]mYcWtWtBhٱ JN7Vtq@.Ʒ(+O_v`V tAv',KTZT y->]PF7VN5%>YmZ_ĉB*Mz0&j}@O/\gd4^<[>}/;Fgq5ZM0w;n3}U4GF/lt7ohLL."w|Bi@g2LzAOҶTi5+ 4eF"fWěT*W<PZknNs)|!YKD)-U'ʩ}Bs74P[ɞ+ەﴪ)ӓML2 4%[)D}ITh% nWXAqrCaFz.iWӪbfWlvfPO-$Q`#%a8 {`"iJEEE^D}s* '7wS SI$UwZU&bg T^TհRjI|W\֔oD)Ӵcg@jFJUK YS*R/jXJ)C$Q+Vkt7"{ՔiZӱ3>k`y&Femuxkm4h;0#@3iX ~ B   ( B   ( B   .uM ROTxEMKڎ_3T5\>&Ey]&>{l?vˑ!v8?#E)rml\?swOfї.住^{pDXH}47W6/۱YkC.}=O&H_5e)˰m'bХc{c6Kπm4`B :Y-VDUZFl204*[~Cۚ M&U%#$F587D GNgOS#] ]ӮOBM >_1(е%F!_9:IS▍U*-#ōO7'D 50\uƊKR<]t(&1+ػ#g(ɣ ?~i i*kʰJ:Aɣ٭%@iuH5Epg0WV$ͤt{=͢)as`QwcLXd"ڼcqT Q+;#H$}@gT~bwszwhk*(]r=xYfғ4ԕ kG斝+C ikXՇk.> ?> Yh1 w ,@Og{Ⱥ-B Sle=hîWOcr~Fڽ98u9>8S'+BPb+=Fn9AN߶ug_h m3bWB/dnls+\زYv#Ll\C#f<=uH(onGN=#cGQ :,6~0ډڛvreLۋ iXvJS~ H0AEnP^\A ( 5.ph4b#H#.y{`0]\ .*,G)A_&uٳcBV\:z[[zWj @ qq:W[cv1Eϲv>y߉ έY./Ij@+3Yг lB Kaa1c\\\3dGfI#Oy9b: Y뼵gy|hXu򭽾ΛF.ס>|1c[y6m'G-ٵꓔg7fV0g4Q>O_'^Q>2Փ¤+[71H;8')7QT[el-BV$!,ev'Gzj55u)sVvmyNr  V]pʡ}q9Z=D]7axWW|\R8BnSQ@+Waz: 4L%[_:ǖj-LߞMaԏ^uCZ8SǴ#ÿe_nk?"sjߝ9,Ȭ lB0~ݻ?T&"XnM)38F*p˧Eqz,&>^ce5ܦEu+19Mټ?=&4 w IKEu:JWoXB $*SWVka%&!1$H;T(Tdgy.ڪ$zJy^]g"->}2wnf&٩7,/5Z[AtC"NJ,:;?Gn]>T2mw o[ՠ>*f܈%jYSX= x2ߝݽ}F̦j*JvdN?p ]t4cK 5?{MBDZlbbL~vݷK~QU?h6 4-d;W~$"çu0n˔9]bib|'')4[иKluǃQ#a$iG;!sVaEPۧؒi51IqQQA+C+wiݺ8bkB업)&z^pGO"hce!Fsvq]&'fHEE+u/\rcz#5,-ZiR bpu5Q9ATa"H#@AAPA!@AAPW@Ph PAt'IDAT!hH<.!DNg2x/_<ϯĞNliݾD!h0Ozნ:#Zu; Bٹ@nNN\\l>}L0 %xof0LLLJB=q޾=`;mDS5xBajʇ7igbl~w|ӧ}[RX&iie`0or߻;xG.M c} ͞;OJ ?U/I_<>40;;uk}슦EL:N:e`0D6PQQ;>>|"h+magR&A^`F`5M l6NF\eqQ+P|cOZ6ejxEu:.l6fkzU#hVpZu222R߿}aj~G{󻑓WZΞ?_}|rNNH:Q+{KVOR>DY}dqSHIyOٶȶWVߠH*c`x(IPt.eM'c_x @WB[){Slɤ=]oiʇS>7=Zq&LO4Zѻ+슎Oϩ5{9jPK5PZ}2Zw"i)J )o/wٗ˱|b?eRv|ϖLS60U3/@H7EMylMuo/DEI^ۜ| Q}%k.V)mmWCBj{7 G>G|=$@81HM'׆ uI aJէ$YT,KSHfڸu(dӵC 5(Z=@5EcpShe25$FZm>ǎ]=\B (|-N5l(CqQѯ۷.[62ug,J]ฎ,^8/|&)+`.s"3N:㧟4?VLPjzXucDM@@4ɯkn``6MI,;N/).ZTgT}o"*Sե~5W Ǐ8hGv!F[?ik@UC**S ?F4GB}?daѦT(ag1:^7'g]t B!ΝUUPȣ& ڑnS4bB!˕U3膆,[;O J"1 M--[;[]f( éDHcC2-,,l6ϯwtMެYF!@zPaq8/EAAPBorݐ,膌|ݐ&膌H\6tCn~tCFĠrSݐ.7!7m ݐJ!#B9alnȍڸ:C܁ktR\W( &?Quՠ.ewm]f};sU? YV˄TU8"q5Y^eCpWbų.ZI3PjX%5iNGߝL,G6 j+{b2 N?^!fINv6NO$Vn/7Y\,;Iަ sGujgagObDydmE act|2N}劻_kYb!:K5| jY^+փfߌwbvCڸHZu8i9v#wzOvqQ%;Q۔7B3Svs'b<b]#gڗ uXZ` : sj4%}ZvTe@#n0,&TY:C~qəv,I^|6kE? c#Kw;01S^=<="B˅S7sh_w@,ed,te|dϵ #n)l;-=ªK/9i"f%TZmf8~pI;[1|JaX,[gRSAg獹L_r- ##0Wc"f" $(ӛ<[ f DJZK$ qkN~d TS5_әtz~1~/7YRnᵝ uۘVTu>Y`MG))Ugnڎaדڰsʝ;{o&:g[%RԤe/7и>몷ab.czć yWu\"uVC6eq0~y1]=r}ބ_n/3 ҿwR&wnzC!iʘ9iOXECiiӧ\@LyF*ͬ'=TACL^iD b:SP ےyb} 4&7dϣQwoofunFLpc؄5:~l[WYw`Y c82}\Vf07jaZX:l>|rT7M ߱mβ+cjuU][8TGfX`wf*i -bG#\9>%&aT)b^>ҋ%:aSqCիO !o^yeeo Yʛ.+SMr)7o tCF Ƀn!7!#rS~$D7dDtA7d+ C2 膌 AAPA!@AAPA!@AAk r ||Fc0 T9ۧ5[iZoobfd2Q:sܜ>}`WR}011S6AH Ϟ>ܢMmɴf07hN5}K $D~gS#|98? @v^ nvB=3 B3qF   ( B:thyydbyyСCH$Ww@^lQdYeI>t^XaH[H ~ RtA縌FAә< 7er`h4ya #+;[+GZ^mN3 &CJ>sގ!W^@ 6fLU]/KJi&) ${ye..  &'JReW&&;;S ̀@wP!T*#*bi1— @ DG' E,/jiGTKIENDB`6wd_V\ܼY. [&KK@hnͩPf>up윭e5Su??f>O_'^Q>8Oaїb^?>5S.?xCg|yfq}3 3+ N'Ƀ' YqB<9 q6D)un;H7MMrRQ88pT5]Ic _}ZgMTP/web/reset.gif000064401651440000012000000006621161641352400147250ustar00darranstaff00003030200010GIF89a,333]]^^ettuvv w x y z z wwxy z | } ~  W!?,@,HP(L'jZ+d2K0d6zp4 f" H~ta  $uC ,,~},,$ a  ,# ,!Dzv,LCc7`$YdÇ#B ;gMTP/prototype000064401651440000012000000046221160433535000143240ustar00darranstaff00003030200010i pkginfo i postinstall d none bin 0755 root root d none share 0755 root root d none share/gmtp 0755 root root d none share/pixmaps 0755 root root d none share/applications 0755 root root d none share/gconf 0755 root root d none share/gconf/schemas 0755 root root d none share/locale 0755 root root d none share/locale/es 0755 root root d none share/locale/fr 0755 root root d none share/locale/it 0755 root root d none share/locale/da 0755 root root d none share/locale/de 0755 root root d none share/locale/es/LC_MESSAGES 0755 root root d none share/locale/fr/LC_MESSAGES 0755 root root d none share/locale/it/LC_MESSAGES 0755 root root d none share/locale/da/LC_MESSAGES 0755 root root d none share/locale/de/LC_MESSAGES 0755 root root f none bin/gmtp=gmtp 0755 root root f none share/gmtp/icon.png=images/icon.png 0644 root root f none share/gmtp/icon-16.png=images/icon-16.png 0644 root root f none share/gmtp/stock-about-16.png=images/stock-about-16.png 0644 root root f none share/gmtp/audio-x-mp3-playlist.png=images/audio-x-mp3-playlist.png 0644 root root f none share/gmtp/audio-x-mpeg.png=images/audio-x-mpeg.png 0644 root root f none share/gmtp/folder.png=images/folder.png 0644 root root f none share/gmtp/image-x-generic.png=images/image-x-generic.png 0644 root root f none share/gmtp/media-cdrom-audio.png=images/media-cdrom-audio.png 0644 root root f none share/gmtp/text-plain.png=images/text-plain.png 0644 root root f none share/gmtp/video-x-generic.png=images/video-x-generic.png 0644 root root f none share/gmtp/empty.png=images/empty.png 0644 root root f none share/gmtp/view-refresh.png=images/view-refresh.png 0644 root root f none share/gmtp/logo.png=images/logo.png 0644 root root f none share/gmtp/README=README 0644 root root f none share/gmtp/COPYING=COPYING 0644 root root f none share/gmtp/ChangeLog=ChangeLog 0644 root root f none share/gmtp/AUTHORS=AUTHORS 0644 root root f none share/gconf/schemas/gMTP.schemas=misc/gMTP.schemas 0644 root root f none share/pixmaps/gMTPicon.png=images/icon.png 0644 root root f none share/applications/gMTP.desktop=misc/gMTP.desktop 0644 root root f none share/locale/es/LC_MESSAGES/gmtp.mo=po/es.mo 0644 root root f none share/locale/fr/LC_MESSAGES/gmtp.mo=po/fr.mo 0644 root root f none share/locale/it/LC_MESSAGES/gmtp.mo=po/it.mo 0644 root root f none share/locale/da/LC_MESSAGES/gmtp.mo=po/da.mo 0644 root root f none share/locale/de/LC_MESSAGES/gmtp.mo=po/de.mo 0644 root root gMTP/.cvsignore000064401651440000012000000000051157272004000143210ustar00darranstaff00003030200010gmtp gMTP/pkginfo000064401651440000012000000005001200772637300137120ustar00darranstaff00003030200010CLASSES=none BASEDIR=/usr/local TZ=PST PATH=/sbin:/usr/sbin:/usr/bin:/usr/sadm/install/bin PKG=gmtp NAME=gMTP - A Simple MP3 Player Client for Solaris VERSION=1.3.4 CATEGORY=application DESC=gMTP - A Simple MP3 Player Client for Solaris VENDOR=Darran Kartaschew EMAIL=chewy509@mailcity.com PKGSAV=/var/sadm/pkg/gMTP/savegMTP/AUTHORS000064401651440000012000000016571167334443200134200ustar00darranstaff00003030200010gMTP Programmers Darran Kartaschew - chewy509@mailcity.com Distribution Package Maintainers. Solaris 10 - Darran Kartaschew - chewy509@mailcity.com Debian/Ubuntu - Alessio Treglia - alessio@debian.org Arch Linux - Mark Moeller - yugrotavele@archlinux.us Gentoo Linux - Thomas Kahle - tomka@gentoo.org Translations. English - Darran Kartaschew Italian - Francesca Ciceri French - 'Coug' German - Laurenz Kamp Spanish - Google Translate Danish - Cai Andersen gMTP Icon Oliver Scholtz - unknown email. Icon from Human O2 icon theme set as downloadable from Icon Finder - http://www.iconfinder.net/icondetails/24592/128/?q=mp3 Icon Set released under GPL. Icons in file view. Matthieu James (aka "tiheum") - matthieu.james@gmail.com ? Icons from Faenza icon set as downloadable from Gnome-Look.org - http://gnome-look.org/content/show.php/Faenza?content=128143 Icon Set released under GPL. gMTP Testers/Feedback Andrew Bradley Mark Moeller gMTP/misc/000075501651440000012000000000001205070637100132645ustar00darranstaff00003030200010gMTP/misc/id3tag.pc000064401651440000012000000003261151405040600147570ustar00darranstaff00003030200010prefix=/usr/local exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: id3tag Description: id3tag Library Requires: Version: 0.15.1 Libs: -L${libdir} -lid3tag Cflags: -I${includedir} gMTP/misc/gMTP.schemas000064401651440000012000000166341204762045200154530ustar00darranstaff00003030200010 /schemas/apps/gMTP/autoconnectdevice /apps/gMTP/autoconnectdevice gMTP bool false Auto Connect Device on Start Auto Connect Device on Start /schemas/apps/gMTP/promptDownloadPath /apps/gMTP/promptDownloadPath gMTP bool true Always Prompt for Download Path Always Prompt for Download Path /schemas/apps/gMTP/promptOverwriteFile /apps/gMTP/promptOverwriteFile gMTP bool true Prompt if file exists and about to overwrite Prompt if file exists and about to overwrite /schemas/apps/gMTP/confirmFileDelete /apps/gMTP/confirmFileDelete gMTP bool false Confirm File Delete Operations Confirm File Delete Operations /schemas/apps/gMTP/DownloadPath /apps/gMTP/DownloadPath gMTP string / Download Path Download Path /schemas/apps/gMTP/UploadPath /apps/gMTP/UploadPath gMTP string / Upload Path Upload Path /schemas/apps/gMTP/viewFileSize /apps/gMTP/viewFileSize gMTP bool true Toggle to view file size in main window Toggle to view file size in main window /schemas/apps/gMTP/viewFileType /apps/gMTP/viewFileType gMTP bool false Toggle to view file type in main window Toggle to view file type in main window /schemas/apps/gMTP/viewTrackNumber /apps/gMTP/viewTrackNumber gMTP bool false Toggle to view track number in main window Toggle to view track number in main window /schemas/apps/gMTP/viewTitle /apps/gMTP/viewTitle gMTP bool false Toggle to view track title in main window Toggle to view track title in main window /schemas/apps/gMTP/viewArtist /apps/gMTP/viewArtist gMTP bool false Toggle to view track artist in main window Toggle to view track artist in main window /schemas/apps/gMTP/viewAlbum /apps/gMTP/viewAlbum gMTP bool false Toggle to view track album in main window Toggle to view track album in main window /schemas/apps/gMTP/viewYear /apps/gMTP/viewYear gMTP bool false Toggle to view track year in main window Toggle to view track year in main window /schemas/apps/gMTP/viewGenre /apps/gMTP/viewGenre gMTP bool false Toggle to view track genre in main window Toggle to view track genre in main window /schemas/apps/gMTP/viewDuration /apps/gMTP/viewDuration gMTP bool false Toggle to view track duration in main window Toggle to view track duration in main window /schemas/apps/gMTP/viewFolders /apps/gMTP/viewFolders gMTP bool false Toggle to view folder tree list in main window Toggle to view folder tree list in main window /schemas/apps/gMTP/autoAddTrackPlaylist /apps/gMTP/autoAddTrackPlaylist gMTP bool false Toggle to automatically add a new track to a playlist Toggle to automatically add a new track to a playlist /schemas/apps/gMTP/ignorepathinplaylist /apps/gMTP/ignorepathinplaylist gMTP bool false Ignore path in playlist file when importing a playlist Ignore path in playlist file when importing a playlist /schemas/apps/gMTP/suppressalbumerrors /apps/gMTP/suppressalbumerrors gMTP bool false Suppress Album related errors for some devices Suppress Album related errors for some devices /schemas/apps/gMTP/alternateaccessmethod /apps/gMTP/alternateaccessmethod gMTP bool false Use alternate access method for Android based devices. Use alternate access method for Android based devices. gMTP string / false Auto Connect Device on Start Auto Connect Device on Start true Always Prompt for Download Path Always Prompt for Download Path true Prompt if file exists and about to overwrite Prompt if file exists and about to overwrite false Confirm File Delete Operations Confirm File Delete Operations '/' Download Path Download Path '/' Upload Path Upload Path true Toggle to view file size in main window Toggle to view file size in main window false Toggle to view file type in main window Toggle to view file type in main window false Toggle to view track number in main window Toggle to view track number in main window false Toggle to view track title in main window Toggle to view track title in main window false Toggle to view track artist in main window Toggle to view track artist in main window false Toggle to view track album in main window Toggle to view track album in main window false Toggle to view track year in main window Toggle to view track year in main window false Toggle to view track genre in main window Toggle to view track genre in main window false Toggle to view track duration in main window Toggle to view track duration in main window false Toggle to view folder tree list in main window Toggle to view folder tree list in main window false Toggle to automatically add a new track to a playlist Toggle to automatically add a new track to a playlist false Ignore path in playlist file when importing a playlist Ignore path in playlist file when importing a playlist false Suppress Album related errors for some devices Suppress Album related errors for some devices false Use alternate access method for Android based devices. Use alternate access method for Android based devices. gMTP/misc/libusb.pc000064401651440000012000000003571151405040600150700ustar00darranstaff00003030200010prefix=/usr/sfw exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: libusb Description: libusb (Native Solaris 10 library) Requires: Version: 0.15 Libs: -L${libdir} -R${libdir} -lusb Cflags: -I${includedir} gMTP/misc/gMTP.desktop000064401651440000012000000006761151405040600154720ustar00darranstaff00003030200010[Desktop Entry] Version=1.0 Name=gMTP Comment=A simple MTP Client for MP3 Players Comment[es]=Un sencillo cliente de plan de mediano plazo para los reproductores de MP3 Comment[fr]=Un simple MTP Client pour Lecteurs MP3 Comment[da]=En simpel MTP Client for MP3-afspillere Comment[de]=Eine einfache MTP-Client für MP3-Player Comment[it]=Un semplice client per MTP Lettori MP3 Exec=gmtp Icon=gMTPicon Type=Application Categories=AudioVideo;Audio; gMTP/src/000075501651440000012000000000001205070640600131175ustar00darranstaff00003030200010gMTP/src/prefs.h000064401651440000012000000031431204762143100144100ustar00darranstaff00003030200010/* * * File: prefs.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _PREFS_H #define _PREFS_H #ifdef __cplusplus extern "C" { #endif typedef struct { GString *fileSystemDownloadPath; GString *fileSystemUploadPath; gboolean attemptDeviceConnectOnStart; gboolean ask_download_path; gboolean prompt_overwrite_file_op; gboolean confirm_file_delete_op; gboolean auto_add_track_to_playlist; gboolean ignore_path_in_playlist_import; gboolean suppress_album_errors; gboolean use_alt_access_method; gboolean view_size; gboolean view_type; gboolean view_track_number; gboolean view_title; gboolean view_artist; gboolean view_album; gboolean view_year; gboolean view_genre; gboolean view_duration; gboolean view_folders; } Preferences_Struct; Preferences_Struct Preferences; void setupPreferences(); gboolean loadPreferences(); gboolean savePreferences(); #if GMTP_USE_GTK2 GConfClient *gconfconnect; void gconf_callback_func(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data); #else GSettings *gsettings_connect; void gsettings_callback_func(GSettings *settings, gchar *key, gpointer user_data); #endif #ifdef __cplusplus } #endif #endif /* _PREFS_H */ gMTP/src/main.h000064401651440000012000000054651205061517200142250ustar00darranstaff00003030200010/* * * File: main.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _MAIN_H #define _MAIN_H #ifdef __cplusplus extern "C" { #endif // Main Device information struct. typedef struct { gboolean deviceConnected; gint numrawdevices; gint rawdeviceID; gint storagedeviceID; LIBMTP_raw_device_t * rawdevices; LIBMTP_mtpdevice_t *device; LIBMTP_devicestorage_t *devicestorage; LIBMTP_error_number_t err; GString *devicename; GString *manufacturername; GString *modelname; GString *serialnumber; GString *deviceversion; GString *syncpartner; GString *sectime; GString *devcert; // Raw device GString *Vendor; GString *Product; uint32_t VendorID; uint32_t ProductID; uint32_t DeviceID; uint32_t BusLoc; uint16_t *filetypes; uint16_t filetypes_len; uint8_t maxbattlevel; uint8_t currbattlevel; } Device_Struct; // Main Window Widgets. GtkWidget *windowMain; GtkWidget *scrolledwindowMain; GtkWidget *windowPrefsDialog; GtkWidget *windowPropDialog; GtkWidget *windowPlaylistDialog; GtkWidget *windowStatusBar; GtkWidget *toolbuttonConnect; GtkWidget *treeviewFiles; GtkWidget *treeviewFolders; // Folder view; GtkWidget *scrolledwindowFolders; GtkTreeSelection *folderSelection; // Device information struct Device_Struct DeviceMgr; // File/Folder/Track/Playlist pointers LIBMTP_file_t *deviceFiles; LIBMTP_folder_t *deviceFolders; LIBMTP_track_t *deviceTracks; LIBMTP_playlist_t *devicePlayLists; uint32_t currentFolderID; // This is the ID of the current folder.... int32_t addTrackPlaylistID; GQueue *stackFolderIDs; GQueue *stackFolderNames; // Icon file locations. gchar *file_logo_png; gchar *file_icon48_png; gchar *file_icon16_png; gchar *file_about_png; gchar *file_format_png; // File view Icons gchar *file_audio_png; gchar *file_video_png; gchar *file_playlist_png; gchar *file_album_png; gchar *file_textfile_png; gchar *file_generic_png; gchar *file_folder_png; gchar *file_image_png; // Misc Utility function; void setFilePaths(int argc, char *argv[]); gchar *getRuntimePath(int argc, char *argv[]); // Common magic numbers. #define MEGABYTE 1048576 #define GMTP_REQUIRE_PLAYLIST -2 #define GMTP_NO_PLAYLIST -1 #define GMTP_MAX_STRING 8192 #ifdef __cplusplus } #endif #endif /* _MAIN_H */ gMTP/src/interface.h000064401651440000012000000232131204762061000152270ustar00darranstaff00003030200010/* * * File: interface.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _INTERFACE_H #define _INTERFACE_H #ifdef __cplusplus extern "C" { #endif #include // Main Window List enum fileListID { COL_FILENAME = 0, COL_FILENAME_HIDDEN, COL_FILENAME_ACTUAL, COL_FILESIZE, COL_FILEID, COL_ISFOLDER, COL_FILESIZE_HID, COL_TYPE, COL_TRACK_NUMBER, COL_TRACK_NUMBER_HIDDEN, COL_TITLE, COL_FL_ARTIST, COL_FL_ALBUM, COL_YEAR, COL_GENRE, COL_DURATION, COL_DURATION_HIDDEN, COL_ICON, COL_LOCATION, NUM_COLUMNS }; enum folderListID { COL_FOL_NAME = 0, COL_FOL_NAME_HIDDEN, COL_FOL_ID, COL_FOL_ICON, NUM_FOL_COLUMNS }; // Playlist windows lists. enum fileTrackID { COL_ARTIST = 0, COL_ALBUM, COL_TRACKID, COL_TRACKNAME, COL_TRACKDURATION, NUM_TCOLUMNS }; enum filePlaylistID { COL_PL_ORDER_NUM = 0, COL_PL_ARTIST, COL_PL_ALBUM, COL_PL_TRACKID, COL_PL_TRACKNAME, COL_PL_TRACKDURATION, NUM_PL_COLUMNS }; typedef struct { uint32_t itemid; gboolean isFolder; gchar *filename; uint64_t filesize; LIBMTP_filetype_t filetype; gchar *location; } FileListStruc; typedef struct { uint32_t album_id; gchar* filename; } Album_Struct; // File operation enums enum MTP_OVERWRITEOP { MTP_ASK, MTP_SKIP, MTP_SKIP_ALL, MTP_OVERWRITE, MTP_OVERWRITE_ALL }; // Main Window widgets GtkListStore *fileList; GtkTreeStore *folderList; GtkTreeSelection *fileSelection; GtkTreeSelection *folderSelection; gulong folderSelectHandler; gulong fileSelectHandler; GtkWidget* create_windowMain(void); void setWindowTitle(gchar *foldername); GtkWidget* create_windowPreferences(void); GtkWidget* create_windowProperties(void); GtkWidget* create_windowMainContextMenu(void); GtkWidget* create_windowMainColumnContextMenu(void); GtkWidget* create_windowFolderContextMenu(void); GtkWidget* create_windowPlaylist(void); GtkWidget* create_windowFormat(void); void SetToolbarButtonState(gboolean); void statusBarSet(gchar *text); void statusBarClear(); gboolean fileListClear(); GSList* getFileGetList2Add(); gboolean fileListAdd(); gchar* displayRenameFileDialog(gchar* currentfilename); gboolean fileListRemove(GList *List); gboolean fileListDownload(GList *List); GList* fileListGetSelection(); gboolean fileListClearSelection(); gboolean fileListSelectAll(void); gboolean folderListRemove(GList *List); gboolean folderListClear(); gboolean folderListAdd(LIBMTP_folder_t *folders, GtkTreeIter *parent); int64_t folderListGetSelection(void); gchar *folderListGetSelectionName(void); gboolean folderListDownload(gchar *foldername, uint32_t folderid); int64_t getTargetFolderLocation(void); gboolean folderListAddDialog(LIBMTP_folder_t *folders, GtkTreeIter *parent, GtkTreeStore *fl); void __fileMove(GtkTreeRowReference *Row); // Flags for overwriting files of host PC and device. gint fileoverwriteop; // Flag to allow overwrite of files on device. gint deviceoverwriteop; // Find options and variables. gboolean inFindMode; GSList *searchList; void g_free_search(FileListStruc *file); GtkWidget *FindToolbar_entry_FindText; GtkWidget *FindToolbar_checkbutton_FindFiles; GtkWidget *FindToolbar_checkbutton_TrackInformation; // Aggreegate function for adding a file to the device. void __filesAdd(gchar* filename); // Progress Dialog GtkWidget* create_windowProgressDialog(gchar* msg); void displayProgressBar(gchar* msg); void destroyProgressBar(void); void setProgressFilename(gchar* filename_stripped); int fileprogress(const uint64_t sent, const uint64_t total, void const * const data); GtkWidget *progressDialog; gboolean progressDialog_killed; // About Dialog box. void displayAbout(void); // Error dialog. void displayError(gchar* msg); void displayInformation(gchar* msg); // New Folder Dialog; gchar* displayFolderNewDialog(void); // Overwrite this file dialog? gint displayFileOverwriteDialog(gchar *filename); // Multidevice/Multistorage dialog; gint displayMultiDeviceDialog(void); gint displayDeviceStorageDialog(void); gchar* displayChangeDeviceNameDialog(gchar* devicename); // Set Album Art dialog; #define ALBUM_SIZE 96 void displayAddAlbumArtDialog(void); void AlbumArtUpdateImage(LIBMTP_album_t* selectedAlbum); void AlbumArtSetDefault(void); // Playlists gint playlist_number; gint comboboxentry_playlist_entries; void displayPlaylistDialog(void); void setupTrackList(GtkTreeView *treeviewFiles); void setup_PL_List(GtkTreeView *treeviewFiles); void SetPlaylistButtonState(gboolean state); void setPlayListComboBox(void); void setPlaylistField(gint PlayListID); gchar* displayPlaylistNewDialog(void); gboolean playlist_PL_ListClearSelection(); GList* playlist_PL_ListGetSelection(); gboolean playlist_PL_ListRemove(GList *List); void __playlist_PL_Remove(GtkTreeRowReference *Row); GList* playlist_TrackList_GetSelection(); gboolean playlist_TrackList_Add(GList *List); void __playlist_TrackList_Add(GtkTreeRowReference *Row); gboolean playlist_move_files(gint direction); void __playlist_move_files_up(GtkTreeRowReference *Row); void __playlist_move_files_down(GtkTreeRowReference *Row); void playlist_SavePlaylist(gint PlayListID); gboolean fileListAddToPlaylist(GList *List, uint32_t PlaylistID); gboolean fileListRemoveFromPlaylist(GList *List, uint32_t PlaylistID); void __fileAddToPlaylist(GtkTreeRowReference *Row, LIBMTP_playlist_t **playlist); void __fileRemoveFromPlaylist(GtkTreeRowReference *Row, LIBMTP_playlist_t **playlist); // Add track to playlist dialog; int32_t displayAddTrackPlaylistDialog(gboolean showNew /* = TRUE */); // Widget for find toolbar GtkWidget *findToolbar; // Widgets for menu items; GtkWidget *fileConnect; GtkWidget *fileAdd; GtkWidget *fileDownload; GtkWidget *fileRemove; GtkWidget *fileRename; GtkWidget *fileMove; GtkWidget *fileNewFolder; GtkWidget *fileRemoveFolder; GtkWidget *fileRescan; GtkWidget *editDeviceName; GtkWidget *editFormatDevice; GtkWidget *editAddAlbumArt; GtkWidget *editFind; GtkWidget *editSelectAll; GtkWidget *contextMenu; GtkWidget *contextMenuColumn; GtkWidget *contestMenuFolder; GtkWidget* cfileAdd; GtkWidget* cfileNewFolder; GtkWidget *toolbuttonAddFile; #if GMTP_USE_GTK2 GtkTooltips *tooltipsToolbar; #endif // Columns in main file view; GtkTreeViewColumn *column_Size; GtkTreeViewColumn *column_Type; GtkTreeViewColumn *column_Track_Number; GtkTreeViewColumn *column_Title; GtkTreeViewColumn *column_Artist; GtkTreeViewColumn *column_Album; GtkTreeViewColumn *column_Year; GtkTreeViewColumn *column_Genre; GtkTreeViewColumn *column_Duration; GtkTreeViewColumn *column_Location; // Main menu widgets GtkWidget *menu_view_filesize; GtkWidget *menu_view_filetype; GtkWidget *menu_view_track_number; GtkWidget *menu_view_title; GtkWidget *menu_view_artist; GtkWidget *menu_view_album; GtkWidget *menu_view_year; GtkWidget *menu_view_genre; GtkWidget *menu_view_duration; GtkWidget *menu_view_folders; // Column view context menu; GtkWidget* cViewSize; GtkWidget* cViewType; GtkWidget* cViewTrackName; GtkWidget* cViewTrackNumber; GtkWidget* cViewArtist; GtkWidget* cViewAlbum; GtkWidget* cViewYear; GtkWidget* cViewGenre; GtkWidget* cViewDuration; // Widgets for preferences buttons; GtkWidget *checkbuttonDeviceConnect; GtkWidget *entryDownloadPath; GtkWidget *entryUploadPath; GtkWidget *checkbuttonDownloadPath; GtkWidget *checkbuttonConfirmFileOp; GtkWidget *checkbuttonConfirmOverWriteFileOp; GtkWidget *checkbuttonAutoAddTrackPlaylist; GtkWidget *checkbuttonIgnorePathInPlaylist; GtkWidget *checkbuttonSuppressAlbumErrors; GtkWidget *checkbuttonAltAccessMethod; // AlbumArt Dialog global pointers GtkWidget *AlbumArtDialog; //GtkWidget *AlbumArtFilename; GtkWidget *AlbumArtImage; GtkWidget *buttonAlbumAdd; GtkWidget *buttonAlbumDownload; GtkWidget *buttonAlbumDelete; GtkWidget *textboxAlbumArt; // Playlist GtkWidget *treeview_Avail_Files; GtkWidget *treeview_Playlist_Files; GtkWidget *comboboxentry_playlist; GtkListStore *playlist_TrackList; GtkListStore *playlist_PL_List; // Buttons for playlist GtkWidget *button_Del_Playlist; GtkWidget *button_Export_Playlist; GtkWidget *button_File_Move_Up; GtkWidget *button_File_Move_Down; GtkWidget *button_Del_File; GtkWidget *button_Add_Files; // Widget for formatDevice progress bar. GtkWidget *formatDialog_progressBar1; // Combobox used in AddTrackPlaylist feature. GtkWidget *combobox_AddTrackPlaylist; int64_t fileMoveTargetFolder; #ifdef __cplusplus } #endif #endif /* _INTERFACE_H */ gMTP/src/metatag_info.c000064401651440000012000000756621176535257100157540ustar00darranstaff00003030200010/* * * File: metatag_info.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include "main.h" #include "callbacks.h" #include "interface.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" #include "metatag_info.h" // Constants needed for MP3 frame calculations. int mp3_samplerate[3][4] = { {22050, 24000, 16000, 50000}, // MPEG 2.0 {44100, 48000, 32000, 50000}, // MPEG 1.0 {11025, 12000, 8000, 50000} // MPEG 2.5 }; int mp3_bitrate[2][3][15] = { { // MPEG 2.0 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer 3 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer 2 {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256} // Layer 1 }, { // MPEG 1.0 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}, // Layer 3 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, // Layer 2 {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448} // Layer 1 } }; int mp3_sampleperframe[3][4] = { // MP2.5 , res , MP2, MP1 {576, 0, 576, 1152}, // Layer 3 {1152, 0, 1152, 1152}, // Layer 2 {384, 0, 384, 384} // Layer 1 }; // ************************************************************************************************ /** * Returns the Frame Text for a given tag. * @param tag * @param frame_name * @return */ gchar * ID3_getFrameText(struct id3_tag *tag, char *frame_name) { const id3_ucs4_t *id3_string; struct id3_frame *id3_frame; union id3_field *id3_field; gchar *rtn_string = NULL; enum id3_field_textencoding id3_field_encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1; id3_frame = id3_tag_findframe(tag, frame_name, 0); if (id3_frame == NULL) return NULL; id3_field = id3_frame_field(id3_frame, 0); if (id3_field && (id3_field_type(id3_field) == ID3_FIELD_TYPE_TEXTENCODING)) { id3_field_encoding = id3_field->number.value; } //if (frame_name == ID3_FRAME_COMMENT) { if(g_ascii_strcasecmp(frame_name, ID3_FRAME_COMMENT) == 0){ id3_field = id3_frame_field(id3_frame, 3); } else { id3_field = id3_frame_field(id3_frame, 1); } if (id3_field == NULL) return NULL; if (g_ascii_strcasecmp(frame_name, ID3_FRAME_COMMENT) == 0) { id3_string = id3_field_getfullstring(id3_field); } else { id3_string = id3_field_getstrings(id3_field, 0); } if (id3_string == NULL) return NULL; if (g_ascii_strcasecmp(frame_name, ID3_FRAME_GENRE) == 0) id3_string = id3_genre_name(id3_string); if (id3_field_encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) { rtn_string = (gchar *) id3_ucs4_latin1duplicate(id3_string); } else { rtn_string = (gchar *) id3_ucs4_utf8duplicate(id3_string); } return rtn_string; } // ************************************************************************************************ /** * Returns header infomation from a MP3 file. * @param mp3_file File handle to an open MP3 file. * @param header_info Variable to hold all the MP3 header information for this MP3 frame. * @return TRUE if successful, otherwise FALSE. */ gboolean get_mp3_header(FILE * mp3_file, MP3_header * header_info) { uint8_t raw_header[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint32_t framesize = 0; uint32_t id3_footer = 0; uint32_t id3_size = 0; resync: if (fread(&raw_header, sizeof (uint8_t)*10, 1, mp3_file) < 1) { header_info->header_sync = 0; return FALSE; } /* // Print header info for debugging printf("Offset: %llx : ", ftell(mp3_file) - 10); for (int i = 0; i < 10; i++) { printf("0x%x ", raw_header[i]); } printf("\n"); */ header_info->header_sync = ((raw_header[0] << 4) | ((raw_header[1]&0xE0) >> 4)); // Check for ID3 Tags while (header_info->header_sync != 0xFFE) { // We may have ID3 Tag. if (raw_header[0] == 'I' && raw_header[1] == 'D' && raw_header[2] == '3') { // We have a ID3 header... // Now skip it. id3_footer = (raw_header[5] >> 4) & 0x1; id3_size = ((uint32_t) (raw_header[6] << 21) + (uint32_t) (raw_header[7] << 14) + (uint32_t) (raw_header[8] << 7) + (uint32_t) (raw_header[9])); if (id3_footer == 1) { fseek(mp3_file, (id3_size + 10), SEEK_CUR); } else { fseek(mp3_file, (id3_size), SEEK_CUR); } if (fread(&raw_header, sizeof (uint8_t)*10, 1, mp3_file) < 1) { header_info->header_sync = 0; return FALSE; } /* // Print header info for debugging printf("Offset: %llx : ", ftell(mp3_file) - 10); for (int i = 0; i < 10; i++) { printf("0x%x ", raw_header[i]); } printf("\n"); */ header_info->header_sync = ((raw_header[0] << 4) | ((raw_header[1]&0xE0) >> 4)); } else { // Not a valid frame, nor an ID3 frame? // Attempt resync; header_info->header_sync = 0; fseek(mp3_file, -9, SEEK_CUR); goto resync; //return FALSE; } } header_info->version = (raw_header[1] >> 3) & 0x3; header_info->layer = (raw_header[1] >> 1) & 0x3; header_info->crc = raw_header[1] & 0x1; header_info->bitrate = (raw_header[2] >> 4) & 0xF; header_info->samplerate = (raw_header[2] >> 2) & 0x3; header_info->padding = (raw_header[2] >> 1) & 0x1; header_info->private_bit = (raw_header[2]) & 0x1; header_info->channel_mode = (raw_header[3] >> 6) & 0x3; header_info->mode_extension = (raw_header[3] >> 4) & 0x3; header_info->copyright = (raw_header[3] >> 3) & 0x1; header_info->original = (raw_header[3] >> 2) & 0x1; header_info->emphasis = (raw_header[3]) & 0x3; // Sanity checks -- if ((header_info->header_sync != 0xFFE) || (header_info->version == 0x1) // Reserved value //|| (header_info->layer == 0x1) // We only care about layer 3 data || (header_info->bitrate == 0xF) // Bad value || (header_info->samplerate == 0x3)) { // Reserved value header_info->header_sync = 0; // Attempt resync; header_info->header_sync = 0; fseek(mp3_file, -9, SEEK_CUR); goto resync; //return FALSE; } // We have a valid header, so forward to next possible frame. // FrameSize = (samples per sec / 8) * BitRate / (SampleRate + Padding). // Layer I if (header_info->layer == 3) { framesize = ((12 * mp3_bitrate[header_info->version & 0x1][header_info->layer - 1][header_info->bitrate] * 1000 / mp3_samplerate[header_info->version & 0x1][header_info->samplerate] ) + header_info->padding) * 4; } else { // Layer 2 and Layer 3 framesize = (mp3_sampleperframe[header_info->layer - 1][header_info->version] / 8 ) // Samples per frame. * mp3_bitrate[header_info->version & 0x1][header_info->layer - 1][header_info->bitrate] * 1000 // Bitrate / (mp3_samplerate[header_info->version & 0x1][header_info->samplerate] // Sample Rate ) + header_info->padding; // Padding bit } fseek(mp3_file, (framesize - 10), SEEK_CUR); return TRUE; } // ************************************************************************************************ /** * Get our file information for an MP3 based file. * @param filename * @param mp3_struct */ void get_mp3_info(gchar *filename, MP3_Info *mp3_struct) { FILE * mp3_file = NULL; struct stat sb; MP3_header header_info; uint32_t initial_bitrate = 0; uint32_t new_bitrate = 0; uint64_t total_bitrate = 0; uint32_t frames_sampled = 0; uint64_t filesize = 0; // Init our struct that has been passed to us, and return defaults even if // things go wrong. mp3_struct->VBR = 1; mp3_struct->bitrate = 0; mp3_struct->channels = 0; mp3_struct->duration = 0; mp3_file = fopen(filename, "r"); if (mp3_file == NULL) return; if (stat(filename, &sb) == -1) { perror("stat"); fclose(mp3_file); return; } filesize = sb.st_size; if (get_mp3_header(mp3_file, &header_info) == TRUE) { initial_bitrate = mp3_bitrate[header_info.version & 0x1][header_info.layer - 1][header_info.bitrate]; total_bitrate = initial_bitrate; frames_sampled = 1; if (header_info.channel_mode == 0x3) { mp3_struct->channels = 1; } else { mp3_struct->channels = 2; } // Scan the full file for all frames. while ((ftell(mp3_file) < (int64_t) (filesize - 128)) && (get_mp3_header(mp3_file, &header_info) == TRUE)) { new_bitrate = mp3_bitrate[header_info.version & 0x1][header_info.layer - 1][header_info.bitrate]; total_bitrate += new_bitrate; frames_sampled++; if (new_bitrate != initial_bitrate) mp3_struct->VBR = 2; } if (mp3_struct->VBR != 2) { mp3_struct->bitrate = initial_bitrate * 1000; } else { mp3_struct->bitrate = (total_bitrate / frames_sampled) * 1000; } //mp3_struct->duration = frames_sampled * 26; mp3_struct->duration = (double) frames_sampled * 26.00; // Each frame lasts for 26ms, so just multiple the number of frames by 26 to get our duration } } // ************************************************************************************************ /** * Get our track ID3 Tag information * @param filename filename of the MP3 file * @param trackinformation ID3 infomation returned via this struct. */ void get_id3_tags(gchar *filename, LIBMTP_track_t *trackinformation) { gchar * tracknumber = NULL; gchar * trackduration = NULL; MP3_Info mp3_information; struct id3_file * id3_file_id = id3_file_open(filename, ID3_FILE_MODE_READONLY); if (id3_file_id != NULL) { // We have a valid file, so lets get some data. struct id3_tag* id3_tag_id = id3_file_tag(id3_file_id); // We have our tag data, so now cycle through the fields. trackinformation->album = ID3_getFrameText(id3_tag_id, ID3_FRAME_ALBUM); trackinformation->title = ID3_getFrameText(id3_tag_id, ID3_FRAME_TITLE); trackinformation->artist = ID3_getFrameText(id3_tag_id, ID3_FRAME_ARTIST); trackinformation->date = ID3_getFrameText(id3_tag_id, ID3_FRAME_YEAR); trackinformation->genre = ID3_getFrameText(id3_tag_id, ID3_FRAME_GENRE); tracknumber = ID3_getFrameText(id3_tag_id, ID3_FRAME_TRACK); if (tracknumber != 0) { trackinformation->tracknumber = atoi(tracknumber); } else { trackinformation->tracknumber = 0; } // Need below if the default artist field is NULL if (trackinformation->artist == NULL) trackinformation->artist = ID3_getFrameText(id3_tag_id, "TPE2"); if (trackinformation->artist == NULL) trackinformation->artist = ID3_getFrameText(id3_tag_id, "TPE3"); if (trackinformation->artist == NULL) trackinformation->artist = ID3_getFrameText(id3_tag_id, "TPE4"); if (trackinformation->artist == NULL) trackinformation->artist = ID3_getFrameText(id3_tag_id, "TCOM"); // Need this if using different Year field. if (trackinformation->date == NULL) trackinformation->date = ID3_getFrameText(id3_tag_id, "TDRC"); // Get our track duration via ID3 Tag. trackduration = ID3_getFrameText(id3_tag_id, "TLEN"); if (trackduration != 0) { trackinformation->duration = atoi(trackduration); } else { trackinformation->duration = 0; } // Close our file for reading the fields. id3_file_close(id3_file_id); } // Duration, bitrate and other information // This information must be derived by manually decoding the MP3 file. get_mp3_info(filename, &mp3_information); trackinformation->duration = mp3_information.duration; trackinformation->bitrate = mp3_information.bitrate; trackinformation->bitratetype = mp3_information.VBR; trackinformation->nochannels = mp3_information.channels; } // ************************************************************************************************ /** * Return the OGG Comment for the given tag name * @param comments The OGG Comments data field. * @param name The tag name to return * @return gchar* to string with tag contents */ gchar * OGG_getFieldText(const vorbis_comment *comments, const char *name) { gchar ** file_comments; gchar ** comments_split; gint file_comments_count = 0; // We simple cycle through our comments, looking for our name, and return it's value; if (comments->comments > 0) { file_comments = comments->user_comments; file_comments_count = comments->comments; while (file_comments_count--) { // We have our comment, now see if it is what we are after? comments_split = g_strsplit(*file_comments, "=", 2); if (*comments_split != NULL) { if (g_ascii_strcasecmp(name, *comments_split) == 0) { // We have our desrired tag, so return it to the user. comments_split++; return g_strdup(*comments_split); } } // Increment our pointers accordingly. file_comments++; } } else { // No comments, so return a NULL value; return NULL; } // We didn't find our key, so return NULL return NULL; } // ************************************************************************************************ /** * Get our OGG track information * @param filename The OGG file to extract information * @param trackinformation Return the Trackinformation via this variable. */ void get_ogg_tags(gchar *filename, LIBMTP_track_t *trackinformation) { OggVorbis_File *mov_file = NULL; vorbis_info * mov_info = NULL; FILE *mfile; vorbis_comment *mov_file_comment = NULL; gchar * tracknumber = NULL; // Attempt to open the file, and init the OggVorbis_File struct for our file. // Yes I know about ov_fopen(), but Solaris 10 ships with vorbis 1.0.1 which // doesn't have this function. mfile = fopen(filename, "r"); if (mfile == NULL) return; // Allocate memory to hold the OV file information. mov_file = g_malloc0(sizeof (OggVorbis_File)); if (ov_open(mfile, mov_file, NULL, 0) != 0) { fclose(mfile); return; } // Get or comment data; mov_file_comment = ov_comment(mov_file, -1); mov_info = ov_info(mov_file, -1); trackinformation->album = OGG_getFieldText(mov_file_comment, "ALBUM"); trackinformation->title = OGG_getFieldText(mov_file_comment, "TITLE"); trackinformation->artist = OGG_getFieldText(mov_file_comment, "ARTIST"); trackinformation->date = OGG_getFieldText(mov_file_comment, "DATE"); trackinformation->genre = OGG_getFieldText(mov_file_comment, "GENRE"); tracknumber = OGG_getFieldText(mov_file_comment, "TRACKNUMBER"); if (tracknumber != NULL) { trackinformation->tracknumber = atoi(tracknumber); } else { trackinformation->tracknumber = 0; } // Duration, bitrate and other information trackinformation->duration = (int) ov_time_total(mov_file, -1) * 1000; trackinformation->bitrate = ov_bitrate(mov_file, -1); trackinformation->bitratetype = 2; // VBR trackinformation->nochannels = mov_info->channels; // Clean up our data structures. ov_clear(mov_file); g_free(mov_file); return; } // ************************************************************************************************ /** * Get our FLAC Comment for the given tag name * @param tags The FLAC Comments * @param name The tag name. * @return */ gchar *FLAC_getFieldText(const FLAC__StreamMetadata *tags, const char *name) { int index = FLAC__metadata_object_vorbiscomment_find_entry_from(tags, 0, name); if (index < 0) { return NULL; } else { return strchr((const char *) tags->data.vorbis_comment.comments[index].entry, '=') + 1; } } // ************************************************************************************************ /** * Get our FLAC track information * @param filename The FLAC file to extract information * @param trackinformation Return the Trackinformation via this variable. */ void get_flac_tags(gchar *filename, LIBMTP_track_t *trackinformation) { FLAC__StreamMetadata *tags = NULL; FLAC__StreamMetadata streaminfo; gchar * tracknumber = 0; // Load in our tag information stream if (!FLAC__metadata_get_tags(filename, &tags)) return; if (!FLAC__metadata_get_streaminfo(filename, &streaminfo)) { return; } // We have our tag data, get the individual fields. trackinformation->album = g_strdup(FLAC_getFieldText(tags, "ALBUM")); trackinformation->title = g_strdup(FLAC_getFieldText(tags, "TITLE")); trackinformation->artist = g_strdup(FLAC_getFieldText(tags, "ARTIST")); trackinformation->date = g_strdup(FLAC_getFieldText(tags, "DATE")); trackinformation->genre = g_strdup(FLAC_getFieldText(tags, "GENRE")); tracknumber = FLAC_getFieldText(tags, "TRACKNUMBER"); if (tracknumber != 0) { trackinformation->tracknumber = atoi(tracknumber); } else { trackinformation->tracknumber = 0; } // Duration, bitrate and other information if((streaminfo.data.stream_info.sample_rate != 0)&&(streaminfo.data.stream_info.total_samples != 0)){ trackinformation->duration = (streaminfo.data.stream_info.total_samples / streaminfo.data.stream_info.sample_rate) * 1000; trackinformation->bitrate = 8.0 * (float) (trackinformation->filesize) / (1000.0 * (float) streaminfo.data.stream_info.total_samples / (float) streaminfo.data.stream_info.sample_rate); } else { trackinformation->duration = 0; trackinformation->bitrate = 0; } trackinformation->bitratetype = 0; // Not used trackinformation->nochannels = streaminfo.data.stream_info.channels; //trackinformation->tracknumber = atoi(FLAC_getFieldText(tags, "TRACKNUMBER")); FLAC__metadata_object_delete(tags); //FLAC__metadata_object_delete(&streaminfo); return; } // ************************************************************************************************ /** * Get our WMA track information. WMA and WMV are both contained in a ASF Container which the * container header has all the information. (No need to parse the audio streams themselves). * @param filename The WMA file to extract information * @param trackinformation Return the Trackinformation via this variable. */ void get_asf_tags(gchar *filename, LIBMTP_track_t *trackinformation) { FILE *ASF_File; GUID Header_GUID; GUID Stream_GUID; uint32_t Header_Blocks; uint64_t Object_Size; long ASF_File_Position; // Content Object uint16_t Title_Length = 0; uint16_t Author_Length = 0; uint16_t Copyright_Length = 0; uint16_t Description_Length = 0; uint16_t Rating_Length = 0; gchar *Title = NULL; gchar *Author = NULL; // Extended Content Object uint16_t Content_Descriptors_Count = 0; uint16_t Descriptor_Name_Length = 0; gchar *Descriptor_Name = NULL; gchar *Descriptor_Name_UTF16 = NULL; uint16_t Descriptor_Value_Type = 0; uint16_t Descriptor_Value_Length = 0; uint64_t Descriptor_Value = 0; gchar *Descriptor_Value_Str = NULL; gchar *Descriptor_Value_Str_UTF16 = NULL; // Audio Object uint16_t Stream_Channels; uint32_t Stream_Bitrate; // File Object uint64_t Stream_Duration; ASF_File = fopen(filename, "r"); if (ASF_File == NULL) return; // Get our header GUID and make sure this is it. fread(&Header_GUID, sizeof (GUID), 1, ASF_File); if (!memcmp(&Header_GUID, &ASF_header, sizeof (GUID))) { // If not exit. fclose(ASF_File); return; } // Skip the rest of the header area; fseek(ASF_File, 8, SEEK_CUR); fread(&Header_Blocks, sizeof (uint32_t), 1, ASF_File); fseek(ASF_File, 2, SEEK_CUR); // We should be at the start of the header blocks; // Header_blocks has the number of header objects that we can test. while (Header_Blocks--) { fread(&Header_GUID, sizeof (GUID), 1, ASF_File); if (memcmp(&Header_GUID, &ASF_comment_header, sizeof (GUID)) == 0) { // We have our standard comment header block; // Get the size of the object, and the current file position. fread(&Object_Size, sizeof (uint64_t), 1, ASF_File); ASF_File_Position = ftell(ASF_File); // Get our field lengths. fread(&Title_Length, sizeof (uint16_t), 1, ASF_File); fread(&Author_Length, sizeof (uint16_t), 1, ASF_File); fread(&Copyright_Length, sizeof (uint16_t), 1, ASF_File); fread(&Description_Length, sizeof (uint16_t), 1, ASF_File); fread(&Rating_Length, sizeof (uint16_t), 1, ASF_File); // Since we only need Title and Author, we only need to alloc memory for those two. Title = g_malloc0(Title_Length + 0x10); Author = g_malloc0(Author_Length + 0x10); fread(Title, Title_Length, 1, ASF_File); fread(Author, Author_Length, 1, ASF_File); // Set our track information trackinformation->title = g_utf16_to_utf8((const gunichar2 *) Title, Title_Length, NULL, NULL, NULL); trackinformation->artist = g_utf16_to_utf8((const gunichar2 *) Author, Author_Length, NULL, NULL, NULL); // Free our memory that we used to load in the fields. g_free(Title); g_free(Author); Title = NULL; Author = NULL; // Set our file position so it's ready to read in the next GUID Header. fseek(ASF_File, ASF_File_Position, SEEK_SET); fseek(ASF_File, (Object_Size - sizeof (uint64_t) - sizeof (GUID)), SEEK_CUR); } else { if (memcmp(&Header_GUID, &ASF_extended_content_header, sizeof (GUID)) == 0) { // We have our standard comment header block; //g_printf("WMA: Found our extended comment block\n"); // Get the size of the object, and the current file position. fread(&Object_Size, sizeof (uint64_t), 1, ASF_File); ASF_File_Position = ftell(ASF_File); // Get the number of Descripions field we have, as we will need to cycle through them all. fread(&Content_Descriptors_Count, sizeof (uint16_t), 1, ASF_File); while (Content_Descriptors_Count--) { // These themselves are Objects within the main extended content header, which we need to handle. // Format is: // Descriptor Name Length (word) // Descriptor Name (varies) // Descriptor Value Type (word) // Descriptor Value Length (word) // Descriptor Value (varies - depend on Value Type). Descriptor_Name_Length = 0; Descriptor_Name = NULL; Descriptor_Name_UTF16 = NULL; Descriptor_Value_Type = 0; Descriptor_Value_Length = 0; Descriptor_Value = 0; Descriptor_Value_Str = NULL; Descriptor_Value_Str_UTF16 = NULL; // Get our Descriptor Name. fread(&Descriptor_Name_Length, sizeof (uint16_t), 1, ASF_File); Descriptor_Name_UTF16 = g_malloc0(Descriptor_Name_Length + 0x10); fread(Descriptor_Name_UTF16, Descriptor_Name_Length, 1, ASF_File); Descriptor_Name = g_utf16_to_utf8((const gunichar2 *) Descriptor_Name_UTF16, Descriptor_Name_Length, NULL, NULL, NULL); // Get our Value Type and Value Length fread(&Descriptor_Value_Type, sizeof (uint16_t), 1, ASF_File); fread(&Descriptor_Value_Length, sizeof (uint16_t), 1, ASF_File); switch (Descriptor_Value_Type) { case 0: // String; case 1: // Binary; Descriptor_Value_Str_UTF16 = g_malloc0(Descriptor_Value_Length + 0x10); fread(Descriptor_Value_Str_UTF16, Descriptor_Value_Length, 1, ASF_File); Descriptor_Value_Str = g_utf16_to_utf8((const gunichar2 *) Descriptor_Value_Str_UTF16, Descriptor_Value_Length, NULL, NULL, NULL); // We have out key=value pair so lets look for our desired keys 'WM/AlbumTitle', 'WM/Genre' and 'WM/Year' if (g_ascii_strcasecmp(Descriptor_Name, "WM/AlbumTitle\0") == 0) { // We have the album Title; trackinformation->album = g_strdup(Descriptor_Value_Str); } else { if (g_ascii_strcasecmp(Descriptor_Name, "WM/Genre\0") == 0) { // We have the album Genre; trackinformation->genre = g_strdup(Descriptor_Value_Str); } else { if (g_ascii_strcasecmp(Descriptor_Name, "WM/Year\0") == 0) { // We have the album Year; trackinformation->date = g_strdup(Descriptor_Value_Str); } } } break; case 2: // Boolean (DWORD) case 3: // DWORD case 4: // QWORD case 5: // WORD if (Descriptor_Value_Length > sizeof (Descriptor_Value)) Descriptor_Value_Length = sizeof (Descriptor_Value); fread(&Descriptor_Value, Descriptor_Value_Length, 1, ASF_File); if ((g_ascii_strcasecmp(Descriptor_Name, "WM/Track\0") == 0)) { trackinformation->tracknumber = Descriptor_Value + 1; } else { if (g_ascii_strcasecmp(Descriptor_Name, "WM/TrackNumber\0") == 0) trackinformation->tracknumber = Descriptor_Value; } break; default: // Unknown so skip it. fseek(ASF_File, Descriptor_Value_Length, SEEK_CUR); break; } // Free up our allocated memory; g_free(Descriptor_Name); g_free(Descriptor_Name_UTF16); g_free(Descriptor_Value_Str); g_free(Descriptor_Value_Str_UTF16); } // Set our file position so it's ready to read in the next GUID Header. fseek(ASF_File, ASF_File_Position, SEEK_SET); fseek(ASF_File, (Object_Size - sizeof (uint64_t) - sizeof (GUID)), SEEK_CUR); } else { if (memcmp(&Header_GUID, &ASF_Stream_header, sizeof (GUID)) == 0) { // We have an audio header for the track information. fread(&Object_Size, sizeof (uint64_t), 1, ASF_File); ASF_File_Position = ftell(ASF_File); // Read in the stream type GUID fread(&Stream_GUID, sizeof (GUID), 1, ASF_File); if (memcmp(&Stream_GUID, &ASF_Audio_Media_header, sizeof (GUID)) == 0) { // We have an audio header. fseek(ASF_File, 38, SEEK_CUR); // We should be pointing at our audio stream data block. fseek(ASF_File, sizeof (uint16_t), SEEK_CUR); // Skip CODEC ID fread(&Stream_Channels, sizeof (uint16_t), 1, ASF_File); fseek(ASF_File, 4, SEEK_CUR); // Skip Samples per second fread(&Stream_Bitrate, sizeof (uint32_t), 1, ASF_File); trackinformation->nochannels = Stream_Channels; trackinformation->bitrate = Stream_Bitrate * 8; // This value is in BYTES trackinformation->bitratetype = 0; // Not used } // Set our file position so it's ready to read in the next GUID Header. fseek(ASF_File, ASF_File_Position, SEEK_SET); fseek(ASF_File, (Object_Size - sizeof (uint64_t) - sizeof (GUID)), SEEK_CUR); } else { if (memcmp(&Header_GUID, &ASF_File_Properties_header, sizeof (GUID)) == 0) { // We have a file header for the track information. fread(&Object_Size, sizeof (uint64_t), 1, ASF_File); ASF_File_Position = ftell(ASF_File); // Skip File ID, Filesize, Creation Date and Data Packets Count fseek(ASF_File, (sizeof (GUID) + (sizeof (uint64_t) * 3)), SEEK_CUR); fread(&Stream_Duration, sizeof (uint64_t), 1, ASF_File); // Convert from 1/100ths nano sec to millisec. trackinformation->duration = Stream_Duration / 10000; fseek(ASF_File, ASF_File_Position, SEEK_SET); fseek(ASF_File, (Object_Size - sizeof (uint64_t) - sizeof (GUID)), SEEK_CUR); } else { // Skip this header; fread(&Object_Size, sizeof (uint64_t), 1, ASF_File); fseek(ASF_File, (Object_Size - sizeof (uint64_t) - sizeof (GUID)), SEEK_CUR); } } } } } fclose(ASF_File); return; } ASF_File); fseek(ASF_File, 2, SEEK_CUR); // We should be at the startgMTP/src/dnd.h000064401651440000012000000031131176535257100140470ustar00darranstaff00003030200010/* * * File: dnd.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _DND_H #define _DND_H #ifdef __cplusplus extern "C" { #endif /* Designate dropped data types that we know and care about */ enum { GMTP_DROP_STRING, GMTP_DROP_PLAINTEXT, GMTP_DROP_URLENCODED }; /* Drag data format listing for gtk_drag_dest_set() */ GtkTargetEntry _gmtp_drop_types[3]; #define gmtp_drag_dest_set(widget) gtk_drag_dest_set(widget, \ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, \ _gmtp_drop_types, 3, GDK_ACTION_COPY | GDK_ACTION_MOVE) void gmtp_drag_data_received(GtkWidget * widget, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint time, gpointer user_data); void gmtpfolders_drag_data_received(GtkWidget * widget, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint time, gpointer user_data); void gmtpfolders_drag_motion_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); GSList* getFilesListURI(gchar* rawdata); void addFilesinFolder(gchar* foldername); #ifdef __cplusplus } #endif #endif /* _DND_H */ * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _DND_H #define _DND_H #ifdef __cplusplus extern "C" { #endif /* Designate dropped data types that we know and care about */ enum { GMTP_DROP_STRING, GMTP_DROP_PLAINgMTP/src/mtp.c000064401651440000012000002543631205061753400141030ustar00darranstaff00003030200010/* * * File: mtp.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include "main.h" #include "callbacks.h" #include "interface.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" #include "metatag_info.h" // Array with file extensions matched to internal libmtp file types; // See find_filetype() for usage; MTP_file_ext_struct file_ext[] = { {"wav", LIBMTP_FILETYPE_WAV}, {"mp3", LIBMTP_FILETYPE_MP3}, {"wma", LIBMTP_FILETYPE_WMA}, {"ogg", LIBMTP_FILETYPE_OGG}, {"mp4", LIBMTP_FILETYPE_MP4}, {"wmv", LIBMTP_FILETYPE_WMV}, {"avi", LIBMTP_FILETYPE_AVI}, {"mpeg", LIBMTP_FILETYPE_MPEG}, {"mpg", LIBMTP_FILETYPE_MPEG}, {"asf", LIBMTP_FILETYPE_ASF}, {"qt", LIBMTP_FILETYPE_QT}, {"mov", LIBMTP_FILETYPE_QT}, {"wma", LIBMTP_FILETYPE_WMA}, {"jpg", LIBMTP_FILETYPE_JPEG}, {"jpeg", LIBMTP_FILETYPE_JPEG}, {"jfif", LIBMTP_FILETYPE_JFIF}, {"tif", LIBMTP_FILETYPE_TIFF}, {"tiff", LIBMTP_FILETYPE_TIFF}, {"bmp", LIBMTP_FILETYPE_BMP}, {"gif", LIBMTP_FILETYPE_GIF}, {"pic", LIBMTP_FILETYPE_PICT}, {"pict", LIBMTP_FILETYPE_PICT}, {"png", LIBMTP_FILETYPE_PNG}, {"wmf", LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT}, {"ics", LIBMTP_FILETYPE_VCALENDAR2}, {"exe", LIBMTP_FILETYPE_WINEXEC}, {"com", LIBMTP_FILETYPE_WINEXEC}, {"bat", LIBMTP_FILETYPE_WINEXEC}, {"dll", LIBMTP_FILETYPE_WINEXEC}, {"sys", LIBMTP_FILETYPE_WINEXEC}, {"txt", LIBMTP_FILETYPE_TEXT}, {"aac", LIBMTP_FILETYPE_AAC}, {"mp2", LIBMTP_FILETYPE_MP2}, {"flac", LIBMTP_FILETYPE_FLAC}, {"m4a", LIBMTP_FILETYPE_M4A}, {"doc", LIBMTP_FILETYPE_DOC}, {"xml", LIBMTP_FILETYPE_XML}, {"xls", LIBMTP_FILETYPE_XLS}, {"ppt", LIBMTP_FILETYPE_PPT}, {"mht", LIBMTP_FILETYPE_MHT}, {"jp2", LIBMTP_FILETYPE_JP2}, {"jpx", LIBMTP_FILETYPE_JPX}, {"bin", LIBMTP_FILETYPE_FIRMWARE}, {"vcf", LIBMTP_FILETYPE_VCARD3}, {"alb", LIBMTP_FILETYPE_ALBUM}, {"pla", LIBMTP_FILETYPE_PLAYLIST} }; static gchar* blank_ext = ""; // Ignore Album errors? gboolean AlbumErrorIgnore = FALSE; // ************************************************************************************************ /** * Attempt to connect to a device. * @return 0 if successful, otherwise error code. */ guint deviceConnect() { gint error; if (DeviceMgr.deviceConnected == TRUE) { // We must be wanting to disconnect the device. return deviceDisconnect(); } else { error = LIBMTP_Detect_Raw_Devices(&DeviceMgr.rawdevices, &DeviceMgr.numrawdevices); switch (error) { case LIBMTP_ERROR_NONE: break; case LIBMTP_ERROR_NO_DEVICE_ATTACHED: g_fprintf(stderr, _("Detect: No raw devices found.\n")); displayError(_("Detect: No raw devices found.\n")); return MTP_GENERAL_FAILURE; case LIBMTP_ERROR_CONNECTING: g_fprintf(stderr, _("Detect: There has been an error connecting. \n")); displayError(_("Detect: There has been an error connecting. \n")); return MTP_GENERAL_FAILURE; case LIBMTP_ERROR_MEMORY_ALLOCATION: g_fprintf(stderr, _("Detect: Encountered a Memory Allocation Error. \n")); displayError(_("Detect: Encountered a Memory Allocation Error. \n")); return MTP_GENERAL_FAILURE; default: // Some other generic error, so let's exit. g_fprintf(stderr, _("Detect: There has been an error connecting. \n")); displayError(_("Detect: There has been an error connecting. \n")); return MTP_GENERAL_FAILURE; } // We have at least 1 raw device, so we connect to the first device. if (DeviceMgr.numrawdevices > 1) { DeviceMgr.rawdeviceID = displayMultiDeviceDialog(); if (!Preferences.use_alt_access_method) { DeviceMgr.device = LIBMTP_Open_Raw_Device(&DeviceMgr.rawdevices[DeviceMgr.rawdeviceID]); } else { DeviceMgr.device = LIBMTP_Open_Raw_Device_Uncached(&DeviceMgr.rawdevices[DeviceMgr.rawdeviceID]); } } else { // Connect to the first device. if (!Preferences.use_alt_access_method) { DeviceMgr.device = LIBMTP_Open_Raw_Device(&DeviceMgr.rawdevices[0]); } else { DeviceMgr.device = LIBMTP_Open_Raw_Device_Uncached(&DeviceMgr.rawdevices[0]); } DeviceMgr.rawdeviceID = 0; } if (DeviceMgr.device == NULL) { g_fprintf(stderr, _("Detect: Unable to open raw device?\n")); displayError(_("Detect: Unable to open raw device?\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); DeviceMgr.deviceConnected = FALSE; return MTP_GENERAL_FAILURE; } LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); DeviceMgr.deviceConnected = TRUE; // We have a successful device connect, but lets check for multiple storageIDs. if (DeviceMgr.device->storage == NULL) { g_fprintf(stderr, _("Detect: No available Storage found on device?\n")); displayError(_("Detect: No available Storage found on device?\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); deviceDisconnect(); return MTP_GENERAL_FAILURE; } if (DeviceMgr.device->storage->next != NULL) { // Oops we have multiple storage IDs. DeviceMgr.storagedeviceID = displayDeviceStorageDialog(); } else { DeviceMgr.storagedeviceID = MTP_DEVICE_SINGLE_STORAGE; } currentFolderID = 0; DeviceMgr.devicename = NULL; DeviceMgr.manufacturername = NULL; DeviceMgr.modelname = NULL; DeviceMgr.serialnumber = NULL; DeviceMgr.deviceversion = NULL; DeviceMgr.syncpartner = NULL; DeviceMgr.sectime = NULL; DeviceMgr.devcert = NULL; DeviceMgr.Vendor = NULL; DeviceMgr.Product = NULL; DeviceMgr.devicestorage = NULL; // if in alt connection mode; if (Preferences.use_alt_access_method) { if (stackFolderIDs != NULL) { g_queue_free(stackFolderIDs); } stackFolderIDs = g_queue_new(); if (stackFolderNames != NULL) { g_queue_free(stackFolderNames); } stackFolderNames = g_queue_new(); } return MTP_SUCCESS; } } // ************************************************************************************************ /** * Disconnect from the currently connected device. * @return 0 if successful, otherwise error code. */ guint deviceDisconnect() { if (DeviceMgr.deviceConnected == FALSE) { DeviceMgr.deviceConnected = FALSE; return MTP_NO_DEVICE; } else { DeviceMgr.deviceConnected = FALSE; LIBMTP_Release_Device(DeviceMgr.device); g_free(DeviceMgr.rawdevices); // Now clean up the dymanic data in struc that get's loaded when displaying the properties dialog. if (DeviceMgr.devicename != NULL) g_string_free(DeviceMgr.devicename, TRUE); if (DeviceMgr.manufacturername != NULL) g_string_free(DeviceMgr.manufacturername, TRUE); if (DeviceMgr.modelname != NULL) g_string_free(DeviceMgr.modelname, TRUE); if (DeviceMgr.serialnumber != NULL) g_string_free(DeviceMgr.serialnumber, TRUE); if (DeviceMgr.deviceversion != NULL) g_string_free(DeviceMgr.deviceversion, TRUE); if (DeviceMgr.syncpartner != NULL) g_string_free(DeviceMgr.syncpartner, TRUE); if (DeviceMgr.sectime != NULL) g_string_free(DeviceMgr.sectime, TRUE); if (DeviceMgr.devcert != NULL) g_string_free(DeviceMgr.devcert, TRUE); if (DeviceMgr.Vendor != NULL) g_string_free(DeviceMgr.Vendor, TRUE); if (DeviceMgr.Product != NULL) g_string_free(DeviceMgr.Product, TRUE); g_free(DeviceMgr.filetypes); return MTP_SUCCESS; } } // ************************************************************************************************ /** * Get the properties of the connected device. These properties are stored in 'DeviceMgr'. */ void deviceProperties() { gint ret; gchar *tmp_string; // We first see if we have a connected device, and then extract the information from it. if (DeviceMgr.deviceConnected == TRUE) { // Lets get our information. Let's start with the raw information. if (DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.vendor == NULL) { DeviceMgr.Vendor = g_string_new(_("Unknown")); } else { DeviceMgr.Vendor = g_string_new(DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.vendor); } if (DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.product == NULL) { DeviceMgr.Product = g_string_new(_("Unknown")); } else { DeviceMgr.Product = g_string_new(DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.product); } DeviceMgr.VendorID = DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.vendor_id; DeviceMgr.ProductID = DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].device_entry.product_id; DeviceMgr.BusLoc = DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].bus_location; DeviceMgr.DeviceID = DeviceMgr.rawdevices[DeviceMgr.rawdeviceID].devnum; // Now lets get our other information. // Nice name: tmp_string = LIBMTP_Get_Friendlyname(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.devicename = g_string_new(_("N/A")); } else { DeviceMgr.devicename = g_string_new(tmp_string); g_free(tmp_string); } // Sync Partner tmp_string = LIBMTP_Get_Syncpartner(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.syncpartner = g_string_new(_("N/A")); } else { DeviceMgr.syncpartner = g_string_new(tmp_string); g_free(tmp_string); } // Battery Level ret = LIBMTP_Get_Batterylevel(DeviceMgr.device, &DeviceMgr.maxbattlevel, &DeviceMgr.currbattlevel); if (ret != 0) { // Silently ignore. Some devices does not support getting the // battery level. DeviceMgr.maxbattlevel = 0; DeviceMgr.currbattlevel = 0; LIBMTP_Clear_Errorstack(DeviceMgr.device); } // Manufacturer Name. tmp_string = LIBMTP_Get_Manufacturername(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.manufacturername = g_string_new(_("N/A")); } else { DeviceMgr.manufacturername = g_string_new(tmp_string); g_free(tmp_string); } // Model Number, tmp_string = LIBMTP_Get_Modelname(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.modelname = g_string_new(_("N/A")); } else { DeviceMgr.modelname = g_string_new(tmp_string); g_free(tmp_string); } // Serial Number. tmp_string = LIBMTP_Get_Serialnumber(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.serialnumber = g_string_new(_("N/A")); } else { DeviceMgr.serialnumber = g_string_new(tmp_string); g_free(tmp_string); } // Device Version. tmp_string = LIBMTP_Get_Deviceversion(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.deviceversion = g_string_new(_("N/A")); } else { DeviceMgr.deviceversion = g_string_new(tmp_string); g_free(tmp_string); } // Secure Time ret = LIBMTP_Get_Secure_Time(DeviceMgr.device, &tmp_string); if (ret == 0 && tmp_string != NULL) { // tmp_string is a XML fragment, and we need just the date/time out of it. DeviceMgr.sectime = g_string_new(tmp_string); g_free(tmp_string); } else { // Silently ignore - there may be devices not supporting secure time. DeviceMgr.sectime = g_string_new(_("N/A")); LIBMTP_Clear_Errorstack(DeviceMgr.device); } // Storage. if (DeviceMgr.devicestorage == NULL) { if (LIBMTP_Get_Storage(DeviceMgr.device, 0) < 0) { // We have an error getting our storage, so let the user know and then disconnect the device. displayError("Failed to get storage parameters from the device - need to disconnect."); on_deviceConnect_activate(NULL, NULL); return; } if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { DeviceMgr.devicestorage = DeviceMgr.device->storage; } else { DeviceMgr.devicestorage = getCurrentDeviceStoragePtr(DeviceMgr.storagedeviceID); } // Supported filetypes; ret = LIBMTP_Get_Supported_Filetypes(DeviceMgr.device, &DeviceMgr.filetypes, &DeviceMgr.filetypes_len); if (ret != 0) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } } else { // Set to to none. g_fprintf(stderr, _("DevicePropeties: How did I get called?\n")); DeviceMgr.device = NULL; } } // ************************************************************************************************ /** * Deallocates the complete chain of the filelist. * @param filelist */ void clearDeviceFiles(LIBMTP_file_t * filelist) { if (filelist != NULL) { if (filelist->next != NULL) { clearDeviceFiles(filelist->next); filelist->next = NULL; } LIBMTP_destroy_file_t(filelist); } } // ************************************************************************************************ /** * Deallocates the complete chain of Album information. * @param albumlist */ void clearAlbumStruc(LIBMTP_album_t * albumlist) { if (albumlist != NULL) { if (albumlist->next != NULL) { clearAlbumStruc(albumlist->next); albumlist->next = NULL; } LIBMTP_destroy_album_t(albumlist); } } // ************************************************************************************************ /** * Deallocates the complete chain of all Playlists. * @param playlist_list */ void clearDevicePlaylist(LIBMTP_playlist_t * playlist_list) { if (playlist_list != NULL) { if (playlist_list->next != NULL) { clearDevicePlaylist(playlist_list->next); playlist_list->next = NULL; } LIBMTP_destroy_playlist_t(playlist_list); } } // ************************************************************************************************ /** * Deallocates the complete chain of all Track information. * @param tracklist */ void clearDeviceTracks(LIBMTP_track_t * tracklist) { if (tracklist != NULL) { if (tracklist->next != NULL) { clearDeviceTracks(tracklist->next); tracklist->next = NULL; } LIBMTP_destroy_track_t(tracklist); } } // ************************************************************************************************ void printFolders(LIBMTP_folder_t *fold) { while (fold != NULL) { g_printf("folder: %s\n", fold->name); if (fold->child != NULL) { printFolders(fold->child); } fold = fold->sibling; } } /** * Perform a rescan of the device, recreating any device properties or device information. */ void deviceRescan() { gchar* tmp_string; //g_print("You selected deviceRescan\n"); // First we clear the file and folder list... fileListClear(); folderListClear(); // Now clear the folder/file structures. if (deviceFolders != NULL) LIBMTP_destroy_folder_t(deviceFolders); if (deviceFiles != NULL) clearDeviceFiles(deviceFiles); // Add in track, playlist globals as well. deviceFolders = NULL; deviceFiles = NULL; // Now get started. if (DeviceMgr.deviceConnected) { // Get a list of folder on the device. deviceFolders = LIBMTP_Get_Folder_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); if (deviceFolders == NULL) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } // Now get a list of files from the device. if (!Preferences.use_alt_access_method) { deviceFiles = LIBMTP_Get_Filelisting_With_Callback(DeviceMgr.device, NULL, NULL); } else { // Alternate access method ONLY gets the files for the CURRENT FOLDER ONLY. This should help some Android based devices. deviceFiles = LIBMTP_Get_Files_And_Folders(DeviceMgr.device, DeviceMgr.devicestorage->id, currentFolderID); } if (deviceFiles == NULL) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } devicePlayLists = getPlaylists(); deviceTracks = getTracks(); fileListAdd(); folderListAdd(deviceFolders, NULL); // Now update the storage... if (DeviceMgr.devicestorage == NULL) { if (LIBMTP_Get_Storage(DeviceMgr.device, 0) < 0) { // We have an error getting our storage, so let the user know and then disconnect the device. displayError(_("Failed to get storage parameters from the device - need to disconnect.")); on_deviceConnect_activate(NULL, NULL); return; } if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { DeviceMgr.devicestorage = DeviceMgr.device->storage; } else { DeviceMgr.devicestorage = getCurrentDeviceStoragePtr(DeviceMgr.storagedeviceID); } } // Update the status bar. if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { if (DeviceMgr.devicestorage->StorageDescription != NULL) { tmp_string = g_strdup_printf(_("Connected to %s (%s) - %d MB free"), DeviceMgr.devicename->str, DeviceMgr.devicestorage->StorageDescription, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } } statusBarSet(tmp_string); g_free(tmp_string); } else { g_fprintf(stderr, _("Rescan: How did I get called?\n")); } } // ************************************************************************************************ /** * Find the ptr to the current storage structure. * @param StorageID * @return */ LIBMTP_devicestorage_t* getCurrentDeviceStoragePtr(gint StorageID) { LIBMTP_devicestorage_t* deviceStorage = DeviceMgr.device->storage; gint i = 0; // This is easy, as the gint is the number of hops we need to do to get to our target. if (StorageID == MTP_DEVICE_SINGLE_STORAGE) return DeviceMgr.device->storage; if (StorageID == 0) return DeviceMgr.device->storage; for (i = 0; i < StorageID; i++) { deviceStorage = deviceStorage->next; if (deviceStorage == NULL) // Oops, off the end return DeviceMgr.device->storage; } return deviceStorage; } // ************************************************************************************************ /** * Find the ID of the parent folder. * @param tmpfolder * @param currentFolderID * @return */ uint32_t getParentFolderID(LIBMTP_folder_t *tmpfolder, uint32_t currentFolderID) { uint32_t parentID = 0; if (tmpfolder == NULL) { return 0; } if (tmpfolder->folder_id == currentFolderID) { parentID = tmpfolder->parent_id; return parentID; } parentID = getParentFolderID(tmpfolder->child, currentFolderID); if (parentID != 0) return parentID; parentID = getParentFolderID(tmpfolder->sibling, currentFolderID); return parentID; } // ************************************************************************************************ /** * Find the structure of the parent MTP Folder based on the currentID. * @param tmpfolder * @param currentFolderID * @return */ LIBMTP_folder_t* getParentFolderPtr(LIBMTP_folder_t *tmpfolder, uint32_t currentFolderID) { LIBMTP_folder_t* parentID = NULL; if (tmpfolder == NULL) { return tmpfolder; } if (tmpfolder->parent_id == currentFolderID) { return tmpfolder; } parentID = getParentFolderPtr(tmpfolder->child, currentFolderID); if (parentID != NULL) return parentID; parentID = getParentFolderPtr(tmpfolder->sibling, currentFolderID); return parentID; } // ************************************************************************************************ /** * Find the structure of the MTP Folder based on the currentID. * @param tmpfolder * @param FolderID * @return */ LIBMTP_folder_t* getCurrentFolderPtr(LIBMTP_folder_t *tmpfolder, uint32_t FolderID) { LIBMTP_folder_t* parentID = NULL; if (tmpfolder == NULL) { return tmpfolder; } if (tmpfolder->folder_id == FolderID) { return tmpfolder; } parentID = getCurrentFolderPtr(tmpfolder->child, FolderID); if (parentID != NULL) return parentID; parentID = getCurrentFolderPtr(tmpfolder->sibling, FolderID); return parentID; } // ************************************************************************************************ /** * Get the list of files for the device. */ void filesUpateFileList() { if (deviceFiles != NULL) { clearDeviceFiles(deviceFiles); } deviceFiles = LIBMTP_Get_Files_And_Folders(DeviceMgr.device, DeviceMgr.devicestorage->id, currentFolderID); if (deviceFiles == NULL) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } // ************************************************************************************************ /** * Add a single file to the current connected device. * @param filename */ void filesAdd(gchar* filename) { uint64_t filesize = 0; gchar *filename_stripped; struct stat sb; LIBMTP_file_t *genfile = NULL; LIBMTP_track_t *trackfile = NULL; LIBMTP_album_t *albuminfo = NULL; LIBMTP_playlist_t* tmpplaylist = NULL; gint ret; // Maybe something went wrong, so we disconnected. If so, then simple exit.... if (DeviceMgr.deviceConnected == FALSE) return; if (stat(filename, &sb) == -1) { perror("stat"); return; } filesize = sb.st_size; if (filesize > DeviceMgr.devicestorage->FreeSpaceInBytes) { g_fprintf(stderr, _("Unable to add %s due to insufficient space: filesize = %llu, freespace = %llu\n"), filename, filesize, DeviceMgr.devicestorage->FreeSpaceInBytes); displayError(_("Unable to add file due to insufficient space")); return; } filename_stripped = basename(filename); displayProgressBar(_("File Upload")); setProgressFilename(g_strdup(filename_stripped)); // What we need to do is work what type of file we are sending // and either use the general file send, or // use the track send function. ret = find_filetype(filename_stripped); if ((ret == LIBMTP_FILETYPE_MP3) || (ret == LIBMTP_FILETYPE_OGG) || (ret == LIBMTP_FILETYPE_FLAC) || (ret == LIBMTP_FILETYPE_WMA)) { // We have an MP3/Ogg/FLAC/WMA file. trackfile = LIBMTP_new_track_t(); trackfile->filesize = filesize; trackfile->filename = g_strdup(filename_stripped); trackfile->filetype = find_filetype(filename_stripped); trackfile->parent_id = currentFolderID; trackfile->storage_id = DeviceMgr.devicestorage->id; trackfile->album = NULL; trackfile->title = NULL; trackfile->artist = NULL; trackfile->date = NULL; trackfile->genre = NULL; trackfile->tracknumber = 0; albuminfo = LIBMTP_new_album_t(); albuminfo->parent_id = currentFolderID; albuminfo->storage_id = DeviceMgr.devicestorage->id; albuminfo->album_id = 0; // Let's collect our metadata from the file, typically id3 tag data. switch (ret) { case LIBMTP_FILETYPE_MP3: // We have an MP3 file, so use id3tag to read the metadata. get_id3_tags(filename, trackfile); break; case LIBMTP_FILETYPE_OGG: get_ogg_tags(filename, trackfile); break; case LIBMTP_FILETYPE_FLAC: get_flac_tags(filename, trackfile); break; case LIBMTP_FILETYPE_WMA: get_asf_tags(filename, trackfile); break; //break; } // Add some data if it's all blank so we don't freak out some players. if (trackfile->album == NULL) trackfile->album = NULL; if (trackfile->title == NULL) trackfile->title = g_strdup(filename_stripped); if (trackfile->artist == NULL) trackfile->artist = g_strdup(_("")); if (trackfile->date == NULL) { trackfile->date = g_strdup(_("")); } else { if (strlen(trackfile->date) == 4) { // only have year part, so extend it. trackfile->date = g_strconcat(trackfile->date, "0101T000000", NULL); } } if (trackfile->genre == NULL) trackfile->genre = g_strdup(_("")); // Update our album info, if we actually have an album. if (trackfile->album != NULL) { albuminfo->name = g_strdup(trackfile->album); albuminfo->artist = g_strdup(trackfile->artist); albuminfo->composer = NULL; albuminfo->genre = g_strdup(trackfile->genre); } // If we need a playlist, then ask for it. if (addTrackPlaylistID == GMTP_REQUIRE_PLAYLIST) { addTrackPlaylistID = displayAddTrackPlaylistDialog(TRUE); } // Now send the track ret = LIBMTP_Send_Track_From_File(DeviceMgr.device, filename, trackfile, fileprogress, NULL); if (ret != 0) { // Report the error in sending the file. g_fprintf(stderr, _("Error sending track.\n")); displayError(g_strdup_printf(_("Error code %d sending track to device: %s"), ret, filename)); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } else { // Adjust device storage. DeviceMgr.devicestorage->FreeSpaceInBytes -= filesize; DeviceMgr.devicestorage->FreeSpaceInObjects--; // Only update Album data if transfer was successful. if (trackfile->album != NULL) { albumAddTrackToAlbum(albuminfo, trackfile); } // Now add to playlist if needed... if ((addTrackPlaylistID != GMTP_REQUIRE_PLAYLIST) && (addTrackPlaylistID != GMTP_NO_PLAYLIST)) { // addTrackPlaylistID has the ID of the playlist, and trackfile is the track we need to // add to that playlist. // Find the playlist. tmpplaylist = devicePlayLists; while (tmpplaylist != NULL) { if (tmpplaylist->playlist_id != (uint32_t) addTrackPlaylistID) { // Don't have it. tmpplaylist = tmpplaylist->next; } else { //We have found it. playlistAddTrack(tmpplaylist, trackfile); tmpplaylist = NULL; } } } } LIBMTP_destroy_track_t(trackfile); LIBMTP_destroy_album_t(albuminfo); } else { // Generic file upload. genfile = LIBMTP_new_file_t(); genfile->filesize = filesize; genfile->filename = g_strdup(filename_stripped); genfile->filetype = find_filetype(filename_stripped); genfile->parent_id = currentFolderID; genfile->storage_id = DeviceMgr.devicestorage->id; // Only import the file if it's not a Playlist or Album. (Bad mojo if this happens). if ((genfile->filetype != LIBMTP_FILETYPE_ALBUM) && (genfile->filetype != LIBMTP_FILETYPE_PLAYLIST)) { ret = LIBMTP_Send_File_From_File(DeviceMgr.device, filename, genfile, fileprogress, NULL); if (ret != 0) { g_fprintf(stderr, _("Error sending file %s.\n"), filename); displayError(g_strconcat(_("Error sending file:"), " ", filename, "", NULL)); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } else { // Adjust device storage. DeviceMgr.devicestorage->FreeSpaceInBytes -= filesize; DeviceMgr.devicestorage->FreeSpaceInObjects--; } } LIBMTP_destroy_file_t(genfile); } destroyProgressBar(); // Now update the storage... if (DeviceMgr.devicestorage == NULL) { if (LIBMTP_Get_Storage(DeviceMgr.device, 0) < 0) { // We have an error getting our storage, so let the user know and then disconnect the device. displayError("Failed to get storage parameters from the device - need to disconnect."); on_deviceConnect_activate(NULL, NULL); return; } if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { DeviceMgr.devicestorage = DeviceMgr.device->storage; } else { DeviceMgr.devicestorage = getCurrentDeviceStoragePtr(DeviceMgr.storagedeviceID); } } } // ************************************************************************************************ /** * Delete a single file from the connected device. * @param filename * @param objectID */ void filesDelete(gchar* filename, uint32_t objectID) { gint ret = 1; GSList *node; FileListStruc *fileptr; uint64_t filesize = 0; // Maybe something went wrong, so we disconnected. If so, then simple exit.... if (DeviceMgr.deviceConnected == FALSE) return; // Now remove the item from the searchlist if we are in search mode. if (inFindMode == TRUE) { // searchList node = searchList; while (node != NULL) { fileptr = node->data; if (fileptr->itemid == objectID) { // remove this node from the main list; searchList = g_slist_delete_link(searchList, node); g_free_search(fileptr); node = NULL; } else { node = node->next; } } } // Get the filesize of the object. LIBMTP_file_t * files = deviceFiles; while (files != NULL) { if (files->item_id == objectID) { filesize = files->filesize; files = NULL; } else { files = files->next; } } // Delete the file based on the object ID. ret = LIBMTP_Delete_Object(DeviceMgr.device, objectID); if (ret != 0) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); g_fprintf(stderr, _("\nFailed to delete file %s\n"), filename); displayError(g_strconcat(_("Failed to delete file"), " ", filename, "", NULL)); } else { // Adjust device storage. DeviceMgr.devicestorage->FreeSpaceInBytes += filesize; DeviceMgr.devicestorage->FreeSpaceInObjects++; } } // ************************************************************************************************ /** * Download a file from the device to local storage. * @param filename * @param objectID */ void filesDownload(gchar* filename, uint32_t objectID) { gchar* fullfilename = NULL; // Maybe something went wrong, so we disconnected. If so, then simple exit.... if (DeviceMgr.deviceConnected == FALSE) return; displayProgressBar(_("File download")); setProgressFilename(filename); // Download the file based on the objectID. fullfilename = g_strdup_printf("%s/%s", Preferences.fileSystemDownloadPath->str, filename); if (LIBMTP_Get_File_To_File(DeviceMgr.device, objectID, fullfilename, fileprogress, NULL) != 0) { g_fprintf(stderr, _("\nError getting file from MTP device.\n")); displayError(_("Error getting file from MTP device.")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } destroyProgressBar(); g_free(fullfilename); } // ************************************************************************************************ /** * Rename a single file on the device. * @param filename - New name to be given to the file. * @param ObjectID - The ID of the object. */ void filesRename(gchar* filename, uint32_t ObjectID) { // We must first determine, if this is a file, a folder, playlist or album // and use the correct API. LIBMTP_file_t *genfile = NULL; LIBMTP_album_t *albuminfo = NULL; LIBMTP_album_t *albumlist = NULL; LIBMTP_playlist_t *playlist = NULL; LIBMTP_folder_t *folder = NULL; GSList *node; FileListStruc *fileptr; if (filename == NULL) { return; } if (ObjectID == 0) { return; } // Now remove the item from the searchlist if we are in search mode. if (inFindMode == TRUE) { // searchList node = searchList; while (node != NULL) { fileptr = node->data; if (fileptr->itemid == ObjectID) { // update the filename appropriately; g_free(fileptr->filename); fileptr->filename = g_strdup(filename); node = NULL; } else { node = node->next; } } } // Lets scan files first. genfile = deviceFiles; while (genfile != NULL) { if (genfile->item_id == ObjectID) { // We have our file, so update it. LIBMTP_Set_File_Name(DeviceMgr.device, genfile, filename); deviceRescan(); return; } genfile = genfile->next; } // Lets scan our albums. albuminfo = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); albumlist = albuminfo; while (albuminfo != NULL) { if (albuminfo->album_id == ObjectID) { LIBMTP_Set_Album_Name(DeviceMgr.device, albuminfo, filename); deviceRescan(); clearAlbumStruc(albumlist); return; } albuminfo = albuminfo->next; } clearAlbumStruc(albumlist); // Let's scan our playlists. playlist = devicePlayLists; while (playlist != NULL) { if (playlist->playlist_id == ObjectID) { // We have our playlist, so update it. LIBMTP_Set_Playlist_Name(DeviceMgr.device, playlist, filename); deviceRescan(); return; } playlist = playlist->next; } // Lets scan our folders; folder = deviceFolders; folder = LIBMTP_Find_Folder(folder, ObjectID); if (folder != NULL) { LIBMTP_Set_Folder_Name(DeviceMgr.device, folder, filename); deviceRescan(); return; } } // ************************************************************************************************ /** * Create a folder on the current connected device. * @param foldername * @return Object ID of new folder, otherwise 0 if failed. */ guint32 folderAdd(gchar* foldername) { guint32 res = LIBMTP_Create_Folder(DeviceMgr.device, foldername, currentFolderID, DeviceMgr.devicestorage->id); if (res == 0) { g_fprintf(stderr, _("Folder creation failed: %s\n"), foldername); displayError(g_strconcat(_("Folder creation failed:"), " ", foldername, "", NULL)); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } return res; } // ************************************************************************************************ /** * Delete a single folder from the currently connected device. * @param folderptr * @param level Set to 0 as default. */ void folderDelete(LIBMTP_folder_t* folderptr, guint level) { GSList *node; FileListStruc *fileptr; if (folderptr == NULL) { // Sanity check for rogue data or exit here operation, that is no child/sibling to work on. return; } // This is fun, as we have to find all child folder, delete those, as well as all files contained within... // First iteratate through all child folders and remove those files in those folders. // But first we need to get the folder structure pointer based on the objectID, so we know where to start. // So now we have our structure to the current select folder, so we need to cycle through all children and remove any files contained within. folderDeleteChildrenFiles(folderptr->folder_id); // Now cycle through folders contained in this folder and delete those; folderDelete(folderptr->child, level + 1); if (level != 0) folderDelete(folderptr->sibling, level + 1); // That should clear all the children. // Now remove the item from the searchlist if we are in search mode. if (inFindMode == TRUE) { // searchList node = searchList; while (node != NULL) { fileptr = node->data; if (fileptr->itemid == folderptr->folder_id) { // remove this node from the main list; searchList = g_slist_delete_link(searchList, node); g_free_search(fileptr); node = NULL; } else { node = node->next; } } } // Now do self. guint res = LIBMTP_Delete_Object(DeviceMgr.device, folderptr->folder_id); if (res != 0) { g_fprintf(stderr, _("Couldn't delete folder %s (%x)\n"), folderptr->name, folderptr->folder_id); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } else { // Adjust device storage. DeviceMgr.devicestorage->FreeSpaceInObjects++; } } // ************************************************************************************************ /** * Delete all files from the specified folder on the device. * @param folderID */ void folderDeleteChildrenFiles(guint folderID) { LIBMTP_file_t* files = deviceFiles; while (files != NULL) { if (files->parent_id == folderID) { filesDelete(files->filename, files->item_id); } files = files->next; } } // ************************************************************************************************ /** * Download the defined folder to the local device. * @param foldername Name of the folder to be downloaded * @param folderID ID of the folder on the device * @param isParent TRUE, if this is the parent folder to download (eg ignore any folder siblings). */ void folderDownload(gchar *foldername, uint32_t folderID, gboolean isParent) { gchar* fullfilename = NULL; LIBMTP_folder_t* currentFolder = NULL; LIBMTP_file_t* tmpFiles = NULL; // Store our current path for safe keeping and generate our new path. GString *currentdownload_folder = g_string_new(Preferences.fileSystemDownloadPath->str); fullfilename = g_strdup_printf("%s/%s", Preferences.fileSystemDownloadPath->str, foldername); // See if folder exists, if not create it. if (g_file_test(fullfilename, G_FILE_TEST_IS_DIR) == FALSE) { if (mkdir(fullfilename, S_IRWXU | S_IRWXG | S_IRWXO) != 0) { g_fprintf(stderr, _("Folder creation failed: %s\n"), fullfilename); displayError(g_strconcat(_("Folder creation failed:"), " ", fullfilename, "", NULL)); // Since we can't create that directory, then we simple return from this call... return; } } // First we scan for all folders in the current folder, and do those first... if (folderID != 0) { currentFolder = getCurrentFolderPtr(deviceFolders, folderID); } else { // If our id = 0 then we are dealing with the root device. currentFolder = deviceFolders; } if (currentFolder == NULL) { // This means we don't exist, so bail out. return; } if (currentFolder->child != NULL) { // Now we can simple set our download path to the new path, and save all folders/files there. Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, fullfilename); // Call to download all folder/files in that folder... folderDownload(currentFolder->child->name, currentFolder->child->folder_id, FALSE); // Restore the old download path; Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, currentdownload_folder->str); } if ((isParent == FALSE) || (folderID == 0)) { if (currentFolder->sibling != NULL) { folderDownload(currentFolder->sibling->name, currentFolder->sibling->folder_id, FALSE); } } // Now download all the files whose parentID = folderID; // Now we can simple set our download path to the new path, and save all folders/files there. Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, fullfilename); // we no longer need the full folder path, so free it's variable. g_free(fullfilename); // Start processing all our files. tmpFiles = deviceFiles; while (tmpFiles != NULL) { if ((tmpFiles->parent_id == folderID) && (tmpFiles->storage_id == DeviceMgr.devicestorage->id)) { // We have a file in this folder, so download it... // But first check to see if it exists, before overwriting it... fullfilename = g_strdup_printf("%s/%s", Preferences.fileSystemDownloadPath->str, tmpFiles->filename); // Check if file exists? if (access(fullfilename, F_OK) != -1) { // We have that file already? if ((Preferences.prompt_overwrite_file_op == TRUE)) { if (fileoverwriteop == MTP_ASK) { fileoverwriteop = displayFileOverwriteDialog(tmpFiles->filename); } switch (fileoverwriteop) { case MTP_ASK: break; case MTP_SKIP: fileoverwriteop = MTP_ASK; break; case MTP_SKIP_ALL: break; case MTP_OVERWRITE: filesDownload(tmpFiles->filename, tmpFiles->item_id); fileoverwriteop = MTP_ASK; break; case MTP_OVERWRITE_ALL: filesDownload(tmpFiles->filename, tmpFiles->item_id); break; } } else { filesDownload(tmpFiles->filename, tmpFiles->item_id); } } else { filesDownload(tmpFiles->filename, tmpFiles->item_id); } // Free our file name... g_free(fullfilename); } // Start working on the next file... tmpFiles = tmpFiles->next; } // Restore the old download path; Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, currentdownload_folder->str); // Clean up any tmp items; g_string_free(currentdownload_folder, TRUE); } // ************************************************************************************************ /** * Find the file type based on extension * @param filename * @return */ LIBMTP_filetype_t find_filetype(const gchar * filename) { LIBMTP_filetype_t filetype = LIBMTP_FILETYPE_UNKNOWN; gchar *fileext; gint i; gint j = sizeof (file_ext) / sizeof (MTP_file_ext_struct); fileext = rindex(filename, '.'); // This accounts for the case with a filename without any "." (period). if (!fileext) { fileext = ""; } else { ++fileext; } // Now cycle through the array of extensions, and get the associated // libmtp filetype. for (i = 0; i < j; i++) { if (g_ascii_strcasecmp(fileext, file_ext[i].file_extension) == 0) { filetype = file_ext[i].file_type; break; } } //if (filetype == 0) { // filetype = LIBMTP_FILETYPE_UNKNOWN; //} return filetype; } // ************************************************************************************************ /** * Get the file extension based on filetype * @param filetype * @return */ gchar* find_filetype_ext(LIBMTP_filetype_t filetype) { gint i = 0; gint j = sizeof (file_ext) / sizeof (MTP_file_ext_struct); for (i = 0; i < j; i++) { if (filetype == file_ext[i].file_type) { return file_ext[i].file_extension; } } return blank_ext; } // ************************************************************************************************ /** * Set the friendly name of the current connected device. * @param devicename */ void setDeviceName(gchar* devicename) { gint res = 0; if (DeviceMgr.deviceConnected == TRUE) { if (devicename != NULL) res = LIBMTP_Set_Friendlyname(DeviceMgr.device, devicename); if (res != 0) { g_fprintf(stderr, _("Error: Couldn't set device name to %s\n"), devicename); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { // Set to to none. g_fprintf(stderr, _("setDeviceName: How did I get called?\n")); } } // ************************************************************************************************ /** * Check to see if this file already exists within the current folder on the device. * @param filename * @return */ gboolean fileExists(gchar* filename) { // What we have to go is scan the entire file tree looking for // entries in the same folder as the current and the same // storage pool, then we do a string compare (since doing a string // compare is so much slower than comparing a few numbers). LIBMTP_file_t* tmpfile; tmpfile = deviceFiles; while (tmpfile != NULL) { // Check for matching folder ID and storage ID. if ((tmpfile->parent_id == currentFolderID) && (tmpfile->storage_id == DeviceMgr.devicestorage->id)) { // Now test for the file name (do case insensitive cmp for those odd devices); if (g_ascii_strcasecmp(filename, tmpfile->filename) == 0) return TRUE; } tmpfile = tmpfile->next; } return FALSE; } // ************************************************************************************************ /** * Add the specified track to an album, and return that album information. * @param albuminfo * @param trackinfo */ void albumAddTrackToAlbum(LIBMTP_album_t* albuminfo, LIBMTP_track_t* trackinfo) { LIBMTP_album_t *album = NULL; LIBMTP_album_t *found_album = NULL; LIBMTP_album_t *album_orig = NULL; gint ret = 0; // Quick sanity check. if ((albuminfo->name == NULL) || (albuminfo->artist == NULL)) return; // Lets try to find the album. album = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); album_orig = album; while ((album != NULL) && (found_album == NULL)) { if ((album->name != NULL) && (album->artist != NULL)) { // Lets test it. We attempt to match both album name and artist. if ((g_ascii_strcasecmp(album->name, albuminfo->name) == 0) && (g_ascii_strcasecmp(album->artist, albuminfo->artist) == 0)) { found_album = album; } } album = album->next; } // Some devices ignore all other fields and only retain the ablum name - so test for this as well! album = album_orig; if (found_album == NULL) { while ((album != NULL) && (found_album == NULL)) { if (album->name != NULL) { // Lets test it. We attempt to match both album name and artist. if (g_ascii_strcasecmp(album->name, albuminfo->name) == 0) { found_album = album; } } album = album->next; } } if (found_album != NULL) { // The album already exists. uint32_t *tracks; tracks = (uint32_t *) g_malloc0((found_album->no_tracks + 1) * sizeof (uint32_t)); if (!tracks) { g_fprintf(stderr, _("ERROR: Failed memory allocation in albumAddTrackToAlbum()\n")); return; } found_album->no_tracks++; if (found_album->tracks != NULL) { memcpy(tracks, found_album->tracks, found_album->no_tracks * sizeof (uint32_t)); free(found_album->tracks); } tracks[found_album->no_tracks - 1] = trackinfo->item_id; // This ID is only set once the track is on the device. found_album->tracks = tracks; ret = LIBMTP_Update_Album(DeviceMgr.device, found_album); g_free(tracks); found_album->tracks = NULL; } else { // New album. uint32_t *trackid; trackid = (uint32_t *) g_malloc0(sizeof (uint32_t)); *trackid = trackinfo->item_id; albuminfo->tracks = trackid; albuminfo->no_tracks = 1; albuminfo->storage_id = DeviceMgr.devicestorage->id; ret = LIBMTP_Create_New_Album(DeviceMgr.device, albuminfo); g_free(trackid); albuminfo->tracks = NULL; } if (ret != 0) { if (Preferences.suppress_album_errors == FALSE) { if (AlbumErrorIgnore == FALSE) { displayError(_("Error creating or updating album.\n(This could be due to that your device does not support albums.)\n")); g_fprintf(stderr, _("Error creating or updating album.\n(This could be due to that your device does not support albums.)\n")); } } // Displayed the message once already per transfer... AlbumErrorIgnore = TRUE; LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } clearAlbumStruc(album_orig); } // ************************************************************************************************ /** * Add album artfile to be associated with an album. * @param album_id * @param filename */ void albumAddArt(guint32 album_id, gchar* filename) { LIBMTP_filesampledata_t *albumart; gint ret; uint64_t filesize; uint8_t *imagedata = NULL; struct stat statbuff; FILE* fd; if (stat(filename, &statbuff) == -1) { perror("stat"); return; } filesize = (uint64_t) statbuff.st_size; imagedata = g_malloc(filesize * sizeof (uint8_t)); if (imagedata == NULL) { g_fprintf(stderr, _("ERROR: Failed memory allocation in albumAddArt()\n")); return; } fd = fopen(filename, "r"); if (fd == NULL) { g_fprintf(stderr, _("Couldn't open image file %s\n"), filename); g_free(imagedata); return; } else { fread(imagedata, filesize, 1, fd); fclose(fd); } albumart = LIBMTP_new_filesampledata_t(); albumart->data = (gchar *) imagedata; albumart->size = filesize; albumart->filetype = find_filetype(basename(filename)); ret = LIBMTP_Send_Representative_Sample(DeviceMgr.device, album_id, albumart); if (ret != 0) { g_fprintf(stderr, _("Couldn't send album art\n")); displayError(_("Couldn't send album art\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } g_free(imagedata); albumart->data = NULL; // Adjust device storage. DeviceMgr.devicestorage->FreeSpaceInBytes -= filesize; DeviceMgr.devicestorage->FreeSpaceInObjects--; LIBMTP_destroy_filesampledata_t(albumart); } // ************************************************************************************************ /** * Retrieves the raw image data for the selected album * @return Pointer to the image data. */ LIBMTP_filesampledata_t * albumGetArt(LIBMTP_album_t* selectedAlbum) { LIBMTP_filesampledata_t *albumart = LIBMTP_new_filesampledata_t(); gint ret; // Attempt to get some data ret = LIBMTP_Get_Representative_Sample(DeviceMgr.device, selectedAlbum->album_id, albumart); if (ret != 0) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); LIBMTP_destroy_filesampledata_t(albumart); return NULL; } if (albumart == NULL) { // Something went wrong; return NULL; } return albumart; } // ************************************************************************************************ /** * Retrieves the raw image data for the selected album * @return Pointer to the image data. */ void albumDeleteArt(guint32 album_id) { LIBMTP_filesampledata_t *albumart = LIBMTP_new_filesampledata_t(); // Attempt to send a null representative sample. albumart->data = NULL; albumart->size = 0; albumart->filetype = LIBMTP_FILETYPE_UNKNOWN; gint ret = LIBMTP_Send_Representative_Sample(DeviceMgr.device, album_id, albumart); if (ret != 0) { g_fprintf(stderr, _("Couldn't remove album art\n")); displayError(_("Couldn't remove album art\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } LIBMTP_destroy_filesampledata_t(albumart); } // ************************************************************************************************ /** * Retrieve all Playlists from the device. * @return Linked list of all playlists. */ LIBMTP_playlist_t* getPlaylists(void) { if (devicePlayLists != NULL) clearDevicePlaylist(devicePlayLists); devicePlayLists = LIBMTP_Get_Playlist_List(DeviceMgr.device); if (devicePlayLists == NULL) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } return devicePlayLists; } // ************************************************************************************************ /** * Retrieve all Tracks from the device. * @return Linked list of all tracks. */ LIBMTP_track_t* getTracks(void) { if (deviceTracks != NULL) clearDeviceTracks(deviceTracks); deviceTracks = LIBMTP_Get_Tracklisting_With_Callback(DeviceMgr.device, NULL, NULL); if (deviceTracks == NULL) { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } return deviceTracks; } // ************************************************************************************************ /** * Create a new playlist on the device. * @param playlistname */ void playlistAdd(gchar* playlistname) { LIBMTP_playlist_t *playlist = LIBMTP_new_playlist_t(); playlist->name = g_strdup(playlistname); playlist->no_tracks = 0; playlist->tracks = NULL; playlist->parent_id = DeviceMgr.device->default_playlist_folder; playlist->storage_id = DeviceMgr.devicestorage->id; gint ret = LIBMTP_Create_New_Playlist(DeviceMgr.device, playlist); if (ret != 0) { displayError(_("Couldn't create playlist object\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } LIBMTP_destroy_playlist_t(playlist); } // ************************************************************************************************ /** * Delete the selected playlist from the device. * @param tmpplaylist */ void playlistDelete(LIBMTP_playlist_t * tmpplaylist) { guint res = LIBMTP_Delete_Object(DeviceMgr.device, tmpplaylist->playlist_id); if (res != 0) { displayError(_("Deleting playlist failed?\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } // ************************************************************************************************ /** * Update the selected playlist with the new information. * @param tmpplaylist */ void playlistUpdate(LIBMTP_playlist_t * tmpplaylist) { guint res = LIBMTP_Update_Playlist(DeviceMgr.device, tmpplaylist); if (res != 0) { displayError(_("Updating playlist failed?\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } // ************************************************************************************************ /** * Import a playlist into the current device. * @param filename Filename of the playlist to import. * @return The Playlist Name as stored in the file. Caller to free when no longer needed. */ gchar* playlistImport(gchar * filename) { FILE* fd; gchar* playlistname = NULL; gchar* fileString = NULL; gchar* needle = NULL; uint32_t *tracktmp = NULL; uint32_t fileobject = 0; gboolean ignorepath = Preferences.ignore_path_in_playlist_import; // Build basic playlist object LIBMTP_playlist_t *playlist = LIBMTP_new_playlist_t(); playlist->name = NULL; playlist->no_tracks = 0; playlist->tracks = NULL; playlist->parent_id = DeviceMgr.device->default_playlist_folder; playlist->storage_id = DeviceMgr.devicestorage->id; // Load the file, and parse. fd = fopen(filename, "r"); if (fd == NULL) { g_fprintf(stderr, _("Couldn't open playlist file %s\n"), filename); displayError(_("Couldn't open playlist file\n")); LIBMTP_destroy_playlist_t(playlist); return NULL; } else { fileString = g_malloc0(GMTP_MAX_STRING); // Read file until EOF while (fgets(fileString, GMTP_MAX_STRING, fd) != NULL) { // Strip any trailing '\n' from the string... fileString = g_strchomp(fileString); if (g_ascii_strncasecmp(fileString, "#GMTPPLA: ", 10) == 0) { // We have a playlist name marker... playlistname = g_strdup((fileString + 10)); playlist->name = g_strdup(playlistname); } else { // We should have a file? // But ignore ANY line starting with a # as this is a comment line. if ((*fileString != '#') && (*fileString != '\0')) { fileobject = getFileID(fileString, ignorepath); if (fileobject != 0) { // We have a file within our device! playlist->no_tracks++; if ((tracktmp = g_realloc(playlist->tracks, sizeof (uint32_t) * (playlist->no_tracks))) == NULL) { g_fprintf(stderr, _("realloc in playlistImport failed\n")); displayError(_("Updating playlist failed? 'realloc in playlistImport'\n")); return NULL; } playlist->tracks = tracktmp; playlist->tracks[(playlist->no_tracks - 1)] = fileobject; } } } } g_free(fileString); fclose(fd); } if ((playlistname == NULL) && (playlist->no_tracks > 0)) { // We have some tracks, but no playlist name. // So derive the playlist name from the filename... playlistname = g_path_get_basename(filename); // Now chop off the file extension? needle = g_strrstr(playlistname, "."); if (needle != NULL) { *needle = '\0'; } // And set the name... playlist->name = g_strdup(playlistname); } // If we have something? if ((playlistname != NULL) && (playlist->no_tracks > 0)) { // Store the playlist on the device... gint ret = LIBMTP_Create_New_Playlist(DeviceMgr.device, playlist); if (ret != 0) { displayError(_("Couldn't create playlist object\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); g_free(playlistname); playlistname = NULL; } else { displayInformation(_("Playlist imported.\n")); } } else { // Let the user know we found zero tracks, so didn't bother to import it. displayInformation(_("Found no tracks within the playlist that exist on this device. Did not import the playlist.\n")); g_fprintf(stderr, _("Found no tracks within the playlist that exist on this device. Did not import the playlist.\n")); // Clean up the playlist name, since we don't need it. if (playlistname != NULL) { g_free(playlistname); playlistname = NULL; } } // Clean up our playlist data structure. LIBMTP_destroy_playlist_t(playlist); // Return to caller. return playlistname; } // ************************************************************************************************ /** * Export a playlist. * @param filename Filename of the playlist to import. * @param playlist Pointer to Playlist to export. */ void playlistExport(gchar * filename, LIBMTP_playlist_t * playlist) { FILE* fd; uint32_t numtracks = playlist->no_tracks; uint32_t *tracks = playlist->tracks; uint32_t trackid = 0; gchar* trackname = NULL; // Open the file to save it to... fd = fopen(filename, "w"); if (fd == NULL) { g_fprintf(stderr, _("Couldn't save playlist file %s\n"), filename); displayError(_("Couldn't save playlist file\n")); } else { fprintf(fd, "#GMTPPLA: %s\n", playlist->name); fflush(fd); while (numtracks--) { trackid = *tracks++; // We now have our track id. Let's form the complete path to the file including the filename. // Then store that string in the file... trackname = getFullFilename(trackid); if (trackname != NULL) { fprintf(fd, "%s\n", trackname); g_free(trackname); trackname = NULL; } } fclose(fd); } } // ************************************************************************************************ /** * Format the current active device/storage partition. */ void formatStorageDevice() { if (DeviceMgr.deviceConnected) { guint res = LIBMTP_Format_Storage(DeviceMgr.device, DeviceMgr.devicestorage); if (res != 0) { displayError(_("Format Device failed?\n")); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { g_fprintf(stderr, ("formatStorageDevice: How did I get called?\n")); } } // ************************************************************************************************ /** * Add the assigned track to the nominated playlist. * @param playlist * @param track */ void playlistAddTrack(LIBMTP_playlist_t* playlist, LIBMTP_track_t* track) { LIBMTP_playlist_t* tmpplaylist = playlist; uint32_t *tmp = NULL; tmpplaylist->no_tracks++; // item_id = our track num... so append to tmpplaylist->tracks if ((tmp = g_realloc(tmpplaylist->tracks, sizeof (uint32_t) * (tmpplaylist->no_tracks))) == NULL) { g_fprintf(stderr, _("realloc in savePlayList failed\n")); displayError(_("Updating playlist failed? 'realloc in savePlayList'\n")); return; } tmpplaylist->tracks = tmp; tmpplaylist->tracks[(tmpplaylist->no_tracks - 1)] = track->item_id; playlistUpdate(tmpplaylist); } // ************************************************************************************************ /** * Remove the assigned track from a playlist. * @param playlist * @param track */ void playlistRemoveTrack(LIBMTP_playlist_t* playlist, LIBMTP_track_t* track, uint32_t instances) { LIBMTP_playlist_t* tmpplaylist = playlist; uint32_t *tmp = NULL; uint32_t numtracks = tmpplaylist->no_tracks; int32_t count; int32_t count2; // item_id = our track num... so remove to tmpplaylist->tracks if ((instances == MTP_PLAYLIST_ALL_INSTANCES) || (instances == MTP_PLAYLIST_FIRST_INSTANCE)) { for (count = 0; count < (int32_t) numtracks; count++) { if (tmpplaylist->tracks[count] == track->item_id) { // move all ones up one. for (count2 = count; count2 < (int32_t) numtracks; count2++) { if ((count2 + 1) != (int32_t) numtracks) { tmpplaylist->tracks[count2] = tmpplaylist->tracks[count2 + 1]; } } // exit the for loop if only doing the first instance. if (instances == MTP_PLAYLIST_FIRST_INSTANCE) { count = numtracks; } tmpplaylist->no_tracks--; } } } else { for (count = numtracks - 1; count >= 0; count--) { if (tmpplaylist->tracks[count] == track->item_id) { // move all ones up one. for (count2 = count; count2 < (int32_t) numtracks; count2++) { if ((count2 + 1) != (int32_t) numtracks) { tmpplaylist->tracks[count2] = tmpplaylist->tracks[count2 + 1]; } } // exit the for loop if only doing the first instance. if (instances == MTP_PLAYLIST_LAST_INSTANCE) { count = numtracks; } tmpplaylist->no_tracks--; } } } // And redo the memory allocation. if ((tmp = g_realloc(tmpplaylist->tracks, sizeof (uint32_t) * (tmpplaylist->no_tracks))) == NULL) { g_fprintf(stderr, _("realloc in savePlayList failed\n")); displayError(_("Updating playlist failed? 'realloc in savePlayList'\n")); return; } tmpplaylist->tracks = tmp; playlistUpdate(tmpplaylist); } // ************************************************************************************************ /** * Return the full filename including path of the selected MTP file object * @param trackid MTP file objectID * @return */ gchar* getFullFilename(uint32_t item_id) { gchar* fullfilename = NULL; gchar* tmpfilename = NULL; uint32_t parent_id = 0; LIBMTP_file_t* tmpfile = deviceFiles; LIBMTP_folder_t* tmpfolder = deviceFolders; // Find our file... while (tmpfile != NULL) { if (tmpfile->item_id == item_id) { fullfilename = g_strdup(tmpfile->filename); parent_id = tmpfile->parent_id; tmpfile = NULL; } else { tmpfile = tmpfile->next; } } // Let's see if we have a filename? if (fullfilename != NULL) { // Now let's prepend the parent folder names to it... while (tmpfolder != NULL) { tmpfolder = getCurrentFolderPtr(deviceFolders, parent_id); if (tmpfolder != NULL) { // We have something. tmpfilename = g_strdup_printf("%s/%s", tmpfolder->name, fullfilename); g_free(fullfilename); fullfilename = tmpfilename; parent_id = tmpfolder->parent_id; } } } return fullfilename; } // ************************************************************************************************ /** * Find the file within the device. * @param filename Name of the file to search for. * @param ignorepath Ignore any path information that may be present. * @return Object ID or 0 if not found. */ uint32_t getFileID(gchar* filename, gboolean ignorepath) { LIBMTP_file_t* files = deviceFiles; uint32_t folderID = 0; // Separate into filename and path... gchar* basefilename = g_path_get_basename(filename); gchar* dirfilename = g_path_get_dirname(filename); // If we are ignoring all path information, then simply scan all files for the filename. if (ignorepath == TRUE) { while (files != NULL) { // Ensure we only check if we are on the current storage device... if (files->storage_id == DeviceMgr.devicestorage->id) { // See if our filename is the same. if (g_ascii_strcasecmp(basefilename, files->filename) == 0) { // We found our file... g_free(basefilename); g_free(dirfilename); return files->item_id; } } files = files->next; } } else { // Lets find the folderid of the path we have, so it makes searching a lot easier... folderID = getFolderID(deviceFolders, dirfilename); if ((int32_t) folderID == -1) { // We don't have this path on the device, so no need to continue checking. g_free(basefilename); g_free(dirfilename); return 0; } while (files != NULL) { // Ensure we only check if we are on the current storage device AND the file is in the correct folder... if ((files->storage_id == DeviceMgr.devicestorage->id) && (files->parent_id == folderID)) { // See if our filename is the same. if (g_ascii_strcasecmp(basefilename, files->filename) == 0) { // We found our file... g_free(basefilename); g_free(dirfilename); return files->item_id; } } files = files->next; } } g_free(basefilename); g_free(dirfilename); return 0; } // ************************************************************************************************ /** * Find the folder within the device. * @param folderptr Folder Structure to search in. * @param foldername Name of the folder to find. * @return Object ID or -1 if not found. */ uint32_t getFolderID(LIBMTP_folder_t* folderptr, gchar* foldername) { gchar** pathcomponents; if (g_ascii_strcasecmp(foldername, ".") == 0) { // We have a root directory... return 0; } // Get the first component of the foldername. pathcomponents = g_strsplit(foldername, "/", 2); while (folderptr != NULL) { if (g_ascii_strcasecmp(pathcomponents[0], folderptr->name) == 0) { // We have found our path... // If we have of the path to process then... if (pathcomponents[1] != NULL) { return (getFolderID(folderptr->child, pathcomponents[1])); } else { return folderptr->folder_id; } } else { folderptr = folderptr->sibling; } } return -1; } // ************************************************************************************************ /** * Returns the string holding the full path name for the given folderid. * @param folderid - the selected path to be returned. * @return The full path name. */ gchar* getFullFolderPath(uint32_t folderid) { gchar* fullfilename = g_strdup(""); gchar* tmpfilename = NULL; uint32_t parent_id = folderid; guint stringlength = 0; LIBMTP_folder_t* tmpfolder = deviceFolders; while (tmpfolder != NULL) { tmpfolder = getCurrentFolderPtr(deviceFolders, parent_id); if (tmpfolder != NULL) { // We have something. tmpfilename = g_strdup_printf("%s/%s", tmpfolder->name, fullfilename); g_free(fullfilename); fullfilename = tmpfilename; parent_id = tmpfolder->parent_id; } } // Add in leading slash if needed if (*fullfilename != '/') { tmpfilename = g_strdup_printf("/%s", fullfilename); g_free(fullfilename); fullfilename = tmpfilename; } // Remove trailing slash if needed. stringlength = strlen(fullfilename); if (stringlength > 1) { fullfilename[stringlength - 1] = '\0'; } return fullfilename; } // ************************************************************************************************ /** * Returns a list of files/folders that have been found on the device. * @param searchstring - string to search * @param searchfiles - search files/folder names. * @param searchmeta - search file metadata. * @return */ GSList *filesSearch(gchar *searchstring, gboolean searchfiles, gboolean searchmeta) { GSList *list = NULL; GPatternSpec *pspec = g_pattern_spec_new(searchstring); LIBMTP_file_t *files = deviceFiles; GSList *folderIDs = NULL; LIBMTP_track_t *tracks = deviceTracks; FileListStruc *filestruc = NULL; LIBMTP_track_t *trackinfo; gchar *tmpstring1 = NULL; gchar *tmpstring2 = NULL; gchar *tmpstring3 = NULL; gchar *tmpstring4 = NULL; uint32_t tmpFolderID = currentFolderID; // if using alt method, we cycle through all folders on the current storage device... if (Preferences.use_alt_access_method) { currentFolderID = 0; filesUpateFileList(); //buildFolderIDs(&folderIDs, deviceFolders); } if (searchfiles == TRUE) { // Search folders // ignore folder search in alt access mode... if (!Preferences.use_alt_access_method) { folderSearch(pspec, &list, deviceFolders); } // Search files. while (files != NULL) { if ((files->storage_id == DeviceMgr.devicestorage->id)) { if (files->filetype == LIBMTP_FILETYPE_FOLDER) { // Add this folder to the list to be searched. folderIDs = g_slist_append(folderIDs, &files->item_id); } // Make search case insensitive. tmpstring1 = g_utf8_strup(files->filename, -1); if (g_pattern_match_string(pspec, tmpstring1) == TRUE) { g_free(tmpstring1); tmpstring1 = NULL; // We have found a matching string... filestruc = g_malloc(sizeof (FileListStruc)); if (filestruc == NULL) { g_fprintf(stderr, _("malloc in filesSearch failed\n")); displayError(_("Failed searching? 'malloc in filesSearch'\n")); return list; } filestruc->filename = g_strdup(files->filename); filestruc->filesize = files->filesize; if (files->filetype == LIBMTP_FILETYPE_FOLDER) { filestruc->isFolder = TRUE; } else { filestruc->isFolder = FALSE; } filestruc->itemid = files->item_id; filestruc->filetype = files->filetype; filestruc->location = getFullFolderPath(files->parent_id); list = g_slist_append(list, filestruc); } else if (searchmeta == TRUE) { // Now if it's a track type file, eg OGG, WMA, MP3 or FLAC, get it's metadata. // search case insensitive. if ((files->filetype == LIBMTP_FILETYPE_MP3) || (files->filetype == LIBMTP_FILETYPE_OGG) || (files->filetype == LIBMTP_FILETYPE_FLAC) || (files->filetype == LIBMTP_FILETYPE_WMA)) { trackinfo = LIBMTP_Get_Trackmetadata(DeviceMgr.device, files->item_id); if (trackinfo != NULL) { // search case insensitive. if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } tmpstring1 = g_utf8_strup(trackinfo->album, -1); tmpstring2 = g_utf8_strup(trackinfo->artist, -1); tmpstring3 = g_utf8_strup(trackinfo->genre, -1); tmpstring4 = g_utf8_strup(trackinfo->title, -1); if ((g_pattern_match_string(pspec, tmpstring1) == TRUE) || (g_pattern_match_string(pspec, tmpstring2) == TRUE) || (g_pattern_match_string(pspec, tmpstring3) == TRUE) || (g_pattern_match_string(pspec, tmpstring4) == TRUE)) { // We have found a matching string... filestruc = g_malloc(sizeof (FileListStruc)); if (filestruc == NULL) { if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } if (tmpstring2 != NULL) { g_free(tmpstring2); tmpstring2 = NULL; } if (tmpstring3 != NULL) { g_free(tmpstring3); tmpstring3 = NULL; } if (tmpstring4 != NULL) { g_free(tmpstring4); tmpstring4 = NULL; } g_fprintf(stderr, _("malloc in filesSearch failed\n")); displayError(_("Failed searching? 'malloc in filesSearch'\n")); return list; } filestruc->filename = g_strdup(files->filename); filestruc->filesize = files->filesize; filestruc->isFolder = FALSE; filestruc->itemid = files->item_id; filestruc->filetype = files->filetype; filestruc->location = getFullFolderPath(files->parent_id); list = g_slist_append(list, filestruc); } if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } if (tmpstring2 != NULL) { g_free(tmpstring2); tmpstring2 = NULL; } if (tmpstring3 != NULL) { g_free(tmpstring3); tmpstring3 = NULL; } if (tmpstring4 != NULL) { g_free(tmpstring4); tmpstring4 = NULL; } LIBMTP_destroy_track_t(trackinfo); } } } if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } } files = files->next; // Update the file list IFF we are using the alt access method if ((files == NULL) && (Preferences.use_alt_access_method)) { // reached the end of the current folder, so lets move onto the next folder. while (files == NULL) { // exit searching for file if we run out of folders. if (folderIDs == NULL) { break; } currentFolderID = *((uint32_t*) folderIDs->data); filesUpateFileList(); folderIDs = folderIDs->next; } } } } else if (searchmeta == TRUE) { // Search using the Track information only. while (tracks != NULL) { // Make search case insensitive. tmpstring1 = g_utf8_strup(tracks->album, -1); tmpstring2 = g_utf8_strup(tracks->artist, -1); tmpstring3 = g_utf8_strup(tracks->genre, -1); tmpstring4 = g_utf8_strup(tracks->title, -1); if ((g_pattern_match_string(pspec, tmpstring1) == TRUE) || (g_pattern_match_string(pspec, tmpstring2) == TRUE) || (g_pattern_match_string(pspec, tmpstring3) == TRUE) || (g_pattern_match_string(pspec, tmpstring4) == TRUE)) { // We have found a matching string... filestruc = g_malloc(sizeof (FileListStruc)); if (filestruc == NULL) { if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } if (tmpstring2 != NULL) { g_free(tmpstring2); tmpstring2 = NULL; } if (tmpstring3 != NULL) { g_free(tmpstring3); tmpstring3 = NULL; } if (tmpstring4 != NULL) { g_free(tmpstring4); tmpstring4 = NULL; } g_fprintf(stderr, _("malloc in filesSearch failed\n")); displayError(_("Failed searching? 'malloc in filesSearch'\n")); return list; } filestruc->filename = g_strdup(tracks->filename); filestruc->filesize = tracks->filesize; filestruc->isFolder = FALSE; filestruc->itemid = tracks->item_id; filestruc->filetype = tracks->filetype; filestruc->location = getFullFolderPath(tracks->parent_id); list = g_slist_append(list, filestruc); } if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } if (tmpstring2 != NULL) { g_free(tmpstring2); tmpstring2 = NULL; } if (tmpstring3 != NULL) { g_free(tmpstring3); tmpstring3 = NULL; } if (tmpstring4 != NULL) { g_free(tmpstring4); tmpstring4 = NULL; } tracks = tracks->next; } } // restore the current folder ID. if (Preferences.use_alt_access_method) { currentFolderID = tmpFolderID; } return list; } // ************************************************************************************************ /** * Generate a list of all the folder IDs in the current storage pool. * @return */ void buildFolderIDs(GSList **list, LIBMTP_folder_t * folderptr) { while (folderptr != NULL) { *(list) = g_slist_append(*(list), &folderptr->folder_id); if (folderptr->child != NULL) { buildFolderIDs(list, folderptr->child); } folderptr = folderptr->sibling; } } // ************************************************************************************************ /** * Search the folder hier for the foldername. * @param pspec - foldername to search * @param list - the GSList to append found items. * @param folderptr - the MTP folder struc to search. */ void folderSearch(GPatternSpec *pspec, GSList **list, LIBMTP_folder_t * folderptr) { FileListStruc *filestruc = NULL; gchar *tmpstring = NULL; while (folderptr != NULL) { // Make search case insensitive. tmpstring = g_utf8_strup(folderptr->name, -1); if (g_pattern_match_string(pspec, tmpstring) == TRUE) { // We have found a matching string... filestruc = g_malloc(sizeof (FileListStruc)); if (filestruc == NULL) { if (tmpstring != NULL) { g_free(tmpstring); tmpstring = NULL; } g_fprintf(stderr, _("malloc in foldersearch failed\n")); displayError(_("Failed searching? 'malloc in foldersearch'\n")); return; } filestruc->filename = g_strdup(folderptr->name); filestruc->filesize = 0; filestruc->isFolder = TRUE; filestruc->itemid = folderptr->folder_id; #if defined(LIBMTP_FILETYPE_FOLDER) filestruc->filetype = LIBMTP_FILETYPE_FOLDER; #else filestruc->filetype = LIBMTP_FILETYPE_UNKNOWN; #endif filestruc->location = getFullFolderPath(folderptr->parent_id); *(list) = g_slist_append(*(list), filestruc); } if (tmpstring != NULL) { g_free(tmpstring); tmpstring = NULL; } // Search child if present; if (folderptr->child != NULL) { folderSearch(pspec, list, folderptr->child); } folderptr = folderptr->sibling; } } // ************************************************************************************************ /** * Updates the parent FolderID for a given object within the device. * @param objectID - The object to be updated. * @param folderID - The new parent folder ID for the object. * @return 0 on success, any other value means failure. */ int setNewParentFolderID(uint32_t objectID, uint32_t folderID) { return LIBMTP_Set_Object_u32(DeviceMgr.device, objectID, LIBMTP_PROPERTY_ParentObject, folderID); } // search case insensitive. if (tmpstring1 != NULL) { g_free(tmpstring1); tmpstring1 = NULL; } tmpstring1 = g_utf8_gMTP/src/callbacks.h000064401651440000012000000136001204763013300152060ustar00darranstaff00003030200010/* * * File: callbacks.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _CALLBACKS_H #define _CALLBACKS_H #ifdef __cplusplus extern "C" { #endif #include // Main window functions. void on_quit1_activate(GtkMenuItem *menuitem, gpointer user_data); void on_preferences1_activate(GtkMenuItem *menuitem, gpointer user_data); void on_about1_activate(GtkMenuItem *menuitem, gpointer user_data); void on_deviceConnect_activate(GtkMenuItem *menuitem, gpointer user_data); void on_deviceProperties_activate(GtkMenuItem *menuitem, gpointer user_data); void on_deviceRescan_activate(GtkMenuItem *menuitem, gpointer user_data); void on_filesAdd_activate(GtkMenuItem *menuitem, gpointer user_data); void on_filesDelete_activate(GtkMenuItem *menuitem, gpointer user_data); void on_filesDownload_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileNewFolder_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileRemoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileRenameFile_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileMoveFile_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editDeviceName_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editFind_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editSelectAll_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editFormatDevice_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editAddAlbumArt_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); void on_view_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileAddToPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); void on_fileRemoveFromPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); // Treeview handling. void fileListRowActivated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data); gboolean on_windowMainContextMenu_activate(GtkWidget *widget, GdkEvent *event); gboolean on_windowViewContextMenu_activate(GtkWidget *widget, GdkEvent *event); void on_treeviewFolders_rowactivated(GtkTreeSelection *treeselection, gpointer user_data); // Folder Treeview handling. void folderListRowActivated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data); void on_folderNewFolder_activate(GtkMenuItem *menuitem, gpointer user_data); void on_folderRemoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data); void on_folderRenameFolder_activate(GtkMenuItem *menuitem, gpointer user_data); void on_folderMoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data); // Preferences Dialog void on_quitPrefs_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsDevice_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsAskDownload_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsDownloadPath_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsUploadPath_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsConfirmDelete_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsConfirmOverWriteFileOp_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsAutoAddTrackPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsIgnorePathInPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsSuppressAlbumError_activate(GtkMenuItem *menuitem, gpointer user_data); void on_PrefsUseAltAccessMethod_activate(GtkMenuItem *menuitem, gpointer user_data); // Properties Dialog void on_quitProp_activate(GtkMenuItem *menuitem, gpointer user_data); // Add Album Art Dialog void on_buttonAlbumArtAdd_activate(GtkWidget *button, gpointer user_data); void on_buttonAlbumArtDelete_activate(GtkWidget *button, gpointer user_data); void on_buttonAlbumArtDownload_activate(GtkWidget *button, gpointer user_data); void on_albumtextbox_activate(GtkComboBox *combobox, gpointer user_data); // Playlist Dialog void on_quitPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_NewPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_ImportPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_ExportPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_DelPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_DelFileButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_AddFileButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_FileUpButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_FileDownButton_activate(GtkMenuItem *menuitem, gpointer user_data); void on_Playlist_Combobox_activate(GtkComboBox *combobox, gpointer user_data); // Progress Dialog void on_progressDialog_Close(GtkWidget *window, gpointer user_data); void on_progressDialog_Cancel(GtkWidget *window, gpointer user_data); // Format Device Progress Bar. void on_editFormatDevice_thread(void); // Add Track to Playlist option. void on_TrackPlaylist_NewPlaylistButton_activate(GtkWidget *button, gpointer user_data); // Search function; void on_editFindSearch_activate(GtkMenuItem *menuitem, gpointer user_data); void on_editFindClose_activate(GtkMenuItem *menuitem, gpointer user_data); #ifdef __cplusplus } #endif #endif /* _CALLBACKS_H */ gMTP/src/main.c000064401651440000012000000146051205062464100142150ustar00darranstaff00003030200010/* * * File: main.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #include #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include #include #include #include "main.h" #include "interface.h" #include "callbacks.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" // Global Widgets needed by various functions. GtkWidget *windowMain; GtkWidget *windowPrefsDialog; GtkWidget *windowPropDialog; GtkWidget *windowPlaylistDialog; GtkWidget *windowStatusBar; GtkWidget *toolbuttonConnect; GtkWidget *treeviewFiles; GtkWidget *treeviewFolders; // The device which we are connected with. Device_Struct DeviceMgr; // Device structures for files, folders, etc. LIBMTP_file_t *deviceFiles = NULL; LIBMTP_folder_t *deviceFolders = NULL; LIBMTP_track_t *deviceTracks = NULL; LIBMTP_playlist_t *devicePlayLists = NULL; uint32_t currentFolderID = 0; int32_t addTrackPlaylistID = GMTP_REQUIRE_PLAYLIST; GQueue *stackFolderIDs = NULL; GQueue *stackFolderNames = NULL; // Paths to the application, and images used within the application. gchar *applicationpath = NULL; gchar *file_logo_png = NULL; gchar *file_icon48_png = NULL; gchar *file_icon16_png = NULL; gchar *file_about_png = NULL; gchar *file_format_png = NULL; // File view Icons gchar *file_audio_png = NULL; gchar *file_video_png = NULL; gchar *file_playlist_png = NULL; gchar *file_album_png = NULL; gchar *file_textfile_png = NULL; gchar *file_generic_png = NULL; gchar *file_folder_png = NULL; gchar *file_image_png = NULL; // ************************************************************************************************ /** * Main Function * @param argc - Number of arguments to the function * @param argv - Argument list * @return - exit code */ int main(int argc, char *argv[]) { setFilePaths(argc, argv); g_thread_init(NULL); #if GMTP_USE_GTK2 gtk_set_locale(); #endif g_set_prgname(PACKAGE_TITLE); g_set_application_name(PACKAGE_TITLE); gtk_init(&argc, &argv); #ifdef ENABLE_NLS bindtextdomain(PACKAGE, g_strconcat(applicationpath, "/../share/locale", NULL)); bind_textdomain_codeset(PACKAGE, "UTF-8"); textdomain(PACKAGE); #endif // Initialise libmtp library LIBMTP_Init(); // Create our main window for the application. windowMain = create_windowMain(); gtk_widget_show(windowMain); //Set default state for application DeviceMgr.deviceConnected = FALSE; statusBarSet(_("No device attached")); SetToolbarButtonState(DeviceMgr.deviceConnected); setupPreferences(); // If preference is to auto-connect then attempt to do so. if (Preferences.attemptDeviceConnectOnStart == TRUE) on_deviceConnect_activate(NULL, NULL); // If we do have a connected device, then do a rescan operation to fill in the filelist. if (DeviceMgr.deviceConnected == TRUE) deviceRescan(); gtk_main(); return EXIT_SUCCESS; } // end main() // ************************************************************************************************ /** * setFilePaths - set paths for image used within gMTP * @param argc * @param argv */ void setFilePaths(int argc, char *argv[]) { // Get our executable location. applicationpath = getRuntimePath(argc, argv); // Set our image locations. file_logo_png = g_strdup_printf("%s/../share/gmtp/logo.png", applicationpath); file_icon48_png = g_strdup_printf("%s/../share/gmtp/icon.png", applicationpath); file_icon16_png = g_strdup_printf("%s/../share/gmtp/icon-16.png", applicationpath); file_about_png = g_strdup_printf("%s/../share/gmtp/stock-about-16.png", applicationpath); file_format_png = g_strdup_printf("%s/../share/gmtp/view-refresh.png", applicationpath); file_audio_png = g_strdup_printf("%s/../share/gmtp/audio-x-mpeg.png", applicationpath); file_video_png = g_strdup_printf("%s/../share/gmtp/video-x-generic.png", applicationpath); file_playlist_png = g_strdup_printf("%s/../share/gmtp/audio-x-mp3-playlist.png", applicationpath); file_album_png = g_strdup_printf("%s/../share/gmtp/media-cdrom-audio.png", applicationpath); file_textfile_png = g_strdup_printf("%s/../share/gmtp/text-plain.png", applicationpath); file_generic_png = g_strdup_printf("%s/../share/gmtp/empty.png", applicationpath); file_folder_png = g_strdup_printf("%s/../share/gmtp/folder.png", applicationpath); file_image_png = g_strdup_printf("%s/../share/gmtp/image-x-generic.png", applicationpath); } // end setFilePaths() // ************************************************************************************************ /** * getRuntimePath - Returns the path which the application was run from * @param argc * @param argv * @return pointer to string with location of the binary. */ gchar *getRuntimePath(int argc, char *argv[]) { gchar *fullpath; gchar *filepath; gchar *foundpath = NULL; const char delimit[] = ";:"; gchar *token; if (g_ascii_strcasecmp(PACKAGE, argv[0]) == 0) { // list each directory individually. fullpath = g_strdup(getenv("PATH")); token = strtok(fullpath, delimit); while ((token != NULL) && (foundpath == NULL)) { // Now test to see if we have it here... filepath = g_strdup(token); filepath = g_strconcat(filepath, "/", PACKAGE, NULL); if (access(filepath, F_OK) != -1) { foundpath = g_strdup(token); } token = strtok(NULL, delimit); g_free(filepath); } } else { // We were started with full file path. foundpath = g_strdup(dirname(argv[0])); } if (argc == 3) { // We have some other options, lets check for --datapath if (g_ascii_strcasecmp("--datapath", argv[1]) == 0) { // our first argument is --datapath, so set the path to argv[2]; foundpath = g_strdup(argv[2]); } } return foundpath; } // end getRuntimePath n; GtkWidget *windowPrefsDialog; GtkWidget *windowPropDialog; GtkWidget *windowPlaylistDialog; GtkWidget *windowStatusBar; gMTP/src/prefs.c000064401651440000012000001131611205063367000144070ustar00darranstaff00003030200010/* * * File: prefs.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include "main.h" #include "interface.h" #include "callbacks.h" #include "mtp.h" #include "prefs.h" Preferences_Struct Preferences; // ************************************************************************************************ /* This file has all logic for handling both GConf and GSetting environments. */ #if GMTP_USE_GTK2 GConfClient *gconfconnect = NULL; guint gconf_callback_id; #else GSettings *gsettings_connect = NULL; #endif // ************************************************************************************************ /** * Set some default values for the application prefences. * Attach the applicable callback handler for eith GConf or GSettings. */ void setupPreferences() { // We setup default Preferences. Preferences.ask_download_path = TRUE; Preferences.attemptDeviceConnectOnStart = TRUE; Preferences.suppress_album_errors = FALSE; #ifdef WIN32 Preferences.fileSystemDownloadPath = g_string_new(g_getenv("HOMEPATH")); Preferences.fileSystemUploadPath = g_string_new(g_getenv("HOMEPATH")); #else Preferences.fileSystemDownloadPath = g_string_new(g_getenv("HOME")); Preferences.fileSystemUploadPath = g_string_new(g_getenv("HOME")); #endif // Now setup our gconf/gsettings callbacks; #if GMTP_USE_GTK2 if (gconfconnect == NULL) gconfconnect = gconf_client_get_default(); if (gconf_client_dir_exists(gconfconnect, "/apps/gMTP", NULL) == TRUE) { gconf_client_add_dir(gconfconnect, "/apps/gMTP", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_callback_id = gconf_client_notify_add(gconfconnect, "/apps/gMTP", (GConfClientNotifyFunc) gconf_callback_func, NULL, NULL, NULL); } #else gsettings_connect = g_settings_new(GMTP_GSETTINGS_SCHEMA); g_signal_connect((gpointer) gsettings_connect, "changed", G_CALLBACK(gsettings_callback_func), NULL); #endif // Now attempt to read the config file from the user config folder. loadPreferences(); } // ************************************************************************************************ /** * Read the Preferences from the settings database. * @return TRUE if successful in reading the setting database for preferences. */ gboolean loadPreferences() { #if GMTP_USE_GTK2 if (gconf_client_dir_exists(gconfconnect, "/apps/gMTP", NULL) == TRUE) { gconf_client_preload(gconfconnect, "/apps/gMTP", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); Preferences.ask_download_path = gconf_client_get_bool(gconfconnect, "/apps/gMTP/promptDownloadPath", NULL); Preferences.confirm_file_delete_op = gconf_client_get_bool(gconfconnect, "/apps/gMTP/confirmFileDelete", NULL); Preferences.prompt_overwrite_file_op = gconf_client_get_bool(gconfconnect, "/apps/gMTP/promptOverwriteFile", NULL); Preferences.attemptDeviceConnectOnStart = gconf_client_get_bool(gconfconnect, "/apps/gMTP/autoconnectdevice", NULL); Preferences.fileSystemDownloadPath = g_string_new(gconf_client_get_string(gconfconnect, "/apps/gMTP/DownloadPath", NULL)); Preferences.fileSystemUploadPath = g_string_new(gconf_client_get_string(gconfconnect, "/apps/gMTP/UploadPath", NULL)); Preferences.auto_add_track_to_playlist = gconf_client_get_bool(gconfconnect, "/apps/gMTP/autoAddTrackPlaylist", NULL); Preferences.ignore_path_in_playlist_import = gconf_client_get_bool(gconfconnect, "/apps/gMTP/ignorepathinplaylist", NULL); Preferences.suppress_album_errors = gconf_client_get_bool(gconfconnect, "/apps/gMTP/suppressalbumerrors", NULL); Preferences.use_alt_access_method = gconf_client_get_bool(gconfconnect, "/apps/gMTP/alternateaccessmethod", NULL); Preferences.view_size = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewFileSize", NULL); Preferences.view_type = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewFileType", NULL); Preferences.view_track_number = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewTrackNumber", NULL); Preferences.view_title = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewTitle", NULL); Preferences.view_artist = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewArtist", NULL); Preferences.view_album = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewAlbum", NULL); Preferences.view_year = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewYear", NULL); Preferences.view_genre = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewGenre", NULL); Preferences.view_duration = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewDuration", NULL); Preferences.view_folders = gconf_client_get_bool(gconfconnect, "/apps/gMTP/viewFolders", NULL); } else { g_fprintf(stderr, _("WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n")); } gconf_client_clear_cache(gconfconnect); #else if (gsettings_connect != NULL) { Preferences.ask_download_path = g_settings_get_boolean(gsettings_connect, "promptdownloadpath"); Preferences.confirm_file_delete_op = g_settings_get_boolean(gsettings_connect, "confirmfiledelete"); Preferences.prompt_overwrite_file_op = g_settings_get_boolean(gsettings_connect, "promptoverwritefile"); Preferences.attemptDeviceConnectOnStart = g_settings_get_boolean(gsettings_connect, "autoconnectdevice"); Preferences.fileSystemDownloadPath = g_string_new(g_settings_get_string(gsettings_connect, "downloadpath")); Preferences.fileSystemUploadPath = g_string_new(g_settings_get_string(gsettings_connect, "uploadpath")); Preferences.auto_add_track_to_playlist = g_settings_get_boolean(gsettings_connect, "autoaddtrackplaylist"); Preferences.ignore_path_in_playlist_import = g_settings_get_boolean(gsettings_connect, "ignorepathinplaylist"); Preferences.suppress_album_errors = g_settings_get_boolean(gsettings_connect, "suppressalbumerrors"); Preferences.use_alt_access_method = g_settings_get_boolean(gsettings_connect, "alternateaccessmethod"); Preferences.view_size = g_settings_get_boolean(gsettings_connect, "viewfilesize"); Preferences.view_type = g_settings_get_boolean(gsettings_connect, "viewfiletype"); Preferences.view_track_number = g_settings_get_boolean(gsettings_connect, "viewtracknumber"); Preferences.view_title = g_settings_get_boolean(gsettings_connect, "viewtitle"); Preferences.view_artist = g_settings_get_boolean(gsettings_connect, "viewartist"); Preferences.view_album = g_settings_get_boolean(gsettings_connect, "viewalbum"); Preferences.view_year = g_settings_get_boolean(gsettings_connect, "viewyear"); Preferences.view_genre = g_settings_get_boolean(gsettings_connect, "viewgenre"); Preferences.view_duration = g_settings_get_boolean(gsettings_connect, "viewduration"); Preferences.view_folders = g_settings_get_boolean(gsettings_connect, "viewfolders"); } #endif // Set some menu options and view states. gtk_tree_view_column_set_visible(column_Size, Preferences.view_size); gtk_tree_view_column_set_visible(column_Type, Preferences.view_type); gtk_tree_view_column_set_visible(column_Track_Number, Preferences.view_track_number); gtk_tree_view_column_set_visible(column_Title, Preferences.view_title); gtk_tree_view_column_set_visible(column_Artist, Preferences.view_artist); gtk_tree_view_column_set_visible(column_Album, Preferences.view_album); gtk_tree_view_column_set_visible(column_Year, Preferences.view_year); gtk_tree_view_column_set_visible(column_Genre, Preferences.view_genre); gtk_tree_view_column_set_visible(column_Duration, Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filesize), Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filetype), Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_track_number), Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_title), Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_artist), Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_album), Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_year), Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_genre), Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_duration), Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_folders), Preferences.view_folders); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewSize), Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewType), Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackNumber), Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackName), Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewArtist), Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewAlbum), Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewYear), Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewGenre), Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewDuration), Preferences.view_duration); // Disable the folder view if in alt access mode... if (Preferences.use_alt_access_method) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_view_folders))) { gtk_menu_item_activate(GTK_MENU_ITEM(menu_view_folders)); } gtk_widget_hide(scrolledwindowFolders); gtk_widget_set_sensitive(menu_view_folders, !Preferences.use_alt_access_method); } return TRUE; } // ************************************************************************************************ /** * Save the application settings to the preferences database. * @return TRUE if successful. */ gboolean savePreferences() { #if GMTP_USE_GTK2 if (gconf_client_dir_exists(gconfconnect, "/apps/gMTP", NULL) == TRUE) { gconf_client_preload(gconfconnect, "/apps/gMTP", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/promptDownloadPath", Preferences.ask_download_path, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/autoconnectdevice", Preferences.attemptDeviceConnectOnStart, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/promptOverwriteFile", Preferences.prompt_overwrite_file_op, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/confirmFileDelete", Preferences.confirm_file_delete_op, NULL); gconf_client_set_string(gconfconnect, "/apps/gMTP/DownloadPath", Preferences.fileSystemDownloadPath->str, NULL); gconf_client_set_string(gconfconnect, "/apps/gMTP/UploadPath", Preferences.fileSystemUploadPath->str, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/autoAddTrackPlaylist", Preferences.auto_add_track_to_playlist, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/ignorepathinplaylist", Preferences.ignore_path_in_playlist_import, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/suppressalbumerrors", Preferences.suppress_album_errors, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/alternateaccessmethod", Preferences.use_alt_access_method, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewFileSize", Preferences.view_size, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewFileType", Preferences.view_type, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewTrackNumber", Preferences.view_track_number, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewTitle", Preferences.view_title, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewArtist", Preferences.view_artist, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewAlbum", Preferences.view_album, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewYear", Preferences.view_year, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewGenre", Preferences.view_genre, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewDuration", Preferences.view_duration, NULL); gconf_client_set_bool(gconfconnect, "/apps/gMTP/viewFolders", Preferences.view_folders, NULL); } else { g_fprintf(stderr, _("WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n")); } gconf_client_suggest_sync(gconfconnect, NULL); gconf_client_clear_cache(gconfconnect); #else if (gsettings_connect != NULL) { g_settings_set_boolean(gsettings_connect, "promptdownloadpath", Preferences.ask_download_path); g_settings_set_boolean(gsettings_connect, "autoconnectdevice", Preferences.attemptDeviceConnectOnStart); g_settings_set_boolean(gsettings_connect, "promptoverwritefile", Preferences.prompt_overwrite_file_op); g_settings_set_boolean(gsettings_connect, "confirmfiledelete", Preferences.confirm_file_delete_op); g_settings_set_string(gsettings_connect, "downloadpath", Preferences.fileSystemDownloadPath->str); g_settings_set_string(gsettings_connect, "uploadpath", Preferences.fileSystemUploadPath->str); g_settings_set_boolean(gsettings_connect, "autoaddtrackplaylist", Preferences.auto_add_track_to_playlist); g_settings_set_boolean(gsettings_connect, "ignorepathinplaylist", Preferences.ignore_path_in_playlist_import); g_settings_set_boolean(gsettings_connect, "suppressalbumerrors", Preferences.suppress_album_errors); g_settings_set_boolean(gsettings_connect, "alternateaccessmethod", Preferences.use_alt_access_method); g_settings_set_boolean(gsettings_connect, "viewfilesize", Preferences.view_size); g_settings_set_boolean(gsettings_connect, "viewfiletype", Preferences.view_type); g_settings_set_boolean(gsettings_connect, "viewtracknumber", Preferences.view_track_number); g_settings_set_boolean(gsettings_connect, "viewtitle", Preferences.view_title); g_settings_set_boolean(gsettings_connect, "viewartist", Preferences.view_artist); g_settings_set_boolean(gsettings_connect, "viewalbum", Preferences.view_album); g_settings_set_boolean(gsettings_connect, "viewyear", Preferences.view_year); g_settings_set_boolean(gsettings_connect, "viewgenre", Preferences.view_genre); g_settings_set_boolean(gsettings_connect, "viewduration", Preferences.view_duration); g_settings_set_boolean(gsettings_connect, "viewfolders", Preferences.view_folders); } g_settings_sync(); #endif return TRUE; } // ************************************************************************************************ /** * The callback function for GConf. * @param client * @param cnxn_id * @param entry * @param user_data */ #if GMTP_USE_GTK2 void gconf_callback_func(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data) { //g_printf("Gconf callback - %s\n", entry->key); if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/promptDownloadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.ask_download_path = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); //g_printf("/apps/gMTP/promptDownloadPath = %d\n", Preferences.ask_download_path ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDownloadPath), Preferences.ask_download_path); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/autoconnectdevice") == 0) { //set our promptDownloadPath in Preferences Preferences.attemptDeviceConnectOnStart = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); //g_printf("/apps/gMTP/autoconnectdevice = %d\n", Preferences.attemptDeviceConnectOnStart ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDeviceConnect), Preferences.attemptDeviceConnectOnStart); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/promptOverwriteFile") == 0) { //set our promptDownloadPath in Preferences Preferences.prompt_overwrite_file_op = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); //g_printf("/apps/gMTP/promptOverwriteFile = %d\n", Preferences.prompt_overwrite_file_op ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmOverWriteFileOp), Preferences.prompt_overwrite_file_op); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/confirmFileDelete") == 0) { //set our promptDownloadPath in Preferences Preferences.confirm_file_delete_op = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); //g_printf("/apps/gMTP/confirmFileDelete = %d\n", Preferences.confirm_file_delete_op ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmFileOp), Preferences.confirm_file_delete_op); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/DownloadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, gconf_value_to_string((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry))); //g_printf("/apps/gMTP/DownloadPath = %s\n", Preferences.fileSystemDownloadPath->str ); if (windowPrefsDialog != NULL) gtk_entry_set_text(GTK_ENTRY(entryDownloadPath), Preferences.fileSystemDownloadPath->str); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/UploadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.fileSystemUploadPath = g_string_assign(Preferences.fileSystemUploadPath, gconf_value_to_string((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry))); //g_printf("/apps/gMTP/UploadPath = %s\n", Preferences.fileSystemUploadPath->str ); if (windowPrefsDialog != NULL) gtk_entry_set_text(GTK_ENTRY(entryUploadPath), Preferences.fileSystemUploadPath->str); return; } // View menu Options. if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewFileSize") == 0) { //set our promptDownloadPath in Preferences Preferences.view_size = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Size, Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filesize), Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewSize), Preferences.view_size); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewFileType") == 0) { //set our promptDownloadPath in Preferences Preferences.view_type = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Type, Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filetype), Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewType), Preferences.view_type); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewTrackNumber") == 0) { //set our promptDownloadPath in Preferences Preferences.view_track_number = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Track_Number, Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_track_number), Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackNumber), Preferences.view_track_number); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewTitle") == 0) { //set our promptDownloadPath in Preferences Preferences.view_title = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Title, Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_title), Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackName), Preferences.view_title); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewArtist") == 0) { //set our promptDownloadPath in Preferences Preferences.view_artist = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Artist, Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_artist), Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewArtist), Preferences.view_artist); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewAlbum") == 0) { //set our promptDownloadPath in Preferences Preferences.view_album = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Album, Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_album), Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewAlbum), Preferences.view_album); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewYear") == 0) { //set our promptDownloadPath in Preferences Preferences.view_year = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Year, Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_year), Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewYear), Preferences.view_year); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewGenre") == 0) { //set our promptDownloadPath in Preferences Preferences.view_genre = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Genre, Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_genre), Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewGenre), Preferences.view_genre); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewDuration") == 0) { //set our promptDownloadPath in Preferences Preferences.view_duration = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_tree_view_column_set_visible(column_Duration, Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_duration), Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewDuration), Preferences.view_duration); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/viewFolders") == 0) { //set our promptDownloadPath in Preferences Preferences.view_folders = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_folders), Preferences.view_folders); if (!Preferences.use_alt_access_method) { if (Preferences.view_folders == TRUE) { gtk_widget_show(scrolledwindowFolders); } else { gtk_widget_hide(scrolledwindowFolders); } } else { // hide the folder view gtk_widget_hide(scrolledwindowFolders); } gtk_widget_set_sensitive(menu_view_folders, !Preferences.use_alt_access_method); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/autoAddTrackPlaylist") == 0) { //set our promptDownloadPath in Preferences Preferences.auto_add_track_to_playlist = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAutoAddTrackPlaylist), Preferences.auto_add_track_to_playlist); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/ignorepathinplaylist") == 0) { //set our promptDownloadPath in Preferences Preferences.ignore_path_in_playlist_import = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonIgnorePathInPlaylist), Preferences.ignore_path_in_playlist_import); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/suppressalbumerrors") == 0) { //set our promptDownloadPath in Preferences Preferences.suppress_album_errors = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonSuppressAlbumErrors), Preferences.suppress_album_errors); return; } if (g_ascii_strcasecmp(entry->key, "/apps/gMTP/alternateaccessmethod") == 0) { //set our promptDownloadPath in Preferences Preferences.use_alt_access_method = (gboolean) gconf_value_get_bool((const GConfValue*) gconf_entry_get_value((const GConfEntry*) entry)); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAltAccessMethod), Preferences.use_alt_access_method); // if we are connected, then disconnect the device... if (DeviceMgr.deviceConnected == TRUE) { on_deviceConnect_activate(NULL, NULL); displayInformation(_("Disconnected device due to access method change")); } // Disable the folder view... if (Preferences.use_alt_access_method) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_view_folders))) { gtk_menu_item_activate(GTK_MENU_ITEM(menu_view_folders)); } } return; } g_fprintf(stderr, _("WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n")); } #else // ************************************************************************************************ /** * The callback for the GSettings database. */ void gsettings_callback_func(GSettings *settings, gchar *key, gpointer user_data) { if (g_ascii_strcasecmp(key, "promptDownloadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.ask_download_path = (gboolean) g_settings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDownloadPath), Preferences.ask_download_path); return; } if (g_ascii_strcasecmp(key, "autoconnectdevice") == 0) { //set our promptDownloadPath in Preferences Preferences.attemptDeviceConnectOnStart = (gboolean) g_settings_get_boolean(settings, key); //g_printf("/apps/gMTP/autoconnectdevice = %d\n", Preferences.attemptDeviceConnectOnStart ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDeviceConnect), Preferences.attemptDeviceConnectOnStart); return; } if (g_ascii_strcasecmp(key, "promptOverwriteFile") == 0) { //set our promptDownloadPath in Preferences Preferences.prompt_overwrite_file_op = (gboolean) g_settings_get_boolean(settings, key); //g_printf("/apps/gMTP/promptOverwriteFile = %d\n", Preferences.prompt_overwrite_file_op ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmOverWriteFileOp), Preferences.prompt_overwrite_file_op); return; } if (g_ascii_strcasecmp(key, "confirmFileDelete") == 0) { //set our promptDownloadPath in Preferences Preferences.confirm_file_delete_op = (gboolean) g_settings_get_boolean(settings, key); //g_printf("/apps/gMTP/confirmFileDelete = %d\n", Preferences.confirm_file_delete_op ); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmFileOp), Preferences.confirm_file_delete_op); return; } if (g_ascii_strcasecmp(key, "DownloadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, g_settings_get_string(settings, key)); //g_printf("/apps/gMTP/DownloadPath = %s\n", Preferences.fileSystemDownloadPath->str ); if (windowPrefsDialog != NULL) gtk_entry_set_text(GTK_ENTRY(entryDownloadPath), Preferences.fileSystemDownloadPath->str); return; } if (g_ascii_strcasecmp(key, "UploadPath") == 0) { //set our promptDownloadPath in Preferences Preferences.fileSystemUploadPath = g_string_assign(Preferences.fileSystemUploadPath, g_settings_get_string(settings, key)); //g_printf("/apps/gMTP/UploadPath = %s\n", Preferences.fileSystemUploadPath->str ); if (windowPrefsDialog != NULL) gtk_entry_set_text(GTK_ENTRY(entryUploadPath), Preferences.fileSystemUploadPath->str); return; } // View menu Options. if (g_ascii_strcasecmp(key, "viewFileSize") == 0) { //set our promptDownloadPath in Preferences Preferences.view_size = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Size, Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filesize), Preferences.view_size); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewSize), Preferences.view_size); return; } if (g_ascii_strcasecmp(key, "viewFileType") == 0) { //set our promptDownloadPath in Preferences Preferences.view_type = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Type, Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_filetype), Preferences.view_type); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewType), Preferences.view_type); return; } if (g_ascii_strcasecmp(key, "viewTrackNumber") == 0) { //set our promptDownloadPath in Preferences Preferences.view_track_number = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Track_Number, Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_track_number), Preferences.view_track_number); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackNumber), Preferences.view_track_number); return; } if (g_ascii_strcasecmp(key, "viewTitle") == 0) { //set our promptDownloadPath in Preferences Preferences.view_title = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Title, Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_title), Preferences.view_title); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewTrackName), Preferences.view_title); return; } if (g_ascii_strcasecmp(key, "viewArtist") == 0) { //set our promptDownloadPath in Preferences Preferences.view_artist = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Artist, Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_artist), Preferences.view_artist); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewArtist), Preferences.view_artist); return; } if (g_ascii_strcasecmp(key, "viewAlbum") == 0) { //set our promptDownloadPath in Preferences Preferences.view_album = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Album, Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_album), Preferences.view_album); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewAlbum), Preferences.view_album); return; } if (g_ascii_strcasecmp(key, "viewYear") == 0) { //set our promptDownloadPath in Preferences Preferences.view_year = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Year, Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_year), Preferences.view_year); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewYear), Preferences.view_year); return; } if (g_ascii_strcasecmp(key, "viewGenre") == 0) { //set our promptDownloadPath in Preferences Preferences.view_genre = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Genre, Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_genre), Preferences.view_genre); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewGenre), Preferences.view_genre); return; } if (g_ascii_strcasecmp(key, "viewDuration") == 0) { //set our promptDownloadPath in Preferences Preferences.view_duration = (gboolean) g_settings_get_boolean(settings, key); gtk_tree_view_column_set_visible(column_Duration, Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_duration), Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(cViewDuration), Preferences.view_duration); return; } if (g_ascii_strcasecmp(key, "viewFolders") == 0) { //set our promptDownloadPath in Preferences Preferences.view_folders = (gboolean) g_settings_get_boolean(settings, key); //gtk_tree_view_column_set_visible(column_Duration, Preferences.view_duration); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_view_folders), Preferences.view_folders); if (!Preferences.use_alt_access_method) { if (Preferences.view_folders == TRUE) { gtk_widget_show(scrolledwindowFolders); } else { gtk_widget_hide(scrolledwindowFolders); } } else { // hide the folder view gtk_widget_hide(scrolledwindowFolders); } gtk_widget_set_sensitive(menu_view_folders, !Preferences.use_alt_access_method); return; } if (g_ascii_strcasecmp(key, "autoaddtrackplaylist") == 0) { //set our promptDownloadPath in Preferences Preferences.auto_add_track_to_playlist = (gboolean) g_settings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAutoAddTrackPlaylist), Preferences.auto_add_track_to_playlist); return; } if (g_ascii_strcasecmp(key, "ignorepathinplaylist") == 0) { //set our promptDownloadPath in Preferences Preferences.ignore_path_in_playlist_import = (gboolean) g_settings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonIgnorePathInPlaylist), Preferences.ignore_path_in_playlist_import); return; } if (g_ascii_strcasecmp(key, "suppressalbumerrors") == 0) { //set our promptDownloadPath in Preferences Preferences.suppress_album_errors = (gboolean) g_settings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonSuppressAlbumErrors), Preferences.suppress_album_errors); return; } if (g_ascii_strcasecmp(key, "alternateaccessmethod") == 0) { //set our promptDownloadPath in Preferences Preferences.use_alt_access_method = (gboolean) g_settings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAltAccessMethod), Preferences.use_alt_access_method); // if we are connected, then disconnect the device... if (DeviceMgr.deviceConnected == TRUE) { on_deviceConnect_activate(NULL, NULL); displayInformation(_("Disconnected device due to access method change")); } // Disable the folder view... if (Preferences.use_alt_access_method) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_view_folders))) { gtk_menu_item_activate(GTK_MENU_ITEM(menu_view_folders)); } } return; } g_fprintf(stderr, _("WARNING: gsettings_callback_func() failed - we got a callback for a key thats not ours?\n")); } #endif ettings_get_boolean(settings, key); if (windowPrefsDialog != NULL) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDownloadPath), Preferences.ask_download_path); return; } if (g_ascii_strcasecmp(key, "autoconnectdevice") == 0) { //set our promptDownloadPath in Preferences Preferences.attemptDeviceConnectOnStart = (gboolean) g_settings_get_boolean(gMTP/src/callbacks.c000064401651440000012000002324211205063157200152070ustar00darranstaff00003030200010/* * * File: callbacks.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include #include #include "main.h" #include "callbacks.h" #include "interface.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" static gboolean formatThreadWorking = TRUE; /** * on_quit1_activate - Call back for Quit toolbar and menu option. * @param menuitem * @param user_data */ void on_quit1_activate(GtkMenuItem *menuitem, gpointer user_data) { savePreferences(); #if GMTP_USE_GTK2 gtk_exit(EXIT_SUCCESS); #else exit(EXIT_SUCCESS); #endif } // end on_quit1_activate() // ************************************************************************************************ /** * on_about1_activate - Call back for displaying the About Dialog Box * @param menuitem * @param user_data */ void on_about1_activate(GtkMenuItem *menuitem, gpointer user_data) { displayAbout(); } // end on_about1_activate() // ************************************************************************************************ /** * on_deviceProperties_activate - Callback for displaying the device Properties Dialog box. * @param menuitem * @param user_data */ void on_deviceProperties_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *tmp_string; // We confirm our device properties, this should setup the device structure information we use below. deviceProperties(); // Update the status bar with our information. if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { if (DeviceMgr.devicestorage->StorageDescription != NULL) { tmp_string = g_strdup_printf(_("Connected to %s (%s) - %d MB free"), DeviceMgr.devicename->str, DeviceMgr.devicestorage->StorageDescription, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } } statusBarSet(tmp_string); g_free(tmp_string); // No idea how this could come about, but we should take it into account so we don't have a // memleak due to recreating the window multiple times. if (windowPropDialog != NULL) { gtk_widget_hide(windowPropDialog); gtk_widget_destroy(windowPropDialog); } // Create and show the dialog box. windowPropDialog = create_windowProperties(); gtk_widget_show(GTK_WIDGET(windowPropDialog)); } // end on_deviceProperties_activate() // ************************************************************************************************ /** * on_quitProp_activate - Callback used to close the Properties Dialog. * @param menuitem * @param user_data */ void on_quitProp_activate(GtkMenuItem *menuitem, gpointer user_data) { gtk_widget_hide(windowPropDialog); gtk_widget_destroy(windowPropDialog); windowPropDialog = NULL; } // end on_quitProp_activate() // ************************************************************************************************ /** * on_deviceRescan_activate - Callback to rescan the device properties and update the main * application window. * @param menuitem * @param user_data */ void on_deviceRescan_activate(GtkMenuItem *menuitem, gpointer user_data) { deviceRescan(); } // end on_deviceRescan_activate() // ************************************************************************************************ /** * on_filesAdd_activate - Callback to initiate an Add Files operation. * @param menuitem * @param user_data */ void on_filesAdd_activate(GtkMenuItem *menuitem, gpointer user_data) { GSList* files; int64_t targetFol = 0; //uint32_t tmpFolderID = 0; // Set the Playlist ID to be asked if needed. if (Preferences.auto_add_track_to_playlist == TRUE) { addTrackPlaylistID = GMTP_REQUIRE_PLAYLIST; } else { addTrackPlaylistID = GMTP_NO_PLAYLIST; } // Get the files, and add them. files = getFileGetList2Add(); // See if a folder is selected in the folder view, and if so add the files to that folder. if ((targetFol = folderListGetSelection()) != -1) { //tmpFolderID = currentFolderID; currentFolderID = (uint32_t) targetFol; } AlbumErrorIgnore = FALSE; if (files != NULL) g_slist_foreach(files, (GFunc) __filesAdd, NULL); // Now clear the GList; g_slist_foreach(files, (GFunc) g_free, NULL); g_slist_free(files); // Restore the current folder ID is we added to another folder. if (targetFol != -1) { // Disable this, so the user is taken to the folder in which the files were added to. //currentFolderID = tmpFolderID; } // Now do a device rescan to see the new files. deviceRescan(); deviceoverwriteop = MTP_ASK; } // end on_filesAdd_activate() // ************************************************************************************************ /** * Callback to handle the Rename Device menu option. * @param menuitem * @param user_data */ void on_fileRenameFile_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkTreePath *path; GtkTreeIter iter; gchar *newfilename = NULL; gchar *filename = NULL; gboolean isFolder; uint32_t ObjectID = 0; // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { // See if anything is selected in the folder view, if so use that as our source. if (folderListGetSelection() != -1) { on_folderRenameFolder_activate(menuitem, user_data); } else { displayInformation(_("No files/folders selected?")); } return; } GList *List = fileListGetSelection(); // We only care about the first entry. // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(List->data); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. // Before we download, is it a folder ? gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_FILENAME_ACTUAL, &filename, COL_ISFOLDER, &isFolder, COL_FILEID, &ObjectID, -1); // Make sure we are not attempting to edit the parent link folder. if (g_ascii_strcasecmp(filename, "..") == 0) { g_fprintf(stderr, _("Unable to rename parent folder\n")); displayInformation(_("Unable to rename this folder")); return; } // Get our new device name. newfilename = displayRenameFileDialog(filename); // If the user supplied something, then update the name of the device. if (newfilename != NULL) { filesRename(newfilename, ObjectID); g_free(newfilename); deviceRescan(); } } // end on_editDeviceName_activate() // ************************************************************************************************ /** * Callback to handle the Move File menu option. * @param menuitem * @param user_data */ void on_fileMoveFile_activate(GtkMenuItem *menuitem, gpointer user_data) { GList *List = NULL; int64_t targetfolder = 0; // If using alternate connection mode, this is disabled. if(Preferences.use_alt_access_method){ displayInformation(_("The move function is disabled when using the alternate access method for your device.")); return; } // Let's check to see if we have anything selected in our treeview? if ((List = fileListGetSelection()) == NULL) { if (folderListGetSelection() != -1) { on_folderRemoveFolder_activate(menuitem, user_data); } else { displayInformation(_("No files/folders selected?")); } return; } // Prompt for the target folder location. targetfolder = getTargetFolderLocation(); if ((targetfolder == -1) || (targetfolder == currentFolderID)) { // If the user didn't select a folder, or the target folder is the current selected folder // then do nothing. return; } fileMoveTargetFolder = targetfolder; fileListClearSelection(); // List is a list of Iter's to be moved g_list_foreach(List, (GFunc) __fileMove, NULL); // We have 2 options, manually scan the file structure for that file and manually fix up... // or do a rescan... // I'll be cheap, and do a full rescan of the device. deviceRescan(); } // ************************************************************************************************ /** * on_filesDelete_activate - Callback to initiate a Delete Files operation. * @param menuitem * @param user_data */ void on_filesDelete_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkWidget *dialog; // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { if (folderListGetSelection() != -1) { on_folderRemoveFolder_activate(menuitem, user_data); } else { displayInformation(_("No files/folders selected?")); } return; } // Now we prompt to confirm delete? if (Preferences.confirm_file_delete_op == FALSE) { // Now download the actual file from the MTP device. fileListRemove(fileListGetSelection()); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(windowMain), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("Are you sure you want to delete these files?")); gtk_window_set_title(GTK_WINDOW(dialog), _("Confirm Delete")); // Run the Dialog and get our result. gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_YES) fileListRemove(fileListGetSelection()); // Destroy the dialog box. gtk_widget_destroy(dialog); } } // on_filesDelete_activate() // ************************************************************************************************ /** * on_filesDownload_activate - Callback to initiate a download files operation. * @param menuitem * @param user_data */ void on_filesDownload_activate(GtkMenuItem *menuitem, gpointer user_data) { int64_t targetfolder = 0; // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { if ((targetfolder = folderListGetSelection()) != -1) { folderListDownload(folderListGetSelectionName(), targetfolder); } else { displayInformation(_("No files/folders selected?")); } return; } // Download the selected files. fileListDownload(fileListGetSelection()); } // end on_filesDownload_activate() // ************************************************************************************************ /** * on_deviceConnect_activate - Callback used to connect a device to the application. * @param menuitem * @param user_data */ void on_deviceConnect_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *tmp_string; GtkWidget *menuText; deviceConnect(); //g_printf("Device connect/disconnect code = %d\n", result); // Update our label to indicate current condition. if (DeviceMgr.deviceConnected == TRUE) { // Set up our properties. deviceProperties(); deviceRescan(); // Update the toolbar to show a disconnect string. gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolbuttonConnect), _("Disconnect")); // Now update the status bar; if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { if (DeviceMgr.devicestorage->StorageDescription != NULL) { tmp_string = g_strdup_printf(_("Connected to %s (%s) - %d MB free"), DeviceMgr.devicename->str, DeviceMgr.devicestorage->StorageDescription, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } } statusBarSet(tmp_string); g_free(tmp_string); // Now update the filemenu; menuText = gtk_bin_get_child(GTK_BIN(fileConnect)); gtk_label_set_text(GTK_LABEL(menuText), _("Disconnect Device")); // Enable the Drag'n'Drop interface for the main window and folder window. gmtp_drag_dest_set(scrolledwindowMain); gmtp_drag_dest_set(treeviewFolders); } else { // Update the toolbar to show the Connect String. gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolbuttonConnect), _("Connect")); // Now update the status bar; statusBarSet(_("No device attached")); // Now update the filemenu; menuText = gtk_bin_get_child(GTK_BIN(fileConnect)); gtk_label_set_text(GTK_LABEL(menuText), _("Connect Device")); // Now update the file list area and disable Drag'n'Drop. fileListClear(); folderListClear(); gtk_drag_dest_unset(scrolledwindowMain); gtk_drag_dest_unset(treeviewFolders); setWindowTitle(NULL); // Hide the find toolbar if open and force search mode to false. gtk_widget_hide(findToolbar); gtk_widget_set_sensitive(GTK_WIDGET(cfileAdd), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(cfileNewFolder), TRUE); inFindMode = FALSE; } // Update the Toolbar and Menus enabling/disabling the menu items. SetToolbarButtonState(DeviceMgr.deviceConnected); } // on_deviceConnect_activate() // Here is the preferences callbacks and dialog box creation. // ************************************************************************************************ /** * Callback to show the Preferences Dialog Box. * @param menuitem * @param user_data */ void on_preferences1_activate(GtkMenuItem *menuitem, gpointer user_data) { // No idea how this could come about, but we should take it into account so we don't have a memleak // due to recreating the window multiple times. if (windowPrefsDialog != NULL) { gtk_widget_hide(windowPrefsDialog); gtk_widget_destroy(windowPrefsDialog); } // Create and display the dialog windowPrefsDialog = create_windowPreferences(); gtk_widget_show(GTK_WIDGET(windowPrefsDialog)); } // end on_preferences1_activate() // ************************************************************************************************ /** * Callback to close the Preferences Dialog Box. * @param menuitem * @param user_data */ void on_quitPrefs_activate(GtkMenuItem *menuitem, gpointer user_data) { gtk_widget_hide(windowPrefsDialog); gtk_widget_destroy(windowPrefsDialog); windowPrefsDialog = NULL; } // end on_quitPrefs_activate() // ************************************************************************************************ /** * Callback for Auto Connect Device toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsDevice_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonDeviceConnect)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/autoconnectdevice", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "autoconnectdevice", state); g_settings_sync(); #endif } // end on_PrefsDevice_activate() // ************************************************************************************************ /** * Callback for Confirm Delete Operations toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsConfirmDelete_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmFileOp)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/confirmFileDelete", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "confirmfiledelete", state); g_settings_sync(); #endif } // end on_PrefsConfirmDelete_activate() // ************************************************************************************************ /** * Callback for Ask Download Path Operations toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsAskDownload_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonDownloadPath)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/promptDownloadPath", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "promptdownloadpath", state); g_settings_sync(); #endif } // end on_PrefsAskDownload_activate() // ************************************************************************************************ /** * Callback for Ask Download Path Operations toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsAutoAddTrackPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonAutoAddTrackPlaylist)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/autoAddTrackPlaylist", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "autoaddtrackplaylist", state); g_settings_sync(); #endif } // end on_PrefsAutoAddTrackPlaylist_activate() // ************************************************************************************************ /** * Callback for Ignore Path in Playlist toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsIgnorePathInPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonIgnorePathInPlaylist)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/ignorepathinplaylist", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "ignorepathinplaylist", state); g_settings_sync(); #endif } // end on_PrefsIgnorePathInPlaylist_activate() // ************************************************************************************************ /** * Callback for Suppress Album Errors toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsSuppressAlbumError_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonSuppressAlbumErrors)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/suppressalbumerrors", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "suppressalbumerrors", state); g_settings_sync(); #endif } // end on_PrefsSuppressAlbumError_activate() // ************************************************************************************************ /** * Callback for Suppress Album Errors toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsUseAltAccessMethod_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonAltAccessMethod)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/alternateaccessmethod", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "alternateaccessmethod", state); g_settings_sync(); #endif } // end on_PrefsUseAltAccessMethod_activate() // ************************************************************************************************ /** * Callback for Confirm Overwrite of File Operations toggle in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsConfirmOverWriteFileOp_activate(GtkMenuItem *menuitem, gpointer user_data) { gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmOverWriteFileOp)); #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_bool(gconfconnect, "/apps/gMTP/promptOverwriteFile", state, NULL); #else if (gsettings_connect != NULL) g_settings_set_boolean(gsettings_connect, "promptoverwritefile", state); g_settings_sync(); #endif } // end on_PrefsConfirmOverWriteFileOp_activate() // ************************************************************************************************ /** * Callback for setting download path in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsDownloadPath_activate(GtkMenuItem *menuitem, gpointer user_data) { // What we do here is display a find folder dialog, and save the resulting folder into the text wigdet and preferences item. gchar *savepath = NULL; GtkWidget *FileDialog; // First of all, lets set the download path. FileDialog = gtk_file_chooser_dialog_new(_("Select Path to Download to"), GTK_WINDOW(windowPrefsDialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemDownloadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { savepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); // Save our download path. #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_string(gconfconnect, "/apps/gMTP/DownloadPath", savepath, NULL); #else if (gsettings_connect != NULL) g_settings_set_string(gsettings_connect, "downloadpath", savepath); g_settings_sync(); #endif g_free(savepath); } gtk_widget_destroy(FileDialog); } // on_PrefsDownloadPath_activate() // ************************************************************************************************ /** * Callback for setting upload path in Preferences Dialog Box. * @param menuitem * @param user_data */ void on_PrefsUploadPath_activate(GtkMenuItem *menuitem, gpointer user_data) { // What we do here is display a find folder dialog, and save the resulting folder into the text wigdet and preferences item. gchar *savepath = NULL; GtkWidget *FileDialog; // First of all, lets set the upload path. FileDialog = gtk_file_chooser_dialog_new(_("Select Path to Upload From"), GTK_WINDOW(windowPrefsDialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemUploadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { savepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); // Save our download path. #if GMTP_USE_GTK2 if (gconfconnect != NULL) gconf_client_set_string(gconfconnect, "/apps/gMTP/UploadPath", savepath, NULL); #else if (gsettings_connect != NULL) g_settings_set_string(gsettings_connect, "uploadpath", savepath); g_settings_sync(); #endif g_free(savepath); } gtk_widget_destroy(FileDialog); } //on_PrefsUploadPath_activate() // ************************************************************************************************ /** * Callback to handle double click on item in main window. If it's a folder, then change to it, * other attempt to download the file(s). * @param treeview * @param path * @param column * @param data */ void fileListRowActivated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) { GtkTreeModel *model; GtkTreeModel *sortmodel; GtkTreeIter iter; gchar *filename = NULL; gboolean isFolder; uint32_t objectID; GtkWidget *FileDialog; gchar *savepath = NULL; // Obtain the iter, and the related objectID. sortmodel = gtk_tree_view_get_model(treeview); model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(sortmodel)); if (gtk_tree_model_get_iter(model, &iter, gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sortmodel), path))) { gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILENAME_ACTUAL, &filename, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { // Now download the actual file from the MTP device. savepath = g_malloc0(8192); // Let's confirm our download path. if (Preferences.ask_download_path == TRUE) { FileDialog = gtk_file_chooser_dialog_new(_("Select Path to Download"), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemDownloadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { savepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); // Save our download path. Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, savepath); // We do the deed. filesDownload(filename, objectID); } gtk_widget_destroy(FileDialog); } else { // We do the deed. filesDownload(filename, objectID); } g_free(savepath); } else { // Maintain the stack of folder IDs and names for alt access mode. if(Preferences.use_alt_access_method){ if(g_ascii_strcasecmp(filename, "..") == 0){ // going down a level. g_free(g_queue_pop_tail(stackFolderIDs)); g_free(g_queue_pop_tail(stackFolderNames)); } else { // going up a level guint *currentFld = g_malloc(sizeof(guint)); *currentFld = currentFolderID; g_queue_push_tail(stackFolderIDs, currentFld); g_queue_push_tail(stackFolderNames, g_strdup(filename)); } } // We have a folder so change to it? currentFolderID = objectID; on_editFindClose_activate(NULL, NULL); } } g_free(filename); } // end fileListRowActivated() // ************************************************************************************************ /** * Callback to handle double click on item in folder main window. * @param treeview * @param path * @param column * @param data */ void folderListRowActivated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) { GtkTreeModel *model; GtkTreeModel *sortmodel; GtkTreeIter iter; uint32_t objectID; // Obtain the iter, and the related objectID. sortmodel = gtk_tree_view_get_model(treeview); model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(sortmodel)); if (gtk_tree_model_get_iter(model, &iter, gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sortmodel), path))) { gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, COL_FOL_ID, &objectID, -1); // We have a folder so change to it? currentFolderID = objectID; on_editFindClose_activate(NULL, NULL); } } // end folderListRowActivated() // ************************************************************************************************ /** * Callback to handle selecting NewFolder from menu or toolbar. * @param menuitem * @param user_data */ void on_fileNewFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *foldername = NULL; if (folderListGetSelection() != -1) { on_folderNewFolder_activate(menuitem, user_data); return; } // Get the folder name by displaying a dialog. foldername = displayFolderNewDialog(); if (foldername != NULL) { // Add in folder to MTP device. folderAdd(foldername); g_free(foldername); deviceRescan(); } } // end on_fileNewFolder_activate() // ************************************************************************************************ /** * Callback to handle selecting NewFolder from menu or toolbar. * @param menuitem * @param user_data */ void on_folderNewFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *foldername = NULL; uint32_t tmpFolderID = 0; // Get the folder name by displaying a dialog. foldername = displayFolderNewDialog(); if (foldername != NULL) { // Let's see if we have anything selected in the folder view, and if not, then we add the // folder to the current Folder. if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // Add in folder to MTP device. folderAdd(foldername); } else { // We have selected a folder in the folder view, so let's get it's ID. tmpFolderID = currentFolderID; currentFolderID = folderListGetSelection(); folderAdd(foldername); currentFolderID = tmpFolderID; } g_free(foldername); deviceRescan(); } } // end on_folderNewFolder_activate() // ************************************************************************************************ /** * Callback to handle selecting RemoveFolder from menu or toolbar. * @param menuitem * @param user_data */ void on_folderRemoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkWidget *dialog; GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; uint32_t objectID; // Let's see if we have anything selected in the folder view, and if not let the user know, and return if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // Add in folder to MTP device. displayInformation(_("No files/folders selected?")); return; } else { // We have selected a folder in the folder view, so let's get it's ID. sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFiles)); gtk_tree_selection_get_selected(folderSelection, &sortmodel, &iter); gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &childiter, COL_FOL_ID, &objectID, -1); // Now we prompt to confirm delete? if (Preferences.confirm_file_delete_op == FALSE) { // Now download the actual file from the MTP device. folderDelete(getCurrentFolderPtr(deviceFolders, objectID), 0); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(windowMain), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("Are you sure you want to delete this folder (and all contents)?")); gtk_window_set_title(GTK_WINDOW(dialog), _("Confirm Delete")); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_YES) folderDelete(getCurrentFolderPtr(deviceFolders, objectID), 0); gtk_widget_destroy(dialog); } //folderDelete(getCurrentFolderPtr(deviceFolders, objectID), 0); } deviceRescan(); } // end on_folderRemoveFolder_activate() // ************************************************************************************************ /** * Callback to handle selecting MoveFolder from context menu. * @param menuitem * @param user_data */ void on_folderMoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; int64_t targetfolder = 0; uint32_t objectID; LIBMTP_folder_t *currentFolder = NULL; LIBMTP_folder_t *newFolder = NULL; int error; // Let's see if we have anything selected in the folder view, and if not let the user know, and return if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // Add in folder to MTP device. displayInformation(_("No files/folders selected?")); return; } else { // We have selected a folder in the folder view, so let's get it's ID. sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFiles)); gtk_tree_selection_get_selected(folderSelection, &sortmodel, &iter); gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &childiter, COL_FOL_ID, &objectID, -1); // Prompt for the target folder location. targetfolder = getTargetFolderLocation(); if ((targetfolder == -1)) { // If the user didn't select a folder, or the target folder is the current selected folder // then do nothing. return; } fileMoveTargetFolder = targetfolder; gtk_tree_selection_unselect_all(folderSelection); // Make sure we don't want to move the folder into itself? if (objectID == fileMoveTargetFolder) { displayError(_("Unable to move the selected folder into itself?\n")); g_fprintf(stderr, _("Unable to move the selected folder into itself?\n")); return; } // We have the target folder, so let's check to ensure that we will not create a circular // reference by moving a folder underneath it self. currentFolder = getCurrentFolderPtr(deviceFolders, objectID); if (currentFolder == NULL) { // WTF? g_fprintf(stderr, "File Move Error: Can't get current folder pointer\n"); return; } // Use currentFolder as the starting point, and simply attempt to get the ptr to the new // folder based on this point. newFolder = getCurrentFolderPtr(currentFolder->child, fileMoveTargetFolder); if (newFolder == NULL) { // We are alright to proceed. if ((error = setNewParentFolderID(objectID, fileMoveTargetFolder)) != 0) { displayError(_("Unable to move the selected folder?\n")); g_fprintf(stderr, "File Move Error: %d\n", error); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { displayError(_("Unable to move the selected folder underneath itself?\n")); g_fprintf(stderr, _("Unable to move the selected folder underneath itself?\n")); } } deviceRescan(); } // end on_folderMoveFolder_activate() // ************************************************************************************************ /** * Callback to handle selecting Rename Folder from menu * @param menuitem * @param user_data */ void on_folderRenameFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *newfilename = NULL; gchar *filename = NULL; GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; uint32_t objectID; // Let's see if we have anything selected in the folder view, and if not let the user know, and return if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // Add in folder to MTP device. displayInformation(_("No files/folders selected?")); return; } else { // We have selected a folder in the folder view, so let's get it's ID. sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFiles)); gtk_tree_selection_get_selected(folderSelection, &sortmodel, &iter); gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &childiter, COL_FOL_ID, &objectID, COL_FOL_NAME_HIDDEN, &filename, -1); // Get our new folder name. newfilename = displayRenameFileDialog(filename); // If the user supplied something, then update the name of the device. if (newfilename != NULL) { filesRename(newfilename, objectID); g_free(newfilename); deviceRescan(); } } } // end on_folderRenameFolder_activate() // ************************************************************************************************ /** * Callback handle to handle deleting a folder menu option. * @param menuitem * @param user_data */ void on_fileRemoveFolder_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkWidget *dialog; // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { if (folderListGetSelection() != -1) { on_folderRemoveFolder_activate(menuitem, user_data); } else { displayInformation(_("No files/folders selected?")); } return; } // Now we prompt to confirm delete? if (Preferences.confirm_file_delete_op == FALSE) { // Now download the actual file from the MTP device. folderListRemove(fileListGetSelection()); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(windowMain), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("Are you sure you want to delete this folder (and all contents)?")); gtk_window_set_title(GTK_WINDOW(dialog), _("Confirm Delete")); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_YES) folderListRemove(fileListGetSelection()); gtk_widget_destroy(dialog); } } // end on_fileRemoveFolder_activate() // ************************************************************************************************ /** * Callback to handle the Rename Device menu option. * @param menuitem * @param user_data */ void on_editDeviceName_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *devicename = NULL; gchar *tmp_string = NULL; // Get our new device name. devicename = displayChangeDeviceNameDialog(DeviceMgr.devicename->str); // If the user supplied something, then update the name of the device. if (devicename != NULL) { // add change to MTP device. setDeviceName(devicename); g_free(devicename); // Attempt to read it back as confirmation that something may of happened. tmp_string = LIBMTP_Get_Friendlyname(DeviceMgr.device); if (tmp_string == NULL) { DeviceMgr.devicename = g_string_new(_("N/A")); } else { DeviceMgr.devicename = g_string_new(tmp_string); g_free(tmp_string); } // Perform a device Rescan operation to reset all device parameters. deviceRescan(); } } // end on_editDeviceName_activate() // ************************************************************************************************ /** * Callback to format the current storage device. * @param menuitem * @param user_data */ void on_editFormatDevice_activate(GtkMenuItem *menuitem, gpointer user_data) { GtkWidget *dialog; dialog = gtk_message_dialog_new(GTK_WINDOW(windowMain), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("Are you sure you want to format this device?")); gtk_window_set_title(GTK_WINDOW(dialog), _("Format Device")); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_hide(GTK_WIDGET(dialog)); gtk_widget_destroy(dialog); if (result == GTK_RESPONSE_YES) { dialog = create_windowFormat(); // Show progress dialog. gtk_widget_show_all(dialog); // Ensure GTK redraws the window. formatThreadWorking = TRUE; g_thread_create((GThreadFunc) on_editFormatDevice_thread, NULL, FALSE, NULL); while (formatThreadWorking) { while (gtk_events_pending()) gtk_main_iteration(); if (formatDialog_progressBar1 != NULL) { gtk_progress_bar_pulse(GTK_PROGRESS_BAR(formatDialog_progressBar1)); g_usleep(G_USEC_PER_SEC * 0.1); } } // The worker thread has finished so let's continue. // Disconnect and reconnect the device. on_deviceConnect_activate(NULL, NULL); // Sleep for 2 secs to allow the device to settle itself g_usleep(G_USEC_PER_SEC * 2); on_deviceConnect_activate(NULL, NULL); // Close progress dialog. gtk_widget_hide(dialog); gtk_widget_destroy(dialog); formatDialog_progressBar1 = NULL; } // } // end on_editFormatDevice_activate() // ************************************************************************************************ /** * Worker thread for on_editFormatDevice_activate(); */ void on_editFormatDevice_thread(void) { formatStorageDevice(); // Add a 5 sec wait so the device has time to settle itself. g_usleep(G_USEC_PER_SEC * 5); formatThreadWorking = FALSE; g_thread_exit(NULL); } // ************************************************************************************************ /** * Callback to handle the displaying of the context menu. * @param widget * @param event * @return */ gboolean on_windowMainContextMenu_activate(GtkWidget *widget, GdkEvent *event) { GtkMenu *menu; GdkEventButton *event_button; g_return_val_if_fail(widget != NULL, FALSE); g_return_val_if_fail(GTK_IS_MENU(widget), FALSE); g_return_val_if_fail(event != NULL, FALSE); /* The "widget" is the menu that was supplied when * g_signal_connect_swapped() was called. */ menu = GTK_MENU(widget); if (event->type == GDK_BUTTON_PRESS) { event_button = (GdkEventButton *) event; if (event_button->button == 3) { gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event_button->button, event_button->time); return TRUE; } } return FALSE; } // end on_windowMainContextMenu_activate() // ************************************************************************************************ /** * Callback to handle the displaying of the context menu. * @param widget * @param event * @return */ gboolean on_windowViewContextMenu_activate(GtkWidget *widget, GdkEvent *event) { GtkMenu *menu; GdkEventButton *event_button; g_return_val_if_fail(event != NULL, FALSE); /* The "widget" is the menu that was supplied when * g_signal_connect_swapped() was called. */ menu = GTK_MENU(contextMenuColumn); if (event->type == GDK_BUTTON_PRESS) { event_button = (GdkEventButton *) event; if (event_button->button == 3) { gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event_button->button, event_button->time); return TRUE; } } return FALSE; } // end on_windowMainContextMenu_activate() // ************************************************************************************************ /** * Callback to handle the Find menu option. * @param menuitem * @param user_data */ void on_editFind_activate(GtkMenuItem *menuitem, gpointer user_data) { if (inFindMode == FALSE) { gtk_widget_show(findToolbar); gtk_widget_hide(scrolledwindowFolders); fileListClear(); //folderListClear(); inFindMode = TRUE; statusBarSet(_("Please enter search item.")); setWindowTitle(_("Search")); gtk_tree_view_column_set_visible(column_Location, TRUE); //Disable some of the menu options, while in search mode. gtk_widget_set_sensitive(GTK_WIDGET(cfileAdd), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(cfileNewFolder), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(fileAdd), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(fileNewFolder), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonAddFile), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(menu_view_folders), FALSE); // Get focus on text entry box. gtk_widget_grab_focus(GTK_WIDGET(FindToolbar_entry_FindText)); } else { on_editFindClose_activate(menuitem, user_data); } } // end on_editFind_activate() // ************************************************************************************************ /** * Callback to handle the Find menu option. * @param menuitem * @param user_data */ void on_editSelectAll_activate(GtkMenuItem *menuitem, gpointer user_data) { fileListSelectAll(); } // end on_editSelectAll_activate() // ************************************************************************************************ /** * Callback to handle the Find toolbar close option. * @param menuitem * @param user_data */ void on_editFindClose_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar* tmp_string; gtk_widget_hide(findToolbar); if (Preferences.view_folders == TRUE) { gtk_widget_show(scrolledwindowFolders); } fileListClear(); inFindMode = FALSE; gtk_tree_view_column_set_visible(column_Location, FALSE); // First we clear the file and folder list... fileListClear(); folderListClear(); // Refresh the file listings. // If using alternate access method, then get our next list of files for the current folder id. if(Preferences.use_alt_access_method){ filesUpateFileList(); } fileListAdd(); folderListAdd(deviceFolders, NULL); // Update the status bar. if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { if (DeviceMgr.devicestorage->StorageDescription != NULL) { tmp_string = g_strdup_printf(_("Connected to %s (%s) - %d MB free"), DeviceMgr.devicename->str, DeviceMgr.devicestorage->StorageDescription, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } else { tmp_string = g_strdup_printf(_("Connected to %s - %d MB free"), DeviceMgr.devicename->str, (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE)); } } statusBarSet(tmp_string); g_free(tmp_string); // Now clear the Search GList; if (searchList != NULL) { g_slist_foreach(searchList, (GFunc) g_free_search, NULL); g_slist_free(searchList); searchList = NULL; } //Enable some of the menu options, while in search mode. gtk_widget_set_sensitive(GTK_WIDGET(cfileAdd), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(cfileNewFolder), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(fileAdd), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(fileNewFolder), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonAddFile), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(menu_view_folders), !Preferences.use_alt_access_method); } // end on_editFindClose_activate() // ************************************************************************************************ /** * Callback to handle the actual searching of files/folders. * @param menuitem * @param user_data */ void on_editFindSearch_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *searchstring = NULL; gboolean searchfiles = FALSE; gboolean searchmeta = FALSE; statusBarSet(_("Searching...")); // Now clear the Search GList; if (searchList != NULL) { g_slist_foreach(searchList, (GFunc) g_free_search, NULL); g_slist_free(searchList); searchList = NULL; } // Set to upper case to perform case insensitive searches. searchstring = g_utf8_strup(gtk_entry_get_text(GTK_ENTRY(FindToolbar_entry_FindText)), -1); searchfiles = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(FindToolbar_checkbutton_FindFiles)); searchmeta = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(FindToolbar_checkbutton_TrackInformation)); // Let's start our search. searchList = filesSearch(searchstring, searchfiles, searchmeta); inFindMode = TRUE; fileListClear(); fileListAdd(); g_free(searchstring); } // end on_editFindSearch_activate() // ************************************************************************************************ /** * Callback to handle the Add Album Art menu option. * @param menuitem * @param user_data */ void on_editAddAlbumArt_activate(GtkMenuItem *menuitem, gpointer user_data) { // Get a filename of the album art. displayAddAlbumArtDialog(); } // end on_editAddAlbumArt_activate() // ************************************************************************************************ /** * Callback to hanlde the Playlist menu/toolbar operations. * @param menuitem * @param user_data */ void on_editPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { displayPlaylistDialog(); } // end on_editPlaylist_activate() // ************************************************************************************************ /** * Callback to handle the select file button in the Add Album Art Dialog box. * @param button * @param user_data */ void on_buttonAlbumArtAdd_activate(GtkWidget *button, gpointer user_data) { // What we do here is display a find folder dialog, and save the resulting folder into the text wigdet and preferences item. //gchar *filename; gchar *filename = NULL; GtkWidget *FileDialog; FileDialog = gtk_file_chooser_dialog_new(_("Select Album Art File"), GTK_WINDOW(AlbumArtDialog), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(FileDialog), TRUE); // Set the default path to be the normal upload folder. gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemUploadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); if (filename != NULL) { // Upload the file to the selected album. gint selected = gtk_combo_box_get_active(GTK_COMBO_BOX(textboxAlbumArt)); gint count = 0; LIBMTP_album_t *albumlist = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); LIBMTP_album_t *albuminfo = albumlist; while (albuminfo != NULL) { if (count == selected) { // Found our album, so update the image on the device, then update the display. albumAddArt(albuminfo->album_id, filename); AlbumArtUpdateImage(albuminfo); clearAlbumStruc(albumlist); g_free(filename); gtk_widget_destroy(FileDialog); return; } // Next album_entry albuminfo = albuminfo->next; count++; } // Set a default image as we didn't find our album. AlbumArtUpdateImage(NULL); clearAlbumStruc(albumlist); g_free(filename); } } gtk_widget_destroy(FileDialog); } // end on_buttonAlbumArtAdd_activate() // ************************************************************************************************ /** * Callback to handle removal of associated album art. * @param button * @param user_data */ void on_buttonAlbumArtDelete_activate(GtkWidget *button, gpointer user_data) { // Send a blank representation. gint selected = gtk_combo_box_get_active(GTK_COMBO_BOX(textboxAlbumArt)); gint count = 0; LIBMTP_album_t *albumlist = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); LIBMTP_album_t *albuminfo = albumlist; while (albuminfo != NULL) { if (count == selected) { // Found our album, so update the image on the device, then update the display. albumDeleteArt(albuminfo->album_id); AlbumArtUpdateImage(NULL); clearAlbumStruc(albumlist); return; } // Next album_entry albuminfo = albuminfo->next; count++; } // Set a default image as we didn't find our album. AlbumArtUpdateImage(NULL); clearAlbumStruc(albumlist); } // end on_buttonAlbumArtDelete_activate() // ************************************************************************************************ /** * Retrieve the album art and attempt to save the file. * @param button * @param user_data */ void on_buttonAlbumArtDownload_activate(GtkWidget *button, gpointer user_data) { FILE* fd; gint selected = gtk_combo_box_get_active(GTK_COMBO_BOX(textboxAlbumArt)); gint count = 0; GtkWidget *FileDialog = NULL; gchar *filename = NULL; LIBMTP_filesampledata_t *imagedata = NULL; LIBMTP_album_t *albumlist = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); LIBMTP_album_t *albuminfo = albumlist; // Scan our albums, looking for the correct one. while (albuminfo != NULL) { if (count == selected) { // Found our album, let's get our data.. imagedata = albumGetArt(albuminfo); if (imagedata != NULL) { FileDialog = gtk_file_chooser_dialog_new(_("Save Album Art File"), GTK_WINDOW(AlbumArtDialog), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(FileDialog), TRUE); // Set the default path to be the normal download folder. gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemDownloadPath->str); // Set a default name to be the album.JPG gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(FileDialog), g_strdup_printf("%s.jpg", albuminfo->name)); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); if (filename != NULL) { // The user has selected a file to save as, so do the deed. fd = fopen(filename, "w"); if (fd == NULL) { g_fprintf(stderr, _("Couldn't save image file %s\n"), filename); displayError(_("Couldn't save image file\n")); } else { fwrite(imagedata->data, imagedata->size, 1, fd); fclose(fd); } g_free(filename); } } // Clean up our image data and dialog. LIBMTP_destroy_filesampledata_t(imagedata); gtk_widget_destroy(FileDialog); } clearAlbumStruc(albumlist); return; } // Next album_entry albuminfo = albuminfo->next; count++; } // Set a default image as we didn't find our album. clearAlbumStruc(albumlist); gtk_widget_destroy(FileDialog); } // end on_buttonAlbumArtDownload_activate() // ************************************************************************************************ /** * Update the Album Image in the Add Album Art Dialog Box. * @param menuitem * @param user_data */ void on_albumtextbox_activate(GtkComboBox *combobox, gpointer user_data) { gint selected = gtk_combo_box_get_active(combobox); gint count = 0; LIBMTP_album_t *albumlist = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); LIBMTP_album_t *albuminfo = albumlist; while (albuminfo != NULL) { if (count == selected) { AlbumArtUpdateImage(albuminfo); clearAlbumStruc(albumlist); return; } // Text the album_entry albuminfo = albuminfo->next; count++; } // Set a default image AlbumArtUpdateImage(NULL); clearAlbumStruc(albumlist); } // Playlist Callbacks. // ************************************************************************************************ /** * Callback to handle closing the Playlist editor dialog. * @param menuitem * @param user_data */ void on_quitPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { // Save our current selected playlist! if (devicePlayLists != NULL) playlist_SavePlaylist(playlist_number); // Kill our window gtk_widget_hide(windowPlaylistDialog); gtk_widget_destroy(windowPlaylistDialog); windowPlaylistDialog = NULL; // Do a device rescan to show the new playlists in the file window deviceRescan(); } // end on_quitPlaylist_activate() // ************************************************************************************************ /** * Callback to handle the new Playlist button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_NewPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data) { //g_printf("Clicked on new playlist button\n"); gchar *playlistname = NULL; // Save our current selected playlist! if (devicePlayLists != NULL) playlist_SavePlaylist(playlist_number); playlistname = displayPlaylistNewDialog(); if (playlistname != NULL) { // Add in playlist to MTP device. playlistAdd(playlistname); // Refresh our playlist information. devicePlayLists = getPlaylists(); gtk_list_store_clear(GTK_LIST_STORE(playlist_PL_List)); // Add it to our combobox #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(comboboxentry_playlist), g_strdup(playlistname)); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(comboboxentry_playlist), g_strdup(playlistname)); #endif g_free(playlistname); // Set the active combobox item. comboboxentry_playlist_entries++; playlist_number = comboboxentry_playlist_entries - 1; gtk_combo_box_set_active(GTK_COMBO_BOX(comboboxentry_playlist), comboboxentry_playlist_entries - 1); SetPlaylistButtonState(TRUE); setPlaylistField(playlist_number); } } // end on_Playlist_NewPlaylistButton_activate() // ************************************************************************************************ /** * Callback to handle the Import Playlist button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_ImportPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data) { //g_printf("Clicked on new playlist button\n"); gchar *playlistfilename = NULL; gchar *playlistname = NULL; GtkWidget *FileDialog; GtkFileFilter *OpenFormFilter, *OpenFormFilter2; // Save our current selected playlist! if (devicePlayLists != NULL) playlist_SavePlaylist(playlist_number); // Get our filename... FileDialog = gtk_file_chooser_dialog_new(_("Select Playlist to Import"), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(FileDialog), FALSE); OpenFormFilter = gtk_file_filter_new(); gtk_file_filter_add_pattern(OpenFormFilter, "*.m3u"); gtk_file_filter_set_name(OpenFormFilter, "m3u Playlists"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(FileDialog), OpenFormFilter); OpenFormFilter2 = gtk_file_filter_new(); gtk_file_filter_add_pattern(OpenFormFilter2, "*"); gtk_file_filter_set_name(OpenFormFilter2, "All Files"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(FileDialog), OpenFormFilter2); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { playlistfilename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); } gtk_widget_hide(FileDialog); gtk_widget_destroy(FileDialog); if (playlistfilename != NULL) { // Add in playlist to MTP device. playlistname = playlistImport(playlistfilename); // If our name is NULL, then the import failed... if (playlistname != NULL) { // Refresh our playlist information. devicePlayLists = getPlaylists(); gtk_list_store_clear(GTK_LIST_STORE(playlist_PL_List)); // Add it to our combobox #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(comboboxentry_playlist), g_strdup(playlistname)); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(comboboxentry_playlist), g_strdup(playlistname)); #endif // Set the active combobox item. comboboxentry_playlist_entries++; playlist_number = comboboxentry_playlist_entries - 1; gtk_combo_box_set_active(GTK_COMBO_BOX(comboboxentry_playlist), comboboxentry_playlist_entries - 1); SetPlaylistButtonState(TRUE); setPlaylistField(playlist_number); // Clean up fields. g_free(playlistname); } else { // Let the user know the import failed. g_fprintf(stderr, _("The playlist failed to import correctly.\n")); displayError(_("The playlist failed to import correctly.\n")); } // Clean up fields. g_free(playlistfilename); } } // end on_Playlist_ImportPlaylistButton_activate() // ************************************************************************************************ /** * Callback to handle the Export Playlist button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_ExportPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data) { gchar *playlistfilename = NULL; GtkWidget *FileDialog; // Save our current selected playlist! if (devicePlayLists != NULL) playlist_SavePlaylist(playlist_number); gint PlayListID = gtk_combo_box_get_active(GTK_COMBO_BOX(comboboxentry_playlist)); if (PlayListID != -1) { // We have something selected so lets do the dance. LIBMTP_playlist_t* tmpplaylist = devicePlayLists; if (PlayListID > 0) { while (PlayListID--) if (tmpplaylist->next != NULL) tmpplaylist = tmpplaylist->next; } // We should be in the correct playlist LIBMTP structure. playlistfilename = g_strdup_printf("%s.%s", tmpplaylist->name, "m3u"); FileDialog = gtk_file_chooser_dialog_new(_("Save as..."), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(FileDialog), playlistfilename); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { playlistfilename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); } gtk_widget_hide(FileDialog); gtk_widget_destroy(FileDialog); if (playlistfilename != NULL) { playlistExport(playlistfilename, tmpplaylist); g_free(playlistfilename); } } } // end on_Playlist_ExportPlaylistButton_activate() // ************************************************************************************************ /** * Callback to handle the Delete Playlist button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_DelPlaylistButton_activate(GtkMenuItem *menuitem, gpointer user_data) { gint PlayListID = gtk_combo_box_get_active(GTK_COMBO_BOX(comboboxentry_playlist)); if (PlayListID != -1) { // We have something selected so lets do the dance. LIBMTP_playlist_t* tmpplaylist = devicePlayLists; if (PlayListID > 0) { while (PlayListID--) if (tmpplaylist->next != NULL) tmpplaylist = tmpplaylist->next; } // We should be in the correct playlist LIBMTP structure. playlistDelete(tmpplaylist); // Clear the PL list view box gtk_list_store_clear(GTK_LIST_STORE(playlist_PL_List)); // Rebuild the playlist structure and combobox. devicePlayLists = getPlaylists(); setPlayListComboBox(); } } // end on_Playlist_DelPlaylistButton_activate() // ************************************************************************************************ /** * Callback to handle the Delete Track button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_DelFileButton_activate(GtkMenuItem *menuitem, gpointer user_data) { if (playlist_PL_ListGetSelection() == NULL) return; playlist_PL_ListRemove(playlist_PL_ListGetSelection()); } // end on_Playlist_DelFileButton_activate() // ************************************************************************************************ /** * Callback to handle the Add Track button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_AddFileButton_activate(GtkMenuItem *menuitem, gpointer user_data) { //g_printf("Clicked on add file in playlist button\n"); if (playlist_TrackList_GetSelection() == NULL) return; playlist_TrackList_Add(playlist_TrackList_GetSelection()); } // end on_Playlist_AddFileButton_activate() // ************************************************************************************************ /** * Callback to handle the Move Track Up button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_FileUpButton_activate(GtkMenuItem *menuitem, gpointer user_data) { playlist_move_files(-1); } // end on_Playlist_FileUpButton_activate() // ************************************************************************************************ /** * Callback to handle the Move Track Down button in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_FileDownButton_activate(GtkMenuItem *menuitem, gpointer user_data) { playlist_move_files(1); } // end on_Playlist_FileDownButton_activate() // ************************************************************************************************ /** * Callback to handle the change of Playlist selection in the Playlist editor dialog. * @param menuitem * @param user_data */ void on_Playlist_Combobox_activate(GtkComboBox *combobox, gpointer user_data) { // Save our current selected playlist playlist_SavePlaylist(playlist_number); // Get our new playlist ID, and display the contents of it. playlist_number = gtk_combo_box_get_active(GTK_COMBO_BOX(comboboxentry_playlist)); setPlaylistField(playlist_number); } // end on_Playlist_Combobox_activate() // ************************************************************************************************ /** * Callback to handle the change of columns viewable in the main window. * @param menuitem * @param user_data */ void on_view_activate(GtkMenuItem *menuitem, gpointer user_data) { #if GMTP_USE_GTK2 gchar *gconf_path = NULL; gboolean state = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); // Main menu. if ((void *) menuitem == (void *) menu_view_filesize) gconf_path = g_strdup("/apps/gMTP/viewFileSize"); if ((void *) menuitem == (void *) menu_view_filetype) gconf_path = g_strdup("/apps/gMTP/viewFileType"); if ((void *) menuitem == (void *) menu_view_track_number) gconf_path = g_strdup("/apps/gMTP/viewTrackNumber"); if ((void *) menuitem == (void *) menu_view_title) gconf_path = g_strdup("/apps/gMTP/viewTitle"); if ((void *) menuitem == (void *) menu_view_artist) gconf_path = g_strdup("/apps/gMTP/viewArtist"); if ((void *) menuitem == (void *) menu_view_album) gconf_path = g_strdup("/apps/gMTP/viewAlbum"); if ((void *) menuitem == (void *) menu_view_year) gconf_path = g_strdup("/apps/gMTP/viewYear"); if ((void *) menuitem == (void *) menu_view_genre) gconf_path = g_strdup("/apps/gMTP/viewGenre"); if ((void *) menuitem == (void *) menu_view_duration) gconf_path = g_strdup("/apps/gMTP/viewDuration"); if ((void *) menuitem == (void *) menu_view_folders) gconf_path = g_strdup("/apps/gMTP/viewFolders"); // context menu if ((void *) menuitem == (void *) cViewSize) gconf_path = g_strdup("/apps/gMTP/viewFileSize"); if ((void *) menuitem == (void *) cViewType) gconf_path = g_strdup("/apps/gMTP/viewFileType"); if ((void *) menuitem == (void *) cViewTrackNumber) gconf_path = g_strdup("/apps/gMTP/viewTrackNumber"); if ((void *) menuitem == (void *) cViewTrackName) gconf_path = g_strdup("/apps/gMTP/viewTitle"); if ((void *) menuitem == (void *) cViewArtist) gconf_path = g_strdup("/apps/gMTP/viewArtist"); if ((void *) menuitem == (void *) cViewAlbum) gconf_path = g_strdup("/apps/gMTP/viewAlbum"); if ((void *) menuitem == (void *) cViewYear) gconf_path = g_strdup("/apps/gMTP/viewYear"); if ((void *) menuitem == (void *) cViewGenre) gconf_path = g_strdup("/apps/gMTP/viewGenre"); if ((void *) menuitem == (void *) cViewDuration) gconf_path = g_strdup("/apps/gMTP/viewDuration"); if ((gconfconnect != NULL) && (gconf_path != NULL)) { gconf_client_set_bool(gconfconnect, gconf_path, state, NULL); g_free(gconf_path); } #else gchar *gsetting_path = NULL; gboolean state = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); // main menu if ((void *) menuitem == (void *) menu_view_filesize) gsetting_path = g_strdup("viewfilesize"); if ((void *) menuitem == (void *) menu_view_filetype) gsetting_path = g_strdup("viewfiletype"); if ((void *) menuitem == (void *) menu_view_track_number) gsetting_path = g_strdup("viewtracknumber"); if ((void *) menuitem == (void *) menu_view_title) gsetting_path = g_strdup("viewtitle"); if ((void *) menuitem == (void *) menu_view_artist) gsetting_path = g_strdup("viewartist"); if ((void *) menuitem == (void *) menu_view_album) gsetting_path = g_strdup("viewalbum"); if ((void *) menuitem == (void *) menu_view_year) gsetting_path = g_strdup("viewyear"); if ((void *) menuitem == (void *) menu_view_genre) gsetting_path = g_strdup("viewgenre"); if ((void *) menuitem == (void *) menu_view_duration) gsetting_path = g_strdup("viewduration"); if ((void *) menuitem == (void *) menu_view_folders) gsetting_path = g_strdup("viewfolders"); //context menu. if ((void *) menuitem == (void *) cViewSize) gsetting_path = g_strdup("viewfilesize"); if ((void *) menuitem == (void *) cViewType) gsetting_path = g_strdup("viewfiletype"); if ((void *) menuitem == (void *) cViewTrackNumber) gsetting_path = g_strdup("viewtracknumber"); if ((void *) menuitem == (void *) cViewTrackName) gsetting_path = g_strdup("viewtitle"); if ((void *) menuitem == (void *) cViewArtist) gsetting_path = g_strdup("viewartist"); if ((void *) menuitem == (void *) cViewAlbum) gsetting_path = g_strdup("viewalbum"); if ((void *) menuitem == (void *) cViewYear) gsetting_path = g_strdup("viewyear"); if ((void *) menuitem == (void *) cViewGenre) gsetting_path = g_strdup("viewgenre"); if ((void *) menuitem == (void *) cViewDuration) gsetting_path = g_strdup("viewduration"); if ((gsettings_connect != NULL) && (gsetting_path != NULL)) { g_settings_set_boolean(gsettings_connect, gsetting_path, state); g_settings_sync(); g_free(gsetting_path); } #endif } // end on_view_activate() // ************************************************************************************************ /** * Callback to handle when a user closes the Progress Dialog box, via the X button. * @param window * @param user_data */ void on_progressDialog_Close(GtkWidget *window, gpointer user_data) { // Set the global flag that the user has done it. progressDialog_killed = TRUE; } // end on_progressDialog_Close() // ************************************************************************************************ /** * Callback to handle when a user presses the Cancel button in the Progress Dialog box * @param window * @param user_data */ void on_progressDialog_Cancel(GtkWidget *button, gpointer user_data) { // Set the global flag that the user has done it. progressDialog_killed = TRUE; // Destroy the dialog box. gtk_widget_hide(progressDialog); gtk_widget_destroy(progressDialog); } // end on_progressDialog_Cancel() // ************************************************************************************************ /** * Callback to handle user asking to create a new playlist from the AutoAddTrack to Playlist option. * @param button * @param user_data */ void on_TrackPlaylist_NewPlaylistButton_activate(GtkWidget *button, gpointer user_data) { gchar *playlistname = NULL; gint combobox_entries = 0; playlistname = displayPlaylistNewDialog(); if (playlistname != NULL) { // Add in playlist to MTP device. playlistAdd(playlistname); // Refresh our playlist information. devicePlayLists = getPlaylists(); // Add it to our combobox #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(combobox_AddTrackPlaylist), g_strdup(playlistname)); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox_AddTrackPlaylist), g_strdup(playlistname)); #endif g_free(playlistname); // Set the active combobox item. combobox_entries = gtk_tree_model_iter_n_children(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox_AddTrackPlaylist)), NULL); gtk_combo_box_set_active(GTK_COMBO_BOX(combobox_AddTrackPlaylist), combobox_entries - 1); } } // ************************************************************************************************ /** * Callback to handle adding file to playlist. * @param menuitem * @param user_data */ void on_fileAddToPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { displayInformation(_("No files/folders selected?")); return; } // Display the select playlist dialog; int32_t addTrackPlaylistID = displayAddTrackPlaylistDialog(TRUE); // Now add the actual files from the MTP device. if (addTrackPlaylistID != GMTP_NO_PLAYLIST) { fileListAddToPlaylist(fileListGetSelection(), addTrackPlaylistID); } } // ************************************************************************************************ /** * Callback to handle removing file from playlist. * @param menuitem * @param user_data */ void on_fileRemoveFromPlaylist_activate(GtkMenuItem *menuitem, gpointer user_data) { // Let's check to see if we have anything selected in our treeview? if (fileListGetSelection() == NULL) { displayInformation(_("No files/folders selected?")); return; } // Display the select playlist dialog; int32_t addTrackPlaylistID = displayAddTrackPlaylistDialog(FALSE); // Now remove the actual files from the MTP device. if (addTrackPlaylistID != GMTP_NO_PLAYLIST) { fileListRemoveFromPlaylist(fileListGetSelection(), addTrackPlaylistID); } } // ************************************************************************************************ /** * Callback to handle when a row is selected in the folder list. * @param treeselection * @param user_data */ void on_treeviewFolders_rowactivated(GtkTreeSelection *treeselection, gpointer user_data) { // Block the handler from running ... g_signal_handler_block((gpointer) fileSelection, fileSelectHandler); g_signal_handler_block((gpointer) folderSelection, folderSelectHandler); if ((void *) treeselection == (void *) fileSelection) { gtk_tree_selection_unselect_all(folderSelection); } else { gtk_tree_selection_unselect_all(fileSelection); } // Unblock the handler from running ... g_signal_handler_unblock((gpointer) fileSelection, fileSelectHandler); g_signal_handler_unblock((gpointer) folderSelection, folderSelectHandler); } data */ void on_Playlist_FileDownButton_activate(GtkMenuItem *menuitem, gpointer user_data) { playlist_move_files(1); } // end on_Playlist_FileDownButton_activate() // *****************************************************************gMTP/src/dnd.c000064401651440000012000000246151176535257100140540ustar00darranstaff00003030200010/* * * File: dnd.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ /* This file contains all the Drag and Drop Functionality for gMTP */ #include "config.h" #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #endif #include #include #include #include #include #include "main.h" #include "interface.h" #include "callbacks.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" GtkTargetEntry _gmtp_drop_types[] = { {"text/plain", 0, GMTP_DROP_PLAINTEXT}, {"text/uri-list", 0, GMTP_DROP_URLENCODED}, {"STRING", 0, GMTP_DROP_STRING} }; // ************************************************************************************************ /** * Callback to handle the initial drop of data into gmtp. * @param widget * @param context * @param x * @param y * @param selection_data * @param info * @param time * @param user_data */ void gmtp_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer user_data) { #if GMTP_USE_GTK2 if (selection_data->data) #else if (gtk_selection_data_get_data(selection_data)) #endif { GSList* files; #if GMTP_USE_GTK2 files = getFilesListURI((gchar *) selection_data->data); #else files = getFilesListURI((gchar *) gtk_selection_data_get_data(selection_data)); #endif // Set the Playlist ID to be asked if needed. if (Preferences.auto_add_track_to_playlist == TRUE) { addTrackPlaylistID = GMTP_REQUIRE_PLAYLIST; } else { addTrackPlaylistID = GMTP_NO_PLAYLIST; } AlbumErrorIgnore = FALSE; // Add the files. if (files != NULL) { g_slist_foreach(files, (GFunc) __filesAdd, NULL); } // Now clear the GList; g_slist_foreach(files, (GFunc) g_free, NULL); g_slist_free(files); // Now do a device rescan to see the new files. deviceRescan(); deviceoverwriteop = MTP_ASK; } } // gmtp_drag_data_received() // ************************************************************************************************ /** * Callback to handle the initial drop of data into gmtp. * @param widget * @param context * @param x * @param y * @param selection_data * @param info * @param time * @param user_data */ void gmtpfolders_drag_data_received(GtkWidget * widget, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint time, gpointer user_data) { //uint32_t mainFolderID = 0; int64_t targetFolderID = 0; g_signal_stop_emission_by_name((gpointer) treeviewFolders, "drag-data-received"); #if GMTP_USE_GTK2 if (selection_data->data) #else if (gtk_selection_data_get_data(selection_data)) #endif { //mainFolderID = currentFolderID; // Get our target folder ID... targetFolderID = folderListGetSelection(); if (targetFolderID != -1) { currentFolderID = targetFolderID; GSList* files; #if GMTP_USE_GTK2 files = getFilesListURI((gchar *) selection_data->data); #else files = getFilesListURI((gchar *) gtk_selection_data_get_data(selection_data)); #endif // Set the Playlist ID to be asked if needed. if (Preferences.auto_add_track_to_playlist == TRUE) { addTrackPlaylistID = GMTP_REQUIRE_PLAYLIST; } else { addTrackPlaylistID = GMTP_NO_PLAYLIST; } AlbumErrorIgnore = FALSE; // Add the files. if (files != NULL) { g_slist_foreach(files, (GFunc) __filesAdd, NULL); } // Now clear the GList; g_slist_foreach(files, (GFunc) g_free, NULL); g_slist_free(files); // Now do a device rescan to see the new files. // Restore our current Folder ID. //currentFolderID = mainFolderID; } deviceRescan(); deviceoverwriteop = MTP_ASK; } } // ************************************************************************************************ /** * Handle drag motion across the folder widget. * @param widget * @param context * @param x * @param y * @param time */ void gmtpfolders_drag_motion_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { GtkTreePath *path; GtkTreeViewDropPosition pos; if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &pos)) { // Highlight the current row... gtk_tree_selection_select_path(folderSelection, path); // Get our folder ID in to which to drop the file. /* GtkTreeModel *sortmodel; GtkTreeIter iter; uint32_t objectID; gchar *filename = NULL; sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sortmodel), path); gtk_tree_model_get_iter(GTK_TREE_MODEL(folderList), &iter, path); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &iter, COL_FOL_ID, &objectID, COL_FOL_NAME_HIDDEN, &filename, -1); printf("X: %d, Y: %d, Object: %d , Name: %s\n", x, y, objectID, filename); */ } } // ************************************************************************************************ /** * Take the raw list of filenames as given via a Drop operation, and return a nicely formatted * list of filenames. * @param rawdata * @return GList of all individual filenames. */ GSList* getFilesListURI(gchar* rawdata) { // The data is just the data in string form // Files are in the URI form of file:///filename\n so just look for those, // and if found see if a folder or not? // Then create a slist of those and use filesAdd() to add them in . GSList* filelist; gchar* tmpstring; gchar *fullpath; gchar *filepath; gchar *token; const char delimit[] = "\n\r"; filelist = NULL; fullpath = g_strdup(rawdata); token = strtok(fullpath, delimit); while ((token != NULL)) { // Now test to see if we have it here... filepath = g_strdup(token); // See if we have a local file URI, otherwise discard. if (!g_ascii_strncasecmp(filepath, "file://", 7)) { tmpstring = g_filename_from_uri(filepath, NULL, NULL); // See if we have a file or a folder? if (g_file_test(tmpstring, G_FILE_TEST_IS_REGULAR) == TRUE) { // Add the file to the list filelist = g_slist_append(filelist, g_strdup(tmpstring)); } else { // Otherwise we have a folder, so add the folder and all it's contents. addFilesinFolder(tmpstring); } g_free(tmpstring); } token = strtok(NULL, delimit); g_free(filepath); } g_free(fullpath); return filelist; } // end getFilesListURI() // ************************************************************************************************ /** * Process a folder looking for all files in that folder, and upload all those files. * @param foldername */ void addFilesinFolder(gchar* foldername) { // foldername is the name of the folder as the absolute path with leading / // We save the currentFolderID, create a new folder on the device, // and set currentFolderID to the new folders ID. // Then scan the folder (on the filesystem) adding in files as needed. // Found folders are always added first, so files are copied from // the deepest level of the folder hierarchy first as well, and we // work our way back down towards to the initial folder that was // dragged in. // Lastly we restore the currentFolderID back to what it was. GDir *fileImageDir; GSList* filelist; const gchar *filename; gchar* relative_foldername; gchar *tmpstring; uint32_t oldFolderID; filelist = NULL; // Save our current working folder. oldFolderID = currentFolderID; // Get just the folder name, as we are given a full absolute path. relative_foldername = basename(foldername); if (relative_foldername != NULL) { // Add our folder to the mtp device and set our new current working folder ID. currentFolderID = folderAdd(relative_foldername); } // Start scanning the folder on the filesystem for our new files/folders. fileImageDir = g_dir_open(foldername, 0, NULL); // Now parse that directory looking for JPEG/PNG files (based on settings). // If we find one, we create a new GString and add it to the list. if (fileImageDir != NULL) { filename = g_dir_read_name(fileImageDir); while (filename != NULL) { // See if a file or a folder? tmpstring = g_strconcat(foldername, "/", filename, NULL); if (g_file_test(tmpstring, G_FILE_TEST_IS_REGULAR) == TRUE) { // We have a regular file. So add it to the list. filelist = g_slist_append(filelist, g_strdup(tmpstring)); } else { if (g_file_test(tmpstring, G_FILE_TEST_IS_DIR) == TRUE) { // We have another folder so recursively call ourselves... addFilesinFolder(tmpstring); } } filename = g_dir_read_name(fileImageDir); g_free(tmpstring); } } // Set the Playlist ID to be asked if needed. if (Preferences.auto_add_track_to_playlist == TRUE) { addTrackPlaylistID = GMTP_REQUIRE_PLAYLIST; } else { addTrackPlaylistID = GMTP_NO_PLAYLIST; } AlbumErrorIgnore = FALSE; // Upload our given files in the current selected folder. if (filelist != NULL) { g_slist_foreach(filelist, (GFunc) __filesAdd, NULL); } // Now clear the GList; g_slist_foreach(filelist, (GFunc) g_free, NULL); g_slist_free(filelist); if (fileImageDir != NULL) g_dir_close(fileImageDir); // Restore our current working folder. currentFolderID = oldFolderID; } // end addFilesinFolder() gMTP/src/mtp.h000064401651440000012000000072451204762771600141140ustar00darranstaff00003030200010/* * * File: mtp.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _MTP_H #define _MTP_H #ifdef __cplusplus extern "C" { #endif enum MTP_ERROR { MTP_SUCCESS, MTP_NO_DEVICE, MTP_GENERAL_FAILURE, MTP_DEVICE_FULL, MTP_NO_MTP_DEVICE }; enum MTP_PLAYLIST_INSTANCES { MTP_PLAYLIST_ALL_INSTANCES, MTP_PLAYLIST_FIRST_INSTANCE, MTP_PLAYLIST_LAST_INSTANCE }; #define MTP_DEVICE_SINGLE_STORAGE -1 gboolean AlbumErrorIgnore; typedef struct { gchar* file_extension; LIBMTP_filetype_t file_type; } MTP_file_ext_struct; guint deviceConnect(); guint deviceDisconnect(); void deviceProperties(); void clearDeviceFiles(LIBMTP_file_t * filelist); void clearAlbumStruc(LIBMTP_album_t * albumlist); void clearDevicePlaylist(LIBMTP_playlist_t * playlist_list); void clearDeviceTracks(LIBMTP_track_t * tracklist); void deviceRescan(); void filesUpateFileList(); void filesRename(gchar* filename, uint32_t ObjectID); void filesAdd(gchar* filename); void filesDelete(gchar* filename, uint32_t objectID); void filesDownload(gchar* filename, uint32_t objectID); gboolean fileExists(gchar* filename); guint32 folderAdd(gchar* foldername); void folderDelete(LIBMTP_folder_t* folderptr, guint level); void folderDeleteChildrenFiles(guint folderID); void folderDownload(gchar * foldername, uint32_t folderID, gboolean isParent); void albumAddTrackToAlbum(LIBMTP_album_t* albuminfo, LIBMTP_track_t* trackinfo); void albumAddArt(guint32 album_id, gchar* filename); void albumDeleteArt(guint32 album_id); LIBMTP_filesampledata_t * albumGetArt(LIBMTP_album_t* selectedAlbum); void setDeviceName(gchar* devicename); void buildFolderIDs(GSList **list, LIBMTP_folder_t * folderptr); uint32_t getParentFolderID(LIBMTP_folder_t *tmpfolder, uint32_t currentFolderID); LIBMTP_folder_t* getParentFolderPtr(LIBMTP_folder_t *tmpfolder, uint32_t currentFolderID); LIBMTP_folder_t* getCurrentFolderPtr(LIBMTP_folder_t *tmpfolder, uint32_t FolderID); LIBMTP_filetype_t find_filetype(const gchar * filename); gchar* find_filetype_ext(LIBMTP_filetype_t filetype); LIBMTP_devicestorage_t* getCurrentDeviceStoragePtr(gint StorageID); int setNewParentFolderID(uint32_t objectID, uint32_t folderID); // Playlist support. LIBMTP_playlist_t* getPlaylists(void); LIBMTP_track_t* getTracks(void); void playlistAdd(gchar* playlistname); void playlistDelete(LIBMTP_playlist_t * tmpplaylist); void playlistUpdate(LIBMTP_playlist_t * tmpplaylist); void playlistAddTrack(LIBMTP_playlist_t* playlist, LIBMTP_track_t* track); void playlistRemoveTrack(LIBMTP_playlist_t* playlist, LIBMTP_track_t* track, uint32_t instances); gchar* playlistImport(gchar * filename); void playlistExport(gchar * filename, LIBMTP_playlist_t * playlist); // Format device. void formatStorageDevice(); // File operation helper. gchar* getFullFilename(uint32_t item_id); uint32_t getFileID(gchar* filename, gboolean ignorepath); uint32_t getFolderID(LIBMTP_folder_t* folderptr, gchar* foldername); gchar* getFullFolderPath(uint32_t folderid); GSList *filesSearch(gchar *searchstring, gboolean searchfiles, gboolean searchmeta); void folderSearch(GPatternSpec *pspec, GSList **list, LIBMTP_folder_t* folderptr); #ifdef __cplusplus } #endif #endif /* _MTP_H */ gMTP/src/config.h000064401651440000012000000016741200772455400145530ustar00darranstaff00003030200010/* * * File: config.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _CONFIG_H #define _CONFIG_H #ifdef __cplusplus extern "C" { #endif #define PACKAGE "gmtp" #define PACKAGE_TITLE "gMTP" #define PACKAGE_VERSION "1.3.4" #define GMTP_GSETTINGS_SCHEMA "org.gnome.gmtp" #define ENABLE_NLS // #define GMTP_USE_GTK3 #ifndef GMTP_USE_GTK3 #ifdef GMTP_USE_GTK2 #undef GMTP_USE_GTK2 #endif #define GMTP_USE_GTK2 1 #else #ifdef GMTP_USE_GTK2 #undef GMTP_USE_GTK2 #endif #define GMTP_USE_GTK2 0 #endif /* Define that all files are to be C99 compliant with POSIX. */ #define _STDC_C99 #define _POSIX_C_SOURCE 200112L #define _XOPEN_SOURCE 600 #ifdef __cplusplus } #endif #endif /* _CONFIG_H */ gMTP/src/interface.c000064401651440000012000007061671205063513700152460ustar00darranstaff00003030200010/* * * File: interface.c * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #if GMTP_USE_GTK2 #include #include #else #include #include #endif #include #include #include #include "main.h" #include "callbacks.h" #include "interface.h" #include "mtp.h" #include "prefs.h" #include "dnd.h" void setupFileList(); GtkTreeViewColumn *setupFolderList(); void __fileRemove(GtkTreeRowReference *Row); void __fileDownload(GtkTreeRowReference *Row); void __folderRemove(GtkTreeRowReference *Row); GtkWidget *windowMain; GtkWidget *scrolledwindowMain; GtkWidget *toolbuttonAddFile; GtkWidget *toolbuttonRetrieve; GtkWidget *toolbuttonRemoveFile; GtkWidget *toolbuttonRescan; GtkWidget *toolbuttonAlbumArt; GtkWidget *toolbuttonPlaylist; GtkWidget *toolbuttonProperties; GtkWidget *properties1; GtkWidget *fileConnect; GtkWidget *fileAdd; GtkWidget *fileDownload; GtkWidget *fileRemove; GtkWidget *fileRename; GtkWidget *fileMove; GtkWidget *fileNewFolder; GtkWidget *fileRemoveFolder; GtkWidget *fileRescan; GtkWidget *editDeviceName; GtkWidget *editFormatDevice; GtkWidget *editAddAlbumArt; GtkWidget *editPlaylist; GtkWidget *editFind; GtkWidget *editSelectAll; GtkWidget *contextMenu; GtkWidget *contextMenuColumn; GtkWidget *contextMenuFolder; GtkWidget* cfileAdd; GtkWidget* cfileNewFolder; GtkWidget *findToolbar; GtkWidget *scrolledwindowFolders; #if GMTP_USE_GTK2 GtkTooltips *tooltipsToolbar; #endif // Menu widget for Properties GtkListStore *fileList; GtkTreeModel *fileListModel; GtkTreeSelection *fileSelection; GtkTreeStore *folderList; GtkTreeModel *folderListModel; GtkTreeViewColumn *folderColumn; GtkTreeSelection *folderSelection; GList *fileSelection_RowReferences = NULL; gulong folderSelectHandler = 0; gulong fileSelectHandler = 0; // Columns in main file view; GtkTreeViewColumn *column_Size; GtkTreeViewColumn *column_Type; GtkTreeViewColumn *column_Track_Number; GtkTreeViewColumn *column_Title; GtkTreeViewColumn *column_Artist; GtkTreeViewColumn *column_Album; GtkTreeViewColumn *column_Year; GtkTreeViewColumn *column_Genre; GtkTreeViewColumn *column_Duration; GtkTreeViewColumn *column_Location; GtkWidget *menu_view_filesize; GtkWidget *menu_view_filetype; GtkWidget *menu_view_track_number; GtkWidget *menu_view_title; GtkWidget *menu_view_artist; GtkWidget *menu_view_album; GtkWidget *menu_view_year; GtkWidget *menu_view_genre; GtkWidget *menu_view_duration; GtkWidget *menu_view_folders; GtkWidget* cViewSize; GtkWidget* cViewType; GtkWidget* cViewTrackName; GtkWidget* cViewTrackNumber; GtkWidget* cViewArtist; GtkWidget* cViewAlbum; GtkWidget* cViewYear; GtkWidget* cViewGenre; GtkWidget* cViewDuration; GtkWidget* cfFolderAdd; GtkWidget* cfFolderDelete; GtkWidget* cfFolderRename; GtkWidget* cfFolderMove; GtkWidget* cfFolderRefresh; // Widgets for preferences buttons; GtkWidget *checkbuttonDeviceConnect; GtkWidget *entryDownloadPath; GtkWidget *entryUploadPath; GtkWidget *checkbuttonDownloadPath; GtkWidget *checkbuttonConfirmFileOp; GtkWidget *checkbuttonConfirmOverWriteFileOp; GtkWidget *checkbuttonAutoAddTrackPlaylist; GtkWidget *checkbuttonIgnorePathInPlaylist; GtkWidget *checkbuttonSuppressAlbumErrors; GtkWidget *checkbuttonAltAccessMethod; // Widget for Progress Bar Dialog box. GtkWidget *progressDialog; GtkWidget *progressDialog_Text; GtkWidget *progressDialog_Bar; gchar *progressDialog_filename; gboolean progressDialog_killed = FALSE; // Widget for formatDevice progress bar. GtkWidget *formatDialog_progressBar1; // Flags for overwriting files of host PC and device. gint fileoverwriteop = MTP_ASK; // Flag to allow overwrite of files on device. gint deviceoverwriteop = MTP_ASK; // Find options and variables. gboolean inFindMode = FALSE; GSList *searchList = NULL; GtkWidget *FindToolbar_entry_FindText; GtkWidget *FindToolbar_checkbutton_FindFiles; GtkWidget *FindToolbar_checkbutton_TrackInformation; // AlbumArt Dialog global pointers GtkWidget *AlbumArtDialog; GtkWidget *AlbumArtFilename; GtkWidget *AlbumArtImage; GtkWidget *buttonAlbumAdd; GtkWidget *buttonAlbumDownload; GtkWidget *buttonAlbumDelete; GtkWidget *textboxAlbumArt; // Playlist GtkWidget *comboboxentry_playlist; gint playlist_number = 0; gint comboboxentry_playlist_entries = 0; gint playlist_track_count = 0; GtkWidget *treeview_Avail_Files; GtkWidget *treeview_Playlist_Files; GtkListStore *playlist_TrackList; GtkTreeSelection *playlist_TrackSelection; GList *playlist_Selection_TrackRowReferences = NULL; GtkListStore *playlist_PL_List; GtkTreeSelection *playlist_PL_Selection; GList *playlist_Selection_PL_RowReferences = NULL; // Buttons for playlist GtkWidget *button_Del_Playlist; GtkWidget *button_Export_Playlist; GtkWidget *button_File_Move_Up; GtkWidget *button_File_Move_Down; GtkWidget *button_Del_File; GtkWidget *button_Add_Files; // Combobox used in AddTrackPlaylist feature. GtkWidget *combobox_AddTrackPlaylist; // File/Folder Move operations. int64_t fileMoveTargetFolder = 0; // ************************************************************************************************ /** * Create the main window for the application * @return Ptr to the main window widget */ GtkWidget* create_windowMain(void) { GtkWidget *vbox1; GtkWidget *menubarMain; GtkWidget *menuitem1; GtkWidget *menuitem1_menu; GtkWidget *menuseparator1; GtkWidget *menuseparator2; GtkWidget *menuseparator3; GtkWidget *menuseparator4; GtkWidget *menuseparator5; GtkWidget *menuseparator6; GtkWidget *menuseparator7; GtkWidget *menuseparator8; GtkWidget *menuseparator9; GtkWidget *quit1; GtkWidget *menuitem2; GtkWidget *menuitem2_menu; GtkWidget *preferences1; GtkWidget *menuView; GtkWidget *menuView_menu; GtkWidget *menuitem4; GtkWidget *menuitem4_menu; GtkWidget *about1; GtkWidget *handlebox1; GtkWidget *toolbarMain; gint tmp_toolbar_icon_size; GtkWidget *tmp_image; GtkWidget *toolbuttonPreferences; GtkWidget *hpanel; GtkWidget *toolbarSeparator; GtkWidget *toolbarSeparator2; GtkWidget *toolbuttonQuit; GtkAccelGroup *accel_group; GtkWidget *FindToolbar_hbox_FindToolbar; GtkWidget *FindToolbar_label_FindLabel; GtkWidget *FindToolbar_FindButton; GtkWidget *FindToolbar_CloseButton; GtkWidget *menuText; accel_group = gtk_accel_group_new(); windowMain = gtk_window_new(GTK_WINDOW_TOPLEVEL); setWindowTitle(NULL); gtk_window_set_default_size(GTK_WINDOW(windowMain), 880, 400); gtk_window_set_icon_from_file(GTK_WINDOW(windowMain), file_icon48_png, NULL); vbox1 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(windowMain), vbox1); menubarMain = gtk_menu_bar_new(); gtk_widget_show(menubarMain); gtk_box_pack_start(GTK_BOX(vbox1), menubarMain, FALSE, FALSE, 0); menuitem1 = gtk_menu_item_new_with_mnemonic(_("_File")); gtk_widget_show(menuitem1); gtk_container_add(GTK_CONTAINER(menubarMain), menuitem1); menuitem1_menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem1), menuitem1_menu); fileConnect = gtk_image_menu_item_new_from_stock(GTK_STOCK_NETWORK, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileConnect)); gtk_label_set_text(GTK_LABEL(menuText), _("Connect Device")); gtk_widget_show(fileConnect); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileConnect); menuseparator4 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator4); gtk_container_add(GTK_CONTAINER(menuitem1_menu), menuseparator4); fileAdd = gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileAdd)); gtk_label_set_text(GTK_LABEL(menuText), _("Add Files")); gtk_widget_show(fileAdd); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileAdd); fileRemove = gtk_image_menu_item_new_from_stock(GTK_STOCK_REMOVE, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileRemove)); gtk_label_set_text(GTK_LABEL(menuText), _("Delete Files")); gtk_widget_show(fileRemove); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileRemove); fileRename = gtk_image_menu_item_new_from_stock(GTK_STOCK_STRIKETHROUGH, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileRename)); gtk_label_set_text(GTK_LABEL(menuText), _("Rename File")); gtk_widget_show(fileRename); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileRename); fileMove = gtk_image_menu_item_new_from_stock(GTK_STOCK_CUT, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileMove)); gtk_label_set_text(GTK_LABEL(menuText), _("Move To...")); gtk_widget_show(fileMove); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileMove); fileDownload = gtk_image_menu_item_new_from_stock(GTK_STOCK_GOTO_BOTTOM, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileDownload)); gtk_label_set_text(GTK_LABEL(menuText), _("Download Files")); gtk_widget_show(fileDownload); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileDownload); menuseparator1 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator1); gtk_container_add(GTK_CONTAINER(menuitem1_menu), menuseparator1); fileNewFolder = gtk_image_menu_item_new_with_label(_("Create Folder")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(fileNewFolder), gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU)); gtk_widget_show(fileNewFolder); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileNewFolder); fileRemoveFolder = gtk_image_menu_item_new_from_stock(GTK_STOCK_DELETE, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileRemoveFolder)); gtk_label_set_text(GTK_LABEL(menuText), _("Delete Folder")); gtk_widget_show(fileRemoveFolder); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileRemoveFolder); menuseparator2 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator2); gtk_container_add(GTK_CONTAINER(menuitem1_menu), menuseparator2); fileRescan = gtk_image_menu_item_new_from_stock(GTK_STOCK_REFRESH, accel_group); menuText = gtk_bin_get_child(GTK_BIN(fileRescan)); gtk_label_set_text(GTK_LABEL(menuText), _("Refresh Device")); gtk_widget_show(fileRescan); gtk_container_add(GTK_CONTAINER(menuitem1_menu), fileRescan); properties1 = gtk_image_menu_item_new_from_stock(GTK_STOCK_PROPERTIES, accel_group); menuText = gtk_bin_get_child(GTK_BIN(properties1)); gtk_label_set_text(GTK_LABEL(menuText), _("Device Properties")); gtk_widget_show(properties1); gtk_container_add(GTK_CONTAINER(menuitem1_menu), properties1); menuseparator3 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator3); gtk_container_add(GTK_CONTAINER(menuitem1_menu), menuseparator3); quit1 = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group); gtk_widget_show(quit1); gtk_container_add(GTK_CONTAINER(menuitem1_menu), quit1); menuitem2 = gtk_menu_item_new_with_mnemonic(_("_Edit")); gtk_widget_show(menuitem2); gtk_container_add(GTK_CONTAINER(menubarMain), menuitem2); menuitem2_menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem2), menuitem2_menu); #if defined(GTK_STOCK_SELECT_ALL) editSelectAll = gtk_image_menu_item_new_from_stock(GTK_STOCK_SELECT_ALL, accel_group); #else editSelectAll = gtk_menu_item_new_with_mnemonic(_("Select All")); #endif gtk_widget_show(editSelectAll); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editSelectAll); menuseparator6 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator6); gtk_container_add(GTK_CONTAINER(menuitem2_menu), menuseparator6); editFind = gtk_image_menu_item_new_from_stock(GTK_STOCK_FIND, accel_group); gtk_widget_show(editFind); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editFind); menuseparator9 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator9); gtk_container_add(GTK_CONTAINER(menuitem2_menu), menuseparator9); editDeviceName = gtk_image_menu_item_new_with_label(_("Change Device Name")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(editDeviceName), gtk_image_new_from_file(file_icon16_png)); gtk_widget_show(editDeviceName); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editDeviceName); editFormatDevice = gtk_image_menu_item_new_with_label(_("Format Device")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(editFormatDevice), gtk_image_new_from_file(file_format_png)); gtk_widget_show(editFormatDevice); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editFormatDevice); menuseparator7 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator7); gtk_container_add(GTK_CONTAINER(menuitem2_menu), menuseparator7); editAddAlbumArt = gtk_image_menu_item_new_from_stock(GTK_STOCK_CDROM, accel_group); menuText = gtk_bin_get_child(GTK_BIN(editAddAlbumArt)); gtk_label_set_text(GTK_LABEL(menuText), _("Album Art")); gtk_widget_show(editAddAlbumArt); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editAddAlbumArt); editPlaylist = gtk_image_menu_item_new_from_stock(GTK_STOCK_DND_MULTIPLE, accel_group); menuText = gtk_bin_get_child(GTK_BIN(editPlaylist)); gtk_label_set_text(GTK_LABEL(menuText), _("Edit Playlist(s)")); gtk_widget_show(editPlaylist); gtk_container_add(GTK_CONTAINER(menuitem2_menu), editPlaylist); menuseparator5 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator5); gtk_container_add(GTK_CONTAINER(menuitem2_menu), menuseparator5); preferences1 = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, accel_group); gtk_widget_show(preferences1); gtk_container_add(GTK_CONTAINER(menuitem2_menu), preferences1); menuView = gtk_menu_item_new_with_mnemonic(_("_View")); gtk_widget_show(menuView); gtk_container_add(GTK_CONTAINER(menubarMain), menuView); menuView_menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuView), menuView_menu); menu_view_folders = gtk_check_menu_item_new_with_label(_("Folders")); gtk_widget_show(menu_view_folders); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_folders); menuseparator8 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator8); gtk_container_add(GTK_CONTAINER(menuView_menu), menuseparator8); menu_view_filesize = gtk_check_menu_item_new_with_label(_("File Size")); gtk_widget_show(menu_view_filesize); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_filesize); menu_view_filetype = gtk_check_menu_item_new_with_label(_("File Type")); gtk_widget_show(menu_view_filetype); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_filetype); menu_view_track_number = gtk_check_menu_item_new_with_label(_("Track Number")); gtk_widget_show(menu_view_track_number); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_track_number); menu_view_title = gtk_check_menu_item_new_with_label(_("Track Name")); gtk_widget_show(menu_view_title); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_title); menu_view_artist = gtk_check_menu_item_new_with_label(_("Artist")); gtk_widget_show(menu_view_artist); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_artist); menu_view_album = gtk_check_menu_item_new_with_label(_("Album")); gtk_widget_show(menu_view_album); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_album); menu_view_year = gtk_check_menu_item_new_with_label(_("Year")); gtk_widget_show(menu_view_year); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_year); menu_view_genre = gtk_check_menu_item_new_with_label(_("Genre")); gtk_widget_show(menu_view_genre); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_genre); menu_view_duration = gtk_check_menu_item_new_with_label(_("Duration")); gtk_widget_show(menu_view_duration); gtk_container_add(GTK_CONTAINER(menuView_menu), menu_view_duration); menuitem4 = gtk_menu_item_new_with_mnemonic(_("_Help")); gtk_widget_show(menuitem4); gtk_container_add(GTK_CONTAINER(menubarMain), menuitem4); menuitem4_menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem4), menuitem4_menu); #if defined(GTK_STOCK_ABOUT) about1 = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, accel_group); #else about1 = gtk_image_menu_item_new_with_mnemonic(_("_About")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(about1), gtk_image_new_from_file(file_about_png)); #endif gtk_widget_show(about1); gtk_container_add(GTK_CONTAINER(menuitem4_menu), about1); handlebox1 = gtk_handle_box_new(); gtk_widget_show(handlebox1); gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(handlebox1), GTK_SHADOW_ETCHED_OUT); gtk_box_pack_start(GTK_BOX(vbox1), handlebox1, FALSE, FALSE, 0); #if GMTP_USE_GTK2 tooltipsToolbar = gtk_tooltips_new(); #endif toolbarMain = gtk_toolbar_new(); gtk_widget_show(toolbarMain); gtk_container_add(GTK_CONTAINER(handlebox1), toolbarMain); gtk_toolbar_set_style(GTK_TOOLBAR(toolbarMain), GTK_TOOLBAR_BOTH); tmp_toolbar_icon_size = gtk_toolbar_get_icon_size(GTK_TOOLBAR(toolbarMain)); #if GMTP_USE_GTK2 gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbarMain), TRUE); #else g_object_set(gtk_settings_get_default(), "gtk-enable-tooltips", TRUE, NULL); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_NETWORK, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonConnect = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Connect")); gtk_widget_show(toolbuttonConnect); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonConnect); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonConnect), GTK_TOOLTIPS(tooltipsToolbar), _("Connect/Disconnect to your device."), _("Connect/Disconnect to your device.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonConnect), _("Connect/Disconnect to your device.")); #endif toolbarSeparator = (GtkWidget*) gtk_separator_tool_item_new(); gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolbarSeparator), TRUE); gtk_widget_show(toolbarSeparator); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbarSeparator); tmp_image = gtk_image_new_from_stock(GTK_STOCK_ADD, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonAddFile = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Add")); gtk_widget_show(toolbuttonAddFile); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonAddFile); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonAddFile), GTK_TOOLTIPS(tooltipsToolbar), _("Add Files to your device."), _("Add a varity of Files to your device in the current folder.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonAddFile), _("Add Files to your device.")); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonRemoveFile = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Delete")); gtk_widget_show(toolbuttonRemoveFile); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonRemoveFile); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonRemoveFile), GTK_TOOLTIPS(tooltipsToolbar), _("Delete Files/Folders from your device."), _("Permanently remove files/folders from your device. Note: Albums are stored as *.alb files.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonRemoveFile), _("Delete Files/Folders from your device.")); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_GOTO_BOTTOM, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonRetrieve = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Download")); gtk_widget_show(toolbuttonRetrieve); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonRetrieve); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonRetrieve), GTK_TOOLTIPS(tooltipsToolbar), _("Download Files from your device to your Host PC."), _("Download files from your device to your PC. Default Download path is set in the preferences dialog.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonRetrieve), _("Download Files from your device to your Host PC.")); #endif toolbarSeparator2 = (GtkWidget*) gtk_separator_tool_item_new(); gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolbarSeparator2), TRUE); gtk_widget_show(toolbarSeparator2); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbarSeparator2); tmp_image = gtk_image_new_from_stock(GTK_STOCK_CDROM, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonAlbumArt = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Album Art")); gtk_widget_show(toolbuttonAlbumArt); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonAlbumArt); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonAlbumArt), GTK_TOOLTIPS(tooltipsToolbar), _("Upload an image file as Album Art."), _("Upload a JPG file and assign it as Album Art.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonAlbumArt), _("Upload an image file as Album Art.")); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_DND_MULTIPLE, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonPlaylist = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Playlists")); gtk_widget_show(toolbuttonPlaylist); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonPlaylist); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonPlaylist), GTK_TOOLTIPS(tooltipsToolbar), _("Add and Modify Playlists."), _("Add and Modify Playlists.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonPlaylist), _("Add and Modify Playlists.")); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_REFRESH, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonRescan = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Refresh")); gtk_widget_show(toolbuttonRescan); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonRescan); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonRescan), GTK_TOOLTIPS(tooltipsToolbar), _("Refresh File/Folder listing."), _("Refresh File/Folder listing.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonRescan), _("Refresh File/Folder listing.")); #endif toolbarSeparator = (GtkWidget*) gtk_separator_tool_item_new(); gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolbarSeparator), TRUE); gtk_widget_show(toolbarSeparator); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbarSeparator); tmp_image = gtk_image_new_from_stock(GTK_STOCK_PROPERTIES, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonProperties = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Properties")); gtk_widget_show(toolbuttonProperties); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonProperties); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonProperties), GTK_TOOLTIPS(tooltipsToolbar), _("View Device Properties."), _("View Device Properties.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonProperties), _("View Device Properties.")); #endif tmp_image = gtk_image_new_from_stock(GTK_STOCK_PREFERENCES, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonPreferences = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Preferences")); gtk_widget_show(toolbuttonPreferences); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonPreferences); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonPreferences), GTK_TOOLTIPS(tooltipsToolbar), _("View/Change gMTP Preferences."), _("View/Change gMTP Preferences.")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonPreferences), _("View/Change gMTP Preferences.")); #endif toolbarSeparator = (GtkWidget*) gtk_separator_tool_item_new(); gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolbarSeparator), TRUE); gtk_widget_show(toolbarSeparator); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbarSeparator); tmp_image = gtk_image_new_from_stock(GTK_STOCK_QUIT, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); toolbuttonQuit = (GtkWidget*) gtk_tool_button_new(tmp_image, _("Quit")); gtk_widget_show(toolbuttonQuit); gtk_container_add(GTK_CONTAINER(toolbarMain), toolbuttonQuit); #if GMTP_USE_GTK2 gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(toolbuttonQuit), GTK_TOOLTIPS(tooltipsToolbar), _("Quit gMTP."), _("Quit")); #else gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(toolbuttonQuit), _("Quit gMTP.")); #endif #if GMTP_USE_GTK2 gtk_tooltips_enable(tooltipsToolbar); #endif // Find toolbar; findToolbar = gtk_handle_box_new(); //gtk_widget_show(findToolbar); // Only show when the user selects the menu option. gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(findToolbar), GTK_SHADOW_ETCHED_OUT); gtk_box_pack_start(GTK_BOX(vbox1), findToolbar, FALSE, FALSE, 0); FindToolbar_hbox_FindToolbar = gtk_hbox_new(FALSE, 5); gtk_widget_show(FindToolbar_hbox_FindToolbar); gtk_container_add(GTK_CONTAINER(findToolbar), FindToolbar_hbox_FindToolbar); gtk_container_set_border_width(GTK_CONTAINER(FindToolbar_hbox_FindToolbar), 2); FindToolbar_label_FindLabel = gtk_label_new(_("Find:")); gtk_widget_show(FindToolbar_label_FindLabel); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_label_FindLabel, FALSE, FALSE, 0); gtk_label_set_justify(GTK_LABEL(FindToolbar_label_FindLabel), GTK_JUSTIFY_RIGHT); FindToolbar_entry_FindText = gtk_entry_new(); gtk_widget_show(FindToolbar_entry_FindText); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_entry_FindText, TRUE, TRUE, 0); gtk_entry_set_max_length(GTK_ENTRY(FindToolbar_entry_FindText), 256); FindToolbar_FindButton = gtk_button_new_from_stock(GTK_STOCK_FIND); gtk_widget_show(FindToolbar_FindButton); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_FindButton, FALSE, FALSE, 5); //gtk_container_set_border_width(GTK_CONTAINER(FindToolbar_FindButton), 5); FindToolbar_checkbutton_FindFiles = gtk_check_button_new_with_mnemonic(_("Filenames")); gtk_widget_show(FindToolbar_checkbutton_FindFiles); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_checkbutton_FindFiles, FALSE, FALSE, 0); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(FindToolbar_checkbutton_FindFiles), TRUE); FindToolbar_checkbutton_TrackInformation = gtk_check_button_new_with_mnemonic(_("Track Information")); gtk_widget_show(FindToolbar_checkbutton_TrackInformation); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_checkbutton_TrackInformation, FALSE, FALSE, 0); tmp_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, tmp_toolbar_icon_size); gtk_widget_show(tmp_image); FindToolbar_CloseButton = (GtkWidget*) gtk_button_new(); gtk_container_add(GTK_CONTAINER(FindToolbar_CloseButton), tmp_image); gtk_widget_show(FindToolbar_CloseButton); gtk_box_pack_start(GTK_BOX(FindToolbar_hbox_FindToolbar), FindToolbar_CloseButton, FALSE, FALSE, 5); gtk_button_set_relief(GTK_BUTTON(FindToolbar_CloseButton), GTK_RELIEF_NONE); // Main Window. // Hpane for showing both folders and main window. hpanel = gtk_hpaned_new(); gtk_widget_show(hpanel); gtk_box_pack_start(GTK_BOX(vbox1), hpanel, TRUE, TRUE, 0); gtk_paned_set_position(GTK_PANED(hpanel), 150); // Folder Window scrolledwindowFolders = gtk_scrolled_window_new(NULL, NULL); gtk_widget_hide(scrolledwindowFolders); gtk_paned_pack1(GTK_PANED(hpanel), scrolledwindowFolders, TRUE, TRUE); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindowFolders), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); treeviewFolders = gtk_tree_view_new(); gtk_widget_show(treeviewFolders); gtk_container_add(GTK_CONTAINER(scrolledwindowFolders), treeviewFolders); gtk_container_set_border_width(GTK_CONTAINER(treeviewFolders), 5); folderSelection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeviewFolders)); gtk_tree_selection_set_mode(folderSelection, GTK_SELECTION_SINGLE); folderList = gtk_tree_store_new(NUM_FOL_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, GDK_TYPE_PIXBUF); folderColumn = setupFolderList(treeviewFolders); folderListModel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(folderList)); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(folderListModel), COL_FOL_NAME_HIDDEN, GTK_SORT_ASCENDING); gtk_tree_view_set_model(GTK_TREE_VIEW(treeviewFolders), GTK_TREE_MODEL(folderListModel)); gtk_tree_view_expand_all(GTK_TREE_VIEW(treeviewFolders)); g_object_unref(folderList); // Main file window scrolledwindowMain = gtk_scrolled_window_new(NULL, NULL); gtk_widget_show(scrolledwindowMain); gtk_paned_pack2(GTK_PANED(hpanel), scrolledwindowMain, TRUE, TRUE); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindowMain), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); treeviewFiles = gtk_tree_view_new(); gtk_widget_show(treeviewFiles); gtk_container_add(GTK_CONTAINER(scrolledwindowMain), treeviewFiles); gtk_container_set_border_width(GTK_CONTAINER(treeviewFiles), 5); fileSelection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeviewFiles)); gtk_tree_selection_set_mode(fileSelection, GTK_SELECTION_MULTIPLE); fileList = gtk_list_store_new(NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, GDK_TYPE_PIXBUF, G_TYPE_STRING); setupFileList(treeviewFiles); fileListModel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(fileList)); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(fileListModel), COL_FILENAME_HIDDEN, GTK_SORT_ASCENDING); gtk_tree_view_set_model(GTK_TREE_VIEW(treeviewFiles), GTK_TREE_MODEL(fileListModel)); g_object_unref(fileList); windowStatusBar = gtk_statusbar_new(); gtk_widget_show(windowStatusBar); gtk_box_pack_start(GTK_BOX(vbox1), windowStatusBar, FALSE, FALSE, 0); // Build our right-click context menu; contextMenu = create_windowMainContextMenu(); contextMenuColumn = create_windowMainColumnContextMenu(); contextMenuFolder = create_windowFolderContextMenu(); // DnD functions g_signal_connect((gpointer) scrolledwindowMain, "drag-data-received", G_CALLBACK(gmtp_drag_data_received), NULL); g_signal_connect((gpointer) treeviewFolders, "drag-data-received", G_CALLBACK(gmtpfolders_drag_data_received), NULL); g_signal_connect((gpointer) treeviewFolders, "drag-motion", G_CALLBACK(gmtpfolders_drag_motion_received), NULL); // End Dnd functions g_signal_connect((gpointer) windowMain, "destroy", G_CALLBACK(on_quit1_activate), NULL); g_signal_connect((gpointer) properties1, "activate", G_CALLBACK(on_deviceProperties_activate), NULL); g_signal_connect((gpointer) toolbuttonProperties, "clicked", G_CALLBACK(on_deviceProperties_activate), NULL); g_signal_connect((gpointer) quit1, "activate", G_CALLBACK(on_quit1_activate), NULL); g_signal_connect((gpointer) preferences1, "activate", G_CALLBACK(on_preferences1_activate), NULL); g_signal_connect((gpointer) editFind, "activate", G_CALLBACK(on_editFind_activate), NULL); g_signal_connect((gpointer) editSelectAll, "activate", G_CALLBACK(on_editSelectAll_activate), NULL); g_signal_connect((gpointer) editDeviceName, "activate", G_CALLBACK(on_editDeviceName_activate), NULL); g_signal_connect((gpointer) editFormatDevice, "activate", G_CALLBACK(on_editFormatDevice_activate), NULL); g_signal_connect((gpointer) editAddAlbumArt, "activate", G_CALLBACK(on_editAddAlbumArt_activate), NULL); g_signal_connect((gpointer) editPlaylist, "activate", G_CALLBACK(on_editPlaylist_activate), NULL); g_signal_connect((gpointer) fileAdd, "activate", G_CALLBACK(on_filesAdd_activate), NULL); g_signal_connect((gpointer) fileDownload, "activate", G_CALLBACK(on_filesDownload_activate), NULL); g_signal_connect((gpointer) fileRemove, "activate", G_CALLBACK(on_filesDelete_activate), NULL); g_signal_connect((gpointer) fileRename, "activate", G_CALLBACK(on_fileRenameFile_activate), NULL); g_signal_connect((gpointer) fileMove, "activate", G_CALLBACK(on_fileMoveFile_activate), NULL); g_signal_connect((gpointer) fileConnect, "activate", G_CALLBACK(on_deviceConnect_activate), NULL); g_signal_connect((gpointer) fileNewFolder, "activate", G_CALLBACK(on_fileNewFolder_activate), NULL); g_signal_connect((gpointer) fileRemoveFolder, "activate", G_CALLBACK(on_fileRemoveFolder_activate), NULL); g_signal_connect((gpointer) fileRescan, "activate", G_CALLBACK(on_deviceRescan_activate), NULL); g_signal_connect((gpointer) about1, "activate", G_CALLBACK(on_about1_activate), NULL); g_signal_connect((gpointer) toolbuttonQuit, "clicked", G_CALLBACK(on_quit1_activate), NULL); g_signal_connect((gpointer) toolbuttonRescan, "clicked", G_CALLBACK(on_deviceRescan_activate), NULL); g_signal_connect((gpointer) toolbuttonAddFile, "clicked", G_CALLBACK(on_filesAdd_activate), NULL); g_signal_connect((gpointer) toolbuttonRemoveFile, "clicked", G_CALLBACK(on_filesDelete_activate), NULL); g_signal_connect((gpointer) toolbuttonRetrieve, "clicked", G_CALLBACK(on_filesDownload_activate), NULL); g_signal_connect((gpointer) toolbuttonAlbumArt, "clicked", G_CALLBACK(on_editAddAlbumArt_activate), NULL); g_signal_connect((gpointer) toolbuttonPlaylist, "clicked", G_CALLBACK(on_editPlaylist_activate), NULL); g_signal_connect((gpointer) toolbuttonConnect, "clicked", G_CALLBACK(on_deviceConnect_activate), NULL); g_signal_connect((gpointer) toolbuttonPreferences, "clicked", G_CALLBACK(on_preferences1_activate), NULL); g_signal_connect((gpointer) treeviewFiles, "row-activated", G_CALLBACK(fileListRowActivated), NULL); g_signal_connect((gpointer) treeviewFolders, "row-activated", G_CALLBACK(folderListRowActivated), NULL); g_signal_connect_swapped(treeviewFiles, "button_press_event", G_CALLBACK(on_windowMainContextMenu_activate), contextMenu); g_signal_connect_swapped(treeviewFolders, "button_press_event", G_CALLBACK(on_windowMainContextMenu_activate), contextMenuFolder); g_signal_connect((gpointer) menu_view_filesize, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_filetype, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_track_number, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_title, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_artist, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_album, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_year, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_genre, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_duration, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) menu_view_folders, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) FindToolbar_CloseButton, "clicked", G_CALLBACK(on_editFindClose_activate), NULL); g_signal_connect((gpointer) FindToolbar_FindButton, "clicked", G_CALLBACK(on_editFindSearch_activate), NULL); g_signal_connect((gpointer) FindToolbar_entry_FindText, "activate", G_CALLBACK(on_editFindSearch_activate), NULL); folderSelectHandler = g_signal_connect_after((gpointer) folderSelection, "changed", G_CALLBACK(on_treeviewFolders_rowactivated), NULL); fileSelectHandler = g_signal_connect_after((gpointer) fileSelection, "changed", G_CALLBACK(on_treeviewFolders_rowactivated), NULL); gtk_window_add_accel_group(GTK_WINDOW(windowMain), accel_group); gtk_menu_set_accel_group(GTK_MENU(menuitem1_menu), accel_group); gtk_widget_add_accelerator(fileRemove, "activate", accel_group, GDK_Delete, 0, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileRename, "activate", accel_group, GDK_F2, 0, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileConnect, "activate", accel_group, GDK_F3, 0, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileRescan, "activate", accel_group, GDK_F5, 0, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileNewFolder, "activate", accel_group, GDK_N, GDK_CONTROL_MASK + GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileAdd, "activate", accel_group, GDK_O, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(fileDownload, "activate", accel_group, GDK_D, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(editPlaylist, "activate", accel_group, GDK_P, GDK_CONTROL_MASK + GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(editAddAlbumArt, "activate", accel_group, GDK_A, GDK_CONTROL_MASK + GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE); gtk_widget_add_accelerator(editSelectAll, "activate", accel_group, GDK_A, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); return windowMain; } // ************************************************************************************************ /** * Set the text on the status bar within the main window * @param text */ void statusBarSet(gchar *text) { statusBarClear(); guint c_id1 = gtk_statusbar_get_context_id(GTK_STATUSBAR(windowStatusBar), ""); gtk_statusbar_push(GTK_STATUSBAR(windowStatusBar), c_id1, text); } // ************************************************************************************************ /** * Clear the text within the status bar window. */ void statusBarClear() { guint c_id1 = gtk_statusbar_get_context_id(GTK_STATUSBAR(windowStatusBar), ""); gtk_statusbar_pop(GTK_STATUSBAR(windowStatusBar), c_id1); } // ************************************************************************************************ /** * Toggle the active state of the buttons on the toolbar and various menus. * @param state */ void SetToolbarButtonState(gboolean state) { gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonAddFile), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonRemoveFile), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonRetrieve), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonRescan), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonAlbumArt), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonPlaylist), state); gtk_widget_set_sensitive(GTK_WIDGET(toolbuttonProperties), state); gtk_widget_set_sensitive(GTK_WIDGET(properties1), state); gtk_widget_set_sensitive(GTK_WIDGET(fileAdd), state); gtk_widget_set_sensitive(GTK_WIDGET(fileDownload), state); gtk_widget_set_sensitive(GTK_WIDGET(fileRemove), state); gtk_widget_set_sensitive(GTK_WIDGET(fileRename), state); gtk_widget_set_sensitive(GTK_WIDGET(fileMove), state); gtk_widget_set_sensitive(GTK_WIDGET(fileNewFolder), state); gtk_widget_set_sensitive(GTK_WIDGET(fileRemoveFolder), state); gtk_widget_set_sensitive(GTK_WIDGET(fileRescan), state); gtk_widget_set_sensitive(GTK_WIDGET(editDeviceName), state); gtk_widget_set_sensitive(GTK_WIDGET(editFormatDevice), state); gtk_widget_set_sensitive(GTK_WIDGET(editAddAlbumArt), state); gtk_widget_set_sensitive(GTK_WIDGET(editFind), state); gtk_widget_set_sensitive(GTK_WIDGET(editSelectAll), state); gtk_widget_set_sensitive(GTK_WIDGET(editPlaylist), state); gtk_widget_set_sensitive(GTK_WIDGET(treeviewFiles), state); // Only set this if we are using the normal connection method. if (!Preferences.use_alt_access_method) { gtk_widget_set_sensitive(GTK_WIDGET(treeviewFolders), state); } } // ************************************************************************************************ /** * Construct the main file view within the main application window. * @param treeviewFiles */ void setupFileList(GtkTreeView *treeviewFiles) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkWidget *header; GtkWidget *parent; // Filename column //renderer = gtk_cell_renderer_text_new(); //column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer, // "text", COL_FILENAME, // NULL); column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, _("Filename")); renderer = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start(column, renderer, FALSE); gtk_tree_view_column_set_attributes(column, renderer, "pixbuf", COL_ICON, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", COL_FILENAME_ACTUAL, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_sort_column_id(column, COL_FILENAME_HIDDEN); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); header = gtk_label_new(_("Filename")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Filename column for sorting. renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Filename Hidden", renderer, "text", COL_FILENAME_HIDDEN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // File name actual - used for renaming operations. renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Filename Actual", renderer, "text", COL_FILENAME_ACTUAL, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // File Size column renderer = gtk_cell_renderer_text_new(); column_Size = gtk_tree_view_column_new_with_attributes(_("Size"), renderer, "text", COL_FILESIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Size); gtk_tree_view_column_set_sort_column_id(column_Size, COL_FILESIZE_HID); gtk_tree_view_column_set_resizable(column_Size, TRUE); gtk_tree_view_column_set_spacing(column_Size, 5); gtk_tree_view_column_set_visible(column_Size, Preferences.view_size); header = gtk_label_new(_("Size")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Size, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Size), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Folder/FileID column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Object ID", renderer, "text", COL_FILEID, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // isFolder column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("isFolder", renderer, "text", COL_ISFOLDER, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // File size column - hidden used for sorting the visible file size column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("FileSize Hidden", renderer, "text", COL_FILESIZE_HID, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // File Type column renderer = gtk_cell_renderer_text_new(); column_Type = gtk_tree_view_column_new_with_attributes(_("File Type"), renderer, "text", COL_TYPE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Type); gtk_tree_view_column_set_sort_column_id(column_Type, COL_TYPE); gtk_tree_view_column_set_resizable(column_Type, TRUE); gtk_tree_view_column_set_spacing(column_Type, 5); gtk_tree_view_column_set_visible(column_Type, Preferences.view_type); header = gtk_label_new(_("File Type")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Type, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Type), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Track Number column renderer = gtk_cell_renderer_text_new(); column_Track_Number = gtk_tree_view_column_new_with_attributes(_("Track"), renderer, "text", COL_TRACK_NUMBER, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Track_Number); gtk_tree_view_column_set_sort_column_id(column_Track_Number, COL_TRACK_NUMBER_HIDDEN); gtk_tree_view_column_set_resizable(column_Track_Number, TRUE); gtk_tree_view_column_set_spacing(column_Track_Number, 5); gtk_tree_view_column_set_visible(column_Track_Number, Preferences.view_track_number); header = gtk_label_new(_("Track")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Track_Number, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Track_Number), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Track Num Hidden", renderer, "text", COL_TRACK_NUMBER_HIDDEN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // Track Title column renderer = gtk_cell_renderer_text_new(); column_Title = gtk_tree_view_column_new_with_attributes(_("Track Name"), renderer, "text", COL_TITLE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Title); gtk_tree_view_column_set_sort_column_id(column_Title, COL_TITLE); gtk_tree_view_column_set_resizable(column_Title, TRUE); gtk_tree_view_column_set_spacing(column_Title, 5); gtk_tree_view_column_set_visible(column_Title, Preferences.view_track_number); header = gtk_label_new(_("Track Name")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Title, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Title), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Artist column renderer = gtk_cell_renderer_text_new(); column_Artist = gtk_tree_view_column_new_with_attributes(_("Artist"), renderer, "text", COL_FL_ARTIST, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Artist); gtk_tree_view_column_set_sort_column_id(column_Artist, COL_FL_ARTIST); gtk_tree_view_column_set_resizable(column_Artist, TRUE); gtk_tree_view_column_set_spacing(column_Artist, 5); gtk_tree_view_column_set_visible(column_Artist, Preferences.view_artist); header = gtk_label_new(_("Artist")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Artist, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Artist), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Album column renderer = gtk_cell_renderer_text_new(); column_Album = gtk_tree_view_column_new_with_attributes(_("Album"), renderer, "text", COL_FL_ALBUM, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Album); gtk_tree_view_column_set_sort_column_id(column_Album, COL_FL_ALBUM); gtk_tree_view_column_set_resizable(column_Album, TRUE); gtk_tree_view_column_set_spacing(column_Album, 5); gtk_tree_view_column_set_visible(column_Album, Preferences.view_album); header = gtk_label_new(_("Album")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Album, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Album), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Year column renderer = gtk_cell_renderer_text_new(); column_Year = gtk_tree_view_column_new_with_attributes(_("Year"), renderer, "text", COL_YEAR, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Year); gtk_tree_view_column_set_sort_column_id(column_Year, COL_YEAR); gtk_tree_view_column_set_resizable(column_Year, TRUE); gtk_tree_view_column_set_spacing(column_Year, 5); gtk_tree_view_column_set_visible(column_Year, Preferences.view_year); header = gtk_label_new(_("Year")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Year, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Year), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Genre column renderer = gtk_cell_renderer_text_new(); column_Genre = gtk_tree_view_column_new_with_attributes(_("Genre"), renderer, "text", COL_GENRE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Genre); gtk_tree_view_column_set_sort_column_id(column_Genre, COL_GENRE); gtk_tree_view_column_set_resizable(column_Genre, TRUE); gtk_tree_view_column_set_spacing(column_Genre, 5); gtk_tree_view_column_set_visible(column_Genre, Preferences.view_genre); header = gtk_label_new(_("Genre")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Genre, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Genre), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Duration Visible column renderer = gtk_cell_renderer_text_new(); column_Duration = gtk_tree_view_column_new_with_attributes(_("Duration"), renderer, "text", COL_DURATION, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Duration); gtk_tree_view_column_set_sort_column_id(column_Duration, COL_DURATION_HIDDEN); gtk_tree_view_column_set_resizable(column_Duration, TRUE); gtk_tree_view_column_set_spacing(column_Duration, 5); gtk_tree_view_column_set_visible(column_Duration, Preferences.view_duration); header = gtk_label_new(_("Duration")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Duration, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Duration), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); // Duration Hidden column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Duration Hidden", renderer, "text", COL_DURATION_HIDDEN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // Location column - only used in search mode. renderer = gtk_cell_renderer_text_new(); column_Location = gtk_tree_view_column_new_with_attributes(_("Location"), renderer, "text", COL_LOCATION, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column_Location); gtk_tree_view_column_set_sort_column_id(column_Location, COL_LOCATION); gtk_tree_view_column_set_resizable(column_Location, TRUE); gtk_tree_view_column_set_spacing(column_Location, 5); gtk_tree_view_column_set_visible(column_Location, FALSE); header = gtk_label_new(_("Location")); gtk_widget_show(header); gtk_tree_view_column_set_widget(column_Location, header); parent = gtk_widget_get_ancestor(gtk_tree_view_column_get_widget(column_Location), GTK_TYPE_BUTTON); g_signal_connect_swapped(parent, "button_press_event", G_CALLBACK(on_windowViewContextMenu_activate), contextMenuColumn); } // ************************************************************************************************ GtkTreeViewColumn *setupFolderList(GtkTreeView *treeviewFolders) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreeViewColumn *folderColumnInt; folderColumnInt = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(folderColumnInt, _("Folder")); renderer = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start(folderColumnInt, renderer, FALSE); gtk_tree_view_column_set_attributes(folderColumnInt, renderer, "pixbuf", COL_FOL_ICON, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "weight", PANGO_WEIGHT_BOLD, NULL); gtk_tree_view_column_pack_start(folderColumnInt, renderer, TRUE); gtk_tree_view_column_set_attributes(folderColumnInt, renderer, "text", COL_FOL_NAME_HIDDEN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFolders), folderColumnInt); gtk_tree_view_column_set_sort_column_id(folderColumnInt, COL_FOL_NAME_HIDDEN); gtk_tree_view_column_set_resizable(folderColumnInt, TRUE); gtk_tree_view_column_set_spacing(folderColumnInt, 5); // Folder column for sorting. renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Folder name Hidden", renderer, "text", COL_FOL_NAME_HIDDEN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFolders), column); gtk_tree_view_column_set_visible(column, FALSE); return folderColumnInt; } // ************************************************************************************************ /** * Clear all entries within the main file window. * @return */ gboolean fileListClear() { gtk_list_store_clear(GTK_LIST_STORE(fileList)); return TRUE; } // ************************************************************************************************ /** * Clear all entries within the main folder window. * @return */ gboolean folderListClear() { gtk_tree_store_clear(GTK_TREE_STORE(folderList)); return TRUE; } // ************************************************************************************************ /** * Display the Add Files dialog box and add the files as selected. * @return List of files to add to the device. */ GSList* getFileGetList2Add() { GSList* files = NULL; GtkWidget *FileDialog; gchar *savepath = NULL; savepath = g_malloc0(8192); FileDialog = gtk_file_chooser_dialog_new(_("Select Files to Add"), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(FileDialog), TRUE); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemUploadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { savepath = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(FileDialog)); // Save our upload path. Preferences.fileSystemUploadPath = g_string_assign(Preferences.fileSystemUploadPath, savepath); files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(FileDialog)); } gtk_widget_hide(FileDialog); gtk_widget_destroy(FileDialog); g_free(savepath); return files; } // ************************************************************************************************ /** * Add the applicable files to the file view in the main window. * @return */ gboolean fileListAdd() { GtkTreeIter rowIter; //gchar *filename = NULL; gchar *filename_hid = NULL; gchar *filesize = NULL; gchar *filetype = NULL; gchar *trackduration = NULL; gchar *tracknumber = NULL; gchar *fileext = NULL; LIBMTP_folder_t *tmpfolder; LIBMTP_file_t *tmpfile; guint parentID; GdkPixbuf *image = NULL; // Confirm our currentFolder exists otherwise goto the root folder. if (!Preferences.use_alt_access_method) { tmpfolder = getCurrentFolderPtr(deviceFolders, currentFolderID); if (tmpfolder == NULL) currentFolderID = 0; } else { // deviceFolders is NULL or has garbage. So assume that we actually do exist! filesUpateFileList(); } // This ensure that if the current folder or a parent of is deleted in the find mode, // the current folder is reset to something sane. // Start our file listing. if (inFindMode == FALSE) { // Since we are in normal mode, hide the location Column. gtk_tree_view_column_set_visible(column_Location, FALSE); // We start with the folder list... if (currentFolderID != 0) { // If we are not folderID = 0; then... image = gdk_pixbuf_new_from_file(file_folder_png, NULL); // Scan the folder list for the current folderID, and set the parent ID, if (!Preferences.use_alt_access_method) { tmpfolder = deviceFolders; parentID = getParentFolderID(tmpfolder, currentFolderID); } else { parentID = *((guint*) g_queue_peek_tail(stackFolderIDs)); } // Now add in the row information. gtk_list_store_append(GTK_LIST_STORE(fileList), &rowIter); gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, "< .. >", COL_FILENAME_HIDDEN, " < .. >", COL_FILENAME_ACTUAL, "..", COL_FILESIZE, "", COL_FILEID, parentID, COL_ISFOLDER, TRUE, COL_FILESIZE_HID, (guint64) 0, COL_ICON, image, -1); // Indicate we are done with this image. g_object_unref(image); } // Only use device folders if using normal display mode. if (!Preferences.use_alt_access_method) { // What we scan for is the folder's details where 'parent_id' == currentFolderID and display those. tmpfolder = getParentFolderPtr(deviceFolders, currentFolderID); while (tmpfolder != NULL) { if ((tmpfolder->parent_id == currentFolderID) && (tmpfolder->storage_id == DeviceMgr.devicestorage->id)) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); gtk_list_store_append(GTK_LIST_STORE(fileList), &rowIter); //filename = g_strdup_printf("< %s >", tmpfolder->name); filename_hid = g_strdup_printf(" < %s >", tmpfolder->name); gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, filename, COL_FILENAME_HIDDEN, filename_hid, COL_FILENAME_ACTUAL, tmpfolder->name, COL_FILESIZE, "", COL_FILEID, tmpfolder->folder_id, COL_ISFOLDER, TRUE, COL_FILESIZE_HID, (guint64) 0, COL_ICON, image, -1); //g_free(filename); g_free(filename_hid); // Indicate we are done with this image. g_object_unref(image); } tmpfolder = tmpfolder->sibling; } } // We don't destroy the structure, only on a rescan operation. // We scan for files in the file details we 'parent_id' == currentFolderID and display those. tmpfile = deviceFiles; while (tmpfile != NULL) { if ((tmpfile->parent_id == currentFolderID) && (tmpfile->storage_id == DeviceMgr.devicestorage->id)) { gtk_list_store_append(GTK_LIST_STORE(fileList), &rowIter); if (tmpfile->filesize < 1000) { filesize = g_strdup_printf("%llu B", tmpfile->filesize); } else { if (tmpfile->filesize < (1000000)) { filesize = g_strdup_printf("%.3f KB", (tmpfile->filesize / 1024.00)); } else { filesize = g_strdup_printf("%.3f MB", (tmpfile->filesize / (1024.00 * 1024.00))); } } fileext = rindex(tmpfile->filename, '.'); // This accounts for the case with a filename without any "." (period). if (!fileext) { filetype = g_strconcat(g_ascii_strup(tmpfile->filename, -1), " File", NULL); } else { filetype = g_strconcat(g_ascii_strup(++fileext, -1), " File", NULL); } // Now if it's a track type file, eg OGG, WMA, MP3 or FLAC, get it's metadata. if ((tmpfile->filetype == LIBMTP_FILETYPE_MP3) || (tmpfile->filetype == LIBMTP_FILETYPE_OGG) || (tmpfile->filetype == LIBMTP_FILETYPE_FLAC) || (tmpfile->filetype == LIBMTP_FILETYPE_WMA)) { LIBMTP_track_t *trackinfo; trackinfo = LIBMTP_Get_Trackmetadata(DeviceMgr.device, tmpfile->item_id); if (trackinfo != NULL) { trackduration = g_strdup_printf("%d:%.2d", (int) ((trackinfo->duration / 1000) / 60), (int) ((trackinfo->duration / 1000) % 60)); if (trackinfo->tracknumber != 0) { tracknumber = g_strdup_printf("%d", trackinfo->tracknumber); } else { tracknumber = g_strdup(" "); } // Some basic sanitisation. if (trackinfo->title == NULL) trackinfo->title = g_strdup(""); if (trackinfo->artist == NULL) trackinfo->artist = g_strdup(""); if (trackinfo->album == NULL) trackinfo->album = g_strdup(""); if (trackinfo->date == NULL) { trackinfo->date = g_strdup(""); } else { if (strlen(trackinfo->date) > 4) trackinfo->date[4] = '\0'; // Shorten the string to year only, yes this is nasty... } if (trackinfo->genre == NULL) trackinfo->genre = g_strdup(""); // Icon image = gdk_pixbuf_new_from_file(file_audio_png, NULL); gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, tmpfile->filename, COL_FILENAME_HIDDEN, tmpfile->filename, COL_FILENAME_ACTUAL, tmpfile->filename, COL_FILESIZE, filesize, COL_FILEID, tmpfile->item_id, COL_ISFOLDER, FALSE, COL_FILESIZE_HID, tmpfile->filesize, COL_TYPE, filetype, COL_TRACK_NUMBER, tracknumber, COL_TRACK_NUMBER_HIDDEN, trackinfo->tracknumber, COL_TITLE, trackinfo->title, COL_FL_ARTIST, trackinfo->artist, COL_FL_ALBUM, trackinfo->album, COL_YEAR, trackinfo->date, COL_GENRE, trackinfo->genre, COL_DURATION, trackduration, COL_DURATION_HIDDEN, trackinfo->duration, COL_ICON, image, -1); g_free(trackduration); g_free(tracknumber); trackduration = NULL; tracknumber = NULL; // Indicate we are done with this image. g_object_unref(image); LIBMTP_destroy_track_t(trackinfo); } else { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { // Determine the file type. if (LIBMTP_FILETYPE_IS_AUDIO(tmpfile->filetype)) { image = gdk_pixbuf_new_from_file(file_audio_png, NULL); } else if (LIBMTP_FILETYPE_IS_AUDIOVIDEO(tmpfile->filetype)) { image = gdk_pixbuf_new_from_file(file_video_png, NULL); } else if (LIBMTP_FILETYPE_IS_VIDEO(tmpfile->filetype)) { image = gdk_pixbuf_new_from_file(file_video_png, NULL); } else if (LIBMTP_FILETYPE_IS_IMAGE(tmpfile->filetype)) { image = gdk_pixbuf_new_from_file(file_image_png, NULL); } else if (tmpfile->filetype == LIBMTP_FILETYPE_ALBUM) { image = gdk_pixbuf_new_from_file(file_album_png, NULL); } else if (tmpfile->filetype == LIBMTP_FILETYPE_PLAYLIST) { image = gdk_pixbuf_new_from_file(file_playlist_png, NULL); } else if (tmpfile->filetype == LIBMTP_FILETYPE_TEXT) { image = gdk_pixbuf_new_from_file(file_textfile_png, NULL); } else if (tmpfile->filetype == LIBMTP_FILETYPE_FOLDER) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); } else { image = gdk_pixbuf_new_from_file(file_generic_png, NULL); } // let folders through IFF we are in alt connection mode. if (!Preferences.use_alt_access_method && tmpfile->filetype == LIBMTP_FILETYPE_FOLDER) { goto skipAdd; } // Set folder type. int isFolder = FALSE; if (tmpfile->filetype == LIBMTP_FILETYPE_FOLDER) { isFolder = TRUE; filesize = g_strdup(""); filetype = g_strdup(""); filename_hid = g_strdup_printf(" < %s >", tmpfile->filename); } else { filename_hid = g_strdup(tmpfile->filename); } // Otherwise just show the file information gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, tmpfile->filename, COL_FILENAME_HIDDEN, filename_hid, COL_FILENAME_ACTUAL, tmpfile->filename, COL_FILESIZE, filesize, COL_FILEID, tmpfile->item_id, COL_ISFOLDER, isFolder, COL_FILESIZE_HID, tmpfile->filesize, COL_TYPE, filetype, COL_ICON, image, -1); g_free(filename_hid); skipAdd: // Indicate we are done with this image. g_object_unref(image); } if (filetype != NULL) g_free(filetype); filetype = NULL; if (filesize != NULL) g_free(filesize); filesize = NULL; } tmpfile = tmpfile->next; } // Now update the title bar with our folder name. if (!Preferences.use_alt_access_method) { setWindowTitle(getFullFolderPath(currentFolderID)); } else { // Construct the place string based on the contents of the stackFolderNameQueue. gchar* fullfilename = g_strdup(""); gchar* tmpfilename = NULL; guint stringlength = 0; int items = g_queue_get_length(stackFolderNames); // Add in our names; while(items-- > 0){ tmpfilename = g_strdup_printf("%s/%s", (gchar *)g_queue_peek_nth(stackFolderNames, items), fullfilename); g_free(fullfilename); fullfilename = tmpfilename; } // Add in leading slash if needed if (*fullfilename != '/') { tmpfilename = g_strdup_printf("/%s", fullfilename); g_free(fullfilename); fullfilename = tmpfilename; } // Remove trailing slash if needed. stringlength = strlen(fullfilename); if (stringlength > 1) { fullfilename[stringlength - 1] = '\0'; } setWindowTitle(fullfilename); } } else { // We are in search mode, so use the searchList instead as our source! gint item_count = 0; GSList *tmpsearchList = searchList; FileListStruc *itemdata = NULL; while (tmpsearchList != NULL) { // Add our files/folders. itemdata = tmpsearchList->data; item_count++; // If a folder... if (itemdata->isFolder == TRUE) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); gtk_list_store_append(GTK_LIST_STORE(fileList), &rowIter); //filename = g_strdup_printf("< %s >", tmpfolder->name); filename_hid = g_strdup_printf(" < %s >", itemdata->filename); gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, filename, COL_FILENAME_HIDDEN, filename_hid, COL_FILENAME_ACTUAL, itemdata->filename, COL_FILESIZE, "", COL_FILEID, itemdata->itemid, COL_ISFOLDER, TRUE, COL_FILESIZE_HID, (guint64) 0, COL_ICON, image, COL_LOCATION, itemdata->location, -1); //g_free(filename); g_free(filename_hid); // Indicate we are done with this image. g_object_unref(image); } else { // else if a file... gtk_list_store_append(GTK_LIST_STORE(fileList), &rowIter); if (itemdata->filesize < 1000) { filesize = g_strdup_printf("%llu B", itemdata->filesize); } else { if (itemdata->filesize < (1000000)) { filesize = g_strdup_printf("%.3f KB", (itemdata->filesize / 1024.00)); } else { filesize = g_strdup_printf("%.3f MB", (itemdata->filesize / (1024.00 * 1024.00))); } } fileext = rindex(itemdata->filename, '.'); // This accounts for the case with a filename without any "." (period). if (!fileext) { filetype = g_strconcat(g_ascii_strup(itemdata->filename, -1), " File", NULL); } else { filetype = g_strconcat(g_ascii_strup(++fileext, -1), " File", NULL); } // Now if it's a track type file, eg OGG, WMA, MP3 or FLAC, get it's metadata. if ((itemdata->filetype == LIBMTP_FILETYPE_MP3) || (itemdata->filetype == LIBMTP_FILETYPE_OGG) || (itemdata->filetype == LIBMTP_FILETYPE_FLAC) || (itemdata->filetype == LIBMTP_FILETYPE_WMA)) { LIBMTP_track_t *trackinfo; trackinfo = LIBMTP_Get_Trackmetadata(DeviceMgr.device, itemdata->itemid); if (trackinfo != NULL) { trackduration = g_strdup_printf("%d:%.2d", (int) ((trackinfo->duration / 1000) / 60), (int) ((trackinfo->duration / 1000) % 60)); if (trackinfo->tracknumber != 0) { tracknumber = g_strdup_printf("%d", trackinfo->tracknumber); } else { tracknumber = g_strdup(" "); } // Some basic sanitisation. if (trackinfo->title == NULL) trackinfo->title = g_strdup(""); if (trackinfo->artist == NULL) trackinfo->artist = g_strdup(""); if (trackinfo->album == NULL) trackinfo->album = g_strdup(""); if (trackinfo->date == NULL) trackinfo->date = g_strdup(""); if (trackinfo->genre == NULL) trackinfo->genre = g_strdup(""); // Icon image = gdk_pixbuf_new_from_file(file_audio_png, NULL); gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, tmpfile->filename, COL_FILENAME_HIDDEN, itemdata->filename, COL_FILENAME_ACTUAL, itemdata->filename, COL_FILESIZE, filesize, COL_FILEID, itemdata->itemid, COL_ISFOLDER, FALSE, COL_FILESIZE_HID, itemdata->filesize, COL_TYPE, filetype, COL_TRACK_NUMBER, tracknumber, COL_TRACK_NUMBER_HIDDEN, trackinfo->tracknumber, COL_TITLE, trackinfo->title, COL_FL_ARTIST, trackinfo->artist, COL_FL_ALBUM, trackinfo->album, COL_YEAR, trackinfo->date, COL_GENRE, trackinfo->genre, COL_DURATION, trackduration, COL_DURATION_HIDDEN, trackinfo->duration, COL_ICON, image, COL_LOCATION, itemdata->location, -1); g_free(trackduration); g_free(tracknumber); trackduration = NULL; tracknumber = NULL; // Indicate we are done with this image. g_object_unref(image); LIBMTP_destroy_track_t(trackinfo); } else { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { // Determine the file type. if (LIBMTP_FILETYPE_IS_AUDIO(itemdata->filetype)) { image = gdk_pixbuf_new_from_file(file_audio_png, NULL); } else if (LIBMTP_FILETYPE_IS_AUDIOVIDEO(itemdata->filetype)) { image = gdk_pixbuf_new_from_file(file_video_png, NULL); } else if (LIBMTP_FILETYPE_IS_VIDEO(itemdata->filetype)) { image = gdk_pixbuf_new_from_file(file_video_png, NULL); } else if (LIBMTP_FILETYPE_IS_IMAGE(itemdata->filetype)) { image = gdk_pixbuf_new_from_file(file_image_png, NULL); } else if (itemdata->filetype == LIBMTP_FILETYPE_ALBUM) { image = gdk_pixbuf_new_from_file(file_album_png, NULL); } else if (itemdata->filetype == LIBMTP_FILETYPE_PLAYLIST) { image = gdk_pixbuf_new_from_file(file_playlist_png, NULL); } else if (itemdata->filetype == LIBMTP_FILETYPE_TEXT) { image = gdk_pixbuf_new_from_file(file_textfile_png, NULL); } else if (itemdata->filetype == LIBMTP_FILETYPE_FOLDER) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); } else { image = gdk_pixbuf_new_from_file(file_generic_png, NULL); } // let folders through IFF we are in alt connection mode. if (!Preferences.use_alt_access_method && itemdata->filetype == LIBMTP_FILETYPE_FOLDER) { goto skipAdd2; } // Set folder type. int isFolder = FALSE; if (itemdata->filetype == LIBMTP_FILETYPE_FOLDER) { isFolder = TRUE; filesize = g_strdup(""); filetype = g_strdup(""); filename_hid = g_strdup_printf(" < %s >", itemdata->filename); } else { filename_hid = g_strdup(itemdata->filename); } // Otherwise just show the file information gtk_list_store_set(GTK_LIST_STORE(fileList), &rowIter, //COL_FILENAME, tmpfile->filename, COL_FILENAME_HIDDEN, itemdata->filename, COL_FILENAME_ACTUAL, itemdata->filename, COL_FILESIZE, filesize, COL_FILEID, itemdata->itemid, COL_ISFOLDER, FALSE, COL_FILESIZE_HID, itemdata->filesize, COL_TYPE, filetype, COL_ICON, image, COL_LOCATION, itemdata->location, -1); // Indicate we are done with this image. g_free(filename_hid); skipAdd2: g_object_unref(image); } if (filetype != NULL) g_free(filetype); filetype = NULL; if (filesize != NULL) g_free(filesize); filesize = NULL; } tmpsearchList = tmpsearchList->next; } gchar *tmp_string; if (item_count != 1) { tmp_string = g_strdup_printf(_("Found %d items"), item_count); } else { tmp_string = g_strdup_printf(_("Found %d item"), item_count); } statusBarSet(tmp_string); g_free(tmp_string); } return TRUE; } // ************************************************************************************************ /** * Add folders to the folder list in main window. */ gboolean folderListAdd(LIBMTP_folder_t *folders, GtkTreeIter *parent) { GtkTreeIter rowIter; GdkPixbuf *image = NULL; if (parent == NULL) { // Add in the root node. image = gdk_pixbuf_new_from_file(file_folder_png, NULL); // Now add in the row information. gtk_tree_store_append(GTK_TREE_STORE(folderList), &rowIter, parent); gtk_tree_store_set(GTK_TREE_STORE(folderList), &rowIter, //COL_FOL_NAME, folders->name, COL_FOL_NAME_HIDDEN, "/", COL_FOL_ID, 0, COL_FOL_ICON, image, -1); // Indicate we are done with this image. g_object_unref(image); folderListAdd(folders, &rowIter); return TRUE; } while (folders != NULL) { // Only add in folder if it's in the current storage device. if (folders->storage_id == DeviceMgr.devicestorage->id) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); // Now add in the row information. gtk_tree_store_append(GTK_TREE_STORE(folderList), &rowIter, parent); gtk_tree_store_set(GTK_TREE_STORE(folderList), &rowIter, //COL_FOL_NAME, folders->name, COL_FOL_NAME_HIDDEN, folders->name, COL_FOL_ID, folders->folder_id, COL_FOL_ICON, image, -1); // Indicate we are done with this image. g_object_unref(image); if (folders->child != NULL) { // Call our child. folderListAdd(folders->child, &rowIter); } } folders = folders->sibling; } gtk_tree_view_expand_all(GTK_TREE_VIEW(treeviewFolders)); gtk_tree_view_column_set_sort_order(folderColumn, GTK_SORT_ASCENDING); return TRUE; } // ************************************************************************************************ /** * Download the selected files. * @param List The files to download. * @return */ gboolean fileListDownload(GList *List) { GtkWidget *FileDialog; gchar *savepath = NULL; //savepath = g_malloc0(8192); // Let's confirm our download path. if (Preferences.ask_download_path == TRUE) { FileDialog = gtk_file_chooser_dialog_new(_("Select Path to Download"), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemDownloadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { gtk_widget_hide(FileDialog); savepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); // Save our download path. Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, savepath); // We do the deed. g_list_foreach(List, (GFunc) __fileDownload, NULL); fileoverwriteop = MTP_ASK; } gtk_widget_destroy(FileDialog); } else { // We do the deed. g_list_foreach(List, (GFunc) __fileDownload, NULL); fileoverwriteop = MTP_ASK; } if (savepath != NULL) g_free(savepath); return TRUE; } // ************************************************************************************************ /** * Download the selected folder. * @param List The files to download. * @return */ gboolean folderListDownload(gchar *foldername, uint32_t folderid) { GtkWidget *FileDialog; gchar *savepath = NULL; //savepath = g_malloc0(8192); // Let's confirm our download path. if (Preferences.ask_download_path == TRUE) { FileDialog = gtk_file_chooser_dialog_new(_("Select Path to Download"), GTK_WINDOW(windowMain), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(FileDialog), Preferences.fileSystemDownloadPath->str); if (gtk_dialog_run(GTK_DIALOG(FileDialog)) == GTK_RESPONSE_ACCEPT) { gtk_widget_hide(FileDialog); savepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(FileDialog)); // Save our download path. Preferences.fileSystemDownloadPath = g_string_assign(Preferences.fileSystemDownloadPath, savepath); // We do the deed. folderDownload(foldername, folderid, TRUE); fileoverwriteop = MTP_ASK; } gtk_widget_destroy(FileDialog); } else { // We do the deed. folderDownload(foldername, folderid, TRUE); fileoverwriteop = MTP_ASK; } if (savepath != NULL) g_free(savepath); return TRUE; } // ************************************************************************************************ /** * Perform each file individually. * @param Row */ void __fileDownload(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; gchar *filename = NULL; gchar* fullfilename = NULL; gboolean isFolder; uint32_t objectID; fullfilename = g_malloc0(8192); // First of all, lets set the download path. // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. // Before we download, is it a folder ? gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILENAME_ACTUAL, &filename, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { // Our strings are not equal, so we get to download the file. g_sprintf(fullfilename, "%s/%s", Preferences.fileSystemDownloadPath->str, filename); // Now download the actual file from the MTP device. // Check if file exists? if (access(fullfilename, F_OK) != -1) { // We have that file already? if ((Preferences.prompt_overwrite_file_op == TRUE)) { if (fileoverwriteop == MTP_ASK) { fileoverwriteop = displayFileOverwriteDialog(filename); } switch (fileoverwriteop) { case MTP_ASK: break; case MTP_SKIP: fileoverwriteop = MTP_ASK; break; case MTP_SKIP_ALL: break; case MTP_OVERWRITE: filesDownload(filename, objectID); fileoverwriteop = MTP_ASK; break; case MTP_OVERWRITE_ALL: filesDownload(filename, objectID); break; } } else { filesDownload(filename, objectID); } } else { filesDownload(filename, objectID); } } else { // Overwrite critera performed within this call... if (g_ascii_strcasecmp(filename, "..") != 0) { folderDownload(filename, objectID, TRUE); } else { g_fprintf(stderr, _("I don't know how to download a parent folder reference?\n")); displayError(_("I don't know how to download a parent folder reference?\n")); } } g_free(filename); g_free(fullfilename); } // ************************************************************************************************ /** * Perform each file individually. * @param Row */ void __fileMove(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; gboolean isFolder; LIBMTP_folder_t *currentFolder = NULL; LIBMTP_folder_t *newFolder = NULL; uint32_t objectID; int error; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. // Before we move, is it a folder ? gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { if ((error = setNewParentFolderID(objectID, fileMoveTargetFolder)) != 0) { displayError(_("Unable to move the selected file?\n")); g_fprintf(stderr, "File Move Error: %d\n", error); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { // Make sure we don't want to move the folder into itself? if (objectID == fileMoveTargetFolder) { displayError(_("Unable to move the selected folder into itself?\n")); g_fprintf(stderr, _("Unable to move the selected folder into itself?\n")); return; } // We have the target folder, so let's check to ensure that we will not create a circular // reference by moving a folder underneath it self. currentFolder = getCurrentFolderPtr(deviceFolders, objectID); if (currentFolder == NULL) { // WTF? g_fprintf(stderr, "File Move Error: Can't get current folder pointer\n"); return; } // Use currentFolder as the starting point, and simply attempt to get the ptr to the new // folder based on this point. newFolder = getCurrentFolderPtr(currentFolder->child, fileMoveTargetFolder); if (newFolder == NULL) { // We are alright to proceed. if ((error = setNewParentFolderID(objectID, fileMoveTargetFolder)) != 0) { displayError(_("Unable to move the selected folder?\n")); g_fprintf(stderr, "File Move Error: %d\n", error); LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } else { displayError(_("Unable to move the selected folder underneath itself?\n")); g_fprintf(stderr, _("Unable to move the selected folder underneath itself?\n")); } } } // ************************************************************************************************ /** * Remove selected files from the device. * @param List * @return */ gboolean fileListRemove(GList *List) { // Clear any selection that is present. fileListClearSelection(); // List is a list of Iter's to be removed g_list_foreach(List, (GFunc) __fileRemove, NULL); // We have 2 options, manually scan the file structure for that file and manually fix up... // or do a rescan... // I'll be cheap, and do a full rescan of the device. deviceRescan(); return TRUE; } // ************************************************************************************************ /** * Remove each selected file from the device. * @param Row */ void __fileRemove(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; gchar* filename = NULL; uint32_t objectID; gboolean isFolder; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILENAME_ACTUAL, &filename, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); gtk_list_store_remove(GTK_LIST_STORE(fileList), &iter); // Now get rid of the actual file from the MTP device. filesDelete(filename, objectID); } else { // Our file is really a folder, so perform a folder remove operation. __folderRemove(Row); } g_free(filename); } // ************************************************************************************************ /** * Remove the selected folders from the device. * @param List * @return */ gboolean folderListRemove(GList *List) { // Clear any selection that is present. fileListClearSelection(); // List is a list of Iter's to be removed g_list_foreach(List, (GFunc) __folderRemove, NULL); // We have 2 options, manually scan the file structure for that file and manually fix up... // or do a rescan... // I'll be cheap, and do a full rescan of the device. deviceRescan(); return TRUE; } // ************************************************************************************************ /** * Remove the indivual folder from the device. * @param Row */ void __folderRemove(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; gchar* filename = NULL; uint32_t objectID; gboolean isFolder; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILENAME_ACTUAL, &filename, COL_FILEID, &objectID, -1); if (isFolder == TRUE) { if (g_ascii_strcasecmp(filename, "..") != 0) { gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); gtk_list_store_remove(GTK_LIST_STORE(fileList), &iter); // Now get rid of the actual file from the MTP device. folderDelete(getCurrentFolderPtr(deviceFolders, objectID), 0); } else { g_fprintf(stderr, _("I don't know how to delete a parent folder reference?\n")); displayError(_("I don't know how to delete a parent folder reference?\n")); } } else { // Our folder is really a file, so delete the file instead. __fileRemove(Row); } g_free(filename); } // ************************************************************************************************ /** * Add an individual file to the device. * @param filename */ void __filesAdd(gchar* filename) { gchar* filename_stripped = NULL; filename_stripped = basename(filename); if (Preferences.prompt_overwrite_file_op == FALSE) { filesAdd(filename); return; } // I guess we want to know if we should replace the file, but first if (deviceoverwriteop == MTP_ASK) { if (fileExists(filename_stripped) == TRUE) { deviceoverwriteop = displayFileOverwriteDialog(filename_stripped); switch (deviceoverwriteop) { case MTP_ASK: break; case MTP_SKIP: deviceoverwriteop = MTP_ASK; break; case MTP_SKIP_ALL: break; case MTP_OVERWRITE: filesAdd(filename); deviceoverwriteop = MTP_ASK; break; case MTP_OVERWRITE_ALL: filesAdd(filename); break; } } else { filesAdd(filename); } } else { if (deviceoverwriteop == MTP_OVERWRITE_ALL) filesAdd(filename); } } // ************************************************************************************************ /** * Get a GList of the TREE ROW REFERENCES that are selected. * @return */ GList* fileListGetSelection() { GList *selectedFiles, *ptr; GtkTreeRowReference *ref; GtkTreeModel *sortmodel; // Lets clear up the old list. g_list_free(fileSelection_RowReferences); fileSelection_RowReferences = NULL; if (gtk_tree_selection_count_selected_rows(fileSelection) == 0) { // We have no rows. return NULL; } // So now we must convert each selection to a row reference and store it in a new GList variable // which we will return below. sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFiles)); selectedFiles = gtk_tree_selection_get_selected_rows(fileSelection, &sortmodel); ptr = selectedFiles; while (ptr != NULL) { // Add our row into the GSList so it can be parsed by the respective operations, including // handling changing from the sort model to the real underlying model. ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(fileList), gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sortmodel), (GtkTreePath*) ptr->data)); fileSelection_RowReferences = g_list_prepend(fileSelection_RowReferences, gtk_tree_row_reference_copy(ref)); gtk_tree_row_reference_free(ref); ptr = ptr->next; } g_list_foreach(selectedFiles, (GFunc) gtk_tree_path_free, NULL); g_list_free(selectedFiles); return fileSelection_RowReferences; } // ************************************************************************************************ /** * Finds the Object ID of the selected folder in the folder view. * @return */ int64_t folderListGetSelection(void) { GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; uint32_t objectID; if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // We have no rows. return -1; } sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFolders)); gtk_tree_selection_get_selected(folderSelection, &sortmodel, &iter); gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &childiter, COL_FOL_ID, &objectID, -1); return objectID; } // ************************************************************************************************ /** * Finds the Object Name of the selected folder in the folder view. * @return */ gchar *folderListGetSelectionName(void) { GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; gchar *objectName; if (gtk_tree_selection_count_selected_rows(folderSelection) == 0) { // We have no rows. return NULL; } sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFolders)); gtk_tree_selection_get_selected(folderSelection, &sortmodel, &iter); gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderList), &childiter, COL_FOL_NAME_HIDDEN, &objectName, -1); return objectName; } // ************************************************************************************************ /** * Clear all selected rows from the main file list. * @return */ gboolean fileListClearSelection() { if (fileSelection != NULL) gtk_tree_selection_unselect_all(fileSelection); return TRUE; } // ************************************************************************************************ /** * Select all rows from the main file list. * @return */ gboolean fileListSelectAll(void) { if (fileSelection != NULL) gtk_tree_selection_select_all(fileSelection); return TRUE; } // ************************************************************************************************ /** * Create the Preferences Dialog Box. * @return */ GtkWidget* create_windowPreferences(void) { GtkWidget *windowDialog; GtkWidget *vbox1; GtkWidget *frame1; GtkWidget *alignment1; GtkWidget *frame3; GtkWidget *alignment3; GtkWidget *alignment4; GtkWidget *alignment7; GtkWidget *labelPlaylist; GtkWidget *frame4; GtkWidget *alignment5; GtkWidget *alignment6; GtkWidget *vbox4; GtkWidget *vbox2; GtkWidget *vbox5; GtkWidget *alignment8; GtkWidget *labelDevice; GtkWidget *frame2; GtkWidget *alignment2; GtkWidget *vbox3; GtkWidget *table1; GtkWidget *labelDownloadPath; GtkWidget *labelUploadPath; GtkWidget *buttonDownloadPath; GtkWidget *buttonUploadPath; GtkWidget *labelFilePath; GtkWidget *hbox1; GtkWidget *buttonClose; windowDialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gchar * winTitle; winTitle = g_strconcat(PACKAGE_TITLE, _(" Preferences"), NULL); gtk_window_set_title(GTK_WINDOW(windowDialog), winTitle); gtk_window_set_modal(GTK_WINDOW(windowDialog), TRUE); gtk_window_set_transient_for(GTK_WINDOW(windowDialog), GTK_WINDOW(windowMain)); gtk_window_set_position(GTK_WINDOW(windowDialog), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_resizable(GTK_WINDOW(windowDialog), FALSE); gtk_window_set_type_hint(GTK_WINDOW(windowDialog), GDK_WINDOW_TYPE_HINT_DIALOG); g_free(winTitle); vbox1 = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(windowDialog), vbox1); gtk_container_set_border_width(GTK_CONTAINER(vbox1), 5); frame1 = gtk_frame_new(NULL); gtk_widget_show(frame1); gtk_box_pack_start(GTK_BOX(vbox1), frame1, TRUE, TRUE, 0); gtk_frame_set_shadow_type(GTK_FRAME(frame1), GTK_SHADOW_NONE); vbox5 = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox5); gtk_container_add(GTK_CONTAINER(frame1), vbox5); alignment1 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment1); gtk_container_add(GTK_CONTAINER(vbox5), alignment1); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 0, 0, 12, 0); alignment8 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment8); gtk_container_add(GTK_CONTAINER(vbox5), alignment8); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment8), 0, 0, 12, 0); checkbuttonDeviceConnect = gtk_check_button_new_with_mnemonic(_("Attempt to connect to Device on startup")); gtk_widget_show(checkbuttonDeviceConnect); gtk_container_add(GTK_CONTAINER(alignment1), checkbuttonDeviceConnect); checkbuttonAltAccessMethod = gtk_check_button_new_with_mnemonic(_("Utilize alternate access method")); gtk_widget_show(checkbuttonAltAccessMethod); gtk_container_add(GTK_CONTAINER(alignment8), checkbuttonAltAccessMethod); labelDevice = gtk_label_new(_("Device")); gtk_widget_show(labelDevice); gtk_frame_set_label_widget(GTK_FRAME(frame1), labelDevice); gtk_label_set_use_markup(GTK_LABEL(labelDevice), TRUE); frame3 = gtk_frame_new(NULL); gtk_widget_show(frame3); gtk_box_pack_start(GTK_BOX(vbox1), frame3, TRUE, TRUE, 0); gtk_frame_set_shadow_type(GTK_FRAME(frame3), GTK_SHADOW_NONE); vbox2 = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox2); gtk_container_add(GTK_CONTAINER(frame3), vbox2); alignment3 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment3); gtk_container_add(GTK_CONTAINER(vbox2), alignment3); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment3), 0, 0, 12, 0); alignment4 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment4); gtk_container_add(GTK_CONTAINER(vbox2), alignment4); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment4), 0, 0, 12, 0); alignment7 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment7); gtk_container_add(GTK_CONTAINER(vbox2), alignment7); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment7), 0, 0, 12, 0); checkbuttonConfirmFileOp = gtk_check_button_new_with_mnemonic(_("Confirm File/Folder Delete")); gtk_widget_show(checkbuttonConfirmFileOp); gtk_container_add(GTK_CONTAINER(alignment3), checkbuttonConfirmFileOp); checkbuttonConfirmOverWriteFileOp = gtk_check_button_new_with_mnemonic(_("Prompt if to Overwrite file if already exists")); gtk_widget_show(checkbuttonConfirmOverWriteFileOp); gtk_container_add(GTK_CONTAINER(alignment4), checkbuttonConfirmOverWriteFileOp); checkbuttonSuppressAlbumErrors = gtk_check_button_new_with_mnemonic(_("Suppress Album Errors")); gtk_widget_show(checkbuttonSuppressAlbumErrors); gtk_container_add(GTK_CONTAINER(alignment7), checkbuttonSuppressAlbumErrors); // Playlist Frame. frame4 = gtk_frame_new(NULL); gtk_widget_show(frame4); gtk_box_pack_start(GTK_BOX(vbox1), frame4, TRUE, TRUE, 0); gtk_frame_set_shadow_type(GTK_FRAME(frame4), GTK_SHADOW_NONE); labelPlaylist = gtk_label_new(_("Playlist")); gtk_widget_show(labelPlaylist); gtk_frame_set_label_widget(GTK_FRAME(frame4), labelPlaylist); gtk_label_set_use_markup(GTK_LABEL(labelPlaylist), TRUE); vbox4 = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox4); gtk_container_add(GTK_CONTAINER(frame4), vbox4); alignment5 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment5); gtk_container_add(GTK_CONTAINER(vbox4), alignment5); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment5), 0, 0, 12, 0); alignment6 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment6); gtk_container_add(GTK_CONTAINER(vbox4), alignment6); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment6), 0, 0, 12, 0); checkbuttonAutoAddTrackPlaylist = gtk_check_button_new_with_mnemonic(_("Prompt to add New Music track to existing playlist")); gtk_widget_show(checkbuttonAutoAddTrackPlaylist); gtk_container_add(GTK_CONTAINER(alignment5), checkbuttonAutoAddTrackPlaylist); checkbuttonIgnorePathInPlaylist = gtk_check_button_new_with_mnemonic(_("Ignore path information when importing playlist")); gtk_widget_show(checkbuttonIgnorePathInPlaylist); gtk_container_add(GTK_CONTAINER(alignment6), checkbuttonIgnorePathInPlaylist); labelDevice = gtk_label_new(_("File Operations")); gtk_widget_show(labelDevice); gtk_frame_set_label_widget(GTK_FRAME(frame3), labelDevice); gtk_label_set_use_markup(GTK_LABEL(labelDevice), TRUE); frame2 = gtk_frame_new(NULL); gtk_widget_show(frame2); gtk_box_pack_start(GTK_BOX(vbox1), frame2, TRUE, TRUE, 0); gtk_frame_set_shadow_type(GTK_FRAME(frame2), GTK_SHADOW_NONE); alignment2 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment2); gtk_container_add(GTK_CONTAINER(frame2), alignment2); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment2), 0, 0, 12, 0); vbox3 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox3); gtk_container_add(GTK_CONTAINER(alignment2), vbox3); checkbuttonDownloadPath = gtk_check_button_new_with_mnemonic(_("Always show Download Path?")); gtk_widget_show(checkbuttonDownloadPath); gtk_box_pack_start(GTK_BOX(vbox3), checkbuttonDownloadPath, FALSE, FALSE, 0); table1 = gtk_table_new(2, 3, FALSE); gtk_widget_show(table1); gtk_box_pack_start(GTK_BOX(vbox3), table1, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(table1), 5); gtk_table_set_row_spacings(GTK_TABLE(table1), 5); gtk_table_set_col_spacings(GTK_TABLE(table1), 5); labelDownloadPath = gtk_label_new(_("Download Path:")); gtk_widget_show(labelDownloadPath); gtk_table_attach(GTK_TABLE(table1), labelDownloadPath, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelDownloadPath), 0, 0.5); labelUploadPath = gtk_label_new(_("Upload Path:")); gtk_widget_show(labelUploadPath); gtk_table_attach(GTK_TABLE(table1), labelUploadPath, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelUploadPath), 0, 0.5); entryDownloadPath = gtk_entry_new(); gtk_widget_show(entryDownloadPath); gtk_editable_set_editable(GTK_EDITABLE(entryDownloadPath), FALSE); gtk_table_attach(GTK_TABLE(table1), entryDownloadPath, 1, 2, 0, 1, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); entryUploadPath = gtk_entry_new(); gtk_widget_show(entryUploadPath); gtk_editable_set_editable(GTK_EDITABLE(entryUploadPath), FALSE); gtk_table_attach(GTK_TABLE(table1), entryUploadPath, 1, 2, 1, 2, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); buttonDownloadPath = gtk_button_new_with_mnemonic(("...")); gtk_widget_show(buttonDownloadPath); gtk_table_attach(GTK_TABLE(table1), buttonDownloadPath, 2, 3, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); buttonUploadPath = gtk_button_new_with_mnemonic(("...")); gtk_widget_show(buttonUploadPath); gtk_table_attach(GTK_TABLE(table1), buttonUploadPath, 2, 3, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); labelFilePath = gtk_label_new(_("Filepaths on PC")); gtk_widget_show(labelFilePath); gtk_frame_set_label_widget(GTK_FRAME(frame2), labelFilePath); gtk_label_set_use_markup(GTK_LABEL(labelFilePath), TRUE); // Now do the ask confirm delete... hbox1 = gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox1); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, FALSE, 0); buttonClose = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_widget_show(buttonClose); gtk_box_pack_end(GTK_BOX(hbox1), buttonClose, FALSE, FALSE, 0); // And now set the fields. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDeviceConnect), Preferences.attemptDeviceConnectOnStart); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonDownloadPath), Preferences.ask_download_path); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmFileOp), Preferences.confirm_file_delete_op); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonConfirmOverWriteFileOp), Preferences.prompt_overwrite_file_op); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAutoAddTrackPlaylist), Preferences.auto_add_track_to_playlist); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonIgnorePathInPlaylist), Preferences.ignore_path_in_playlist_import); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonSuppressAlbumErrors), Preferences.suppress_album_errors); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbuttonAltAccessMethod), Preferences.use_alt_access_method); gtk_entry_set_text(GTK_ENTRY(entryDownloadPath), Preferences.fileSystemDownloadPath->str); gtk_entry_set_text(GTK_ENTRY(entryUploadPath), Preferences.fileSystemUploadPath->str); // Enable the callback functions. g_signal_connect((gpointer) windowDialog, "destroy", G_CALLBACK(on_quitPrefs_activate), NULL); g_signal_connect((gpointer) buttonClose, "clicked", G_CALLBACK(on_quitPrefs_activate), NULL); g_signal_connect((gpointer) checkbuttonDeviceConnect, "toggled", G_CALLBACK(on_PrefsDevice_activate), NULL); g_signal_connect((gpointer) checkbuttonConfirmFileOp, "toggled", G_CALLBACK(on_PrefsConfirmDelete_activate), NULL); g_signal_connect((gpointer) checkbuttonConfirmOverWriteFileOp, "toggled", G_CALLBACK(on_PrefsConfirmOverWriteFileOp_activate), NULL); g_signal_connect((gpointer) checkbuttonAutoAddTrackPlaylist, "toggled", G_CALLBACK(on_PrefsAutoAddTrackPlaylist_activate), NULL); g_signal_connect((gpointer) checkbuttonIgnorePathInPlaylist, "toggled", G_CALLBACK(on_PrefsIgnorePathInPlaylist_activate), NULL); g_signal_connect((gpointer) checkbuttonSuppressAlbumErrors, "toggled", G_CALLBACK(on_PrefsSuppressAlbumError_activate), NULL); g_signal_connect((gpointer) checkbuttonAltAccessMethod, "toggled", G_CALLBACK(on_PrefsUseAltAccessMethod_activate), NULL); g_signal_connect((gpointer) checkbuttonDownloadPath, "toggled", G_CALLBACK(on_PrefsAskDownload_activate), NULL); g_signal_connect((gpointer) buttonDownloadPath, "clicked", G_CALLBACK(on_PrefsDownloadPath_activate), NULL); g_signal_connect((gpointer) buttonUploadPath, "clicked", G_CALLBACK(on_PrefsUploadPath_activate), NULL); // To save the fields, we use callbacks on the widgets via gconf. return windowDialog; } // ************************************************************************************************ /** * Create the Properties Dialog Box. * @return */ GtkWidget* create_windowProperties() { GtkWidget *windowDialog; GtkWidget *windowNotebook; GtkWidget *vbox1; GtkWidget *vbox2; GtkWidget *alignment2; GtkWidget *table2; GtkWidget *label15; GtkWidget *labelName; GtkWidget *labelModel; GtkWidget *labelSerial; GtkWidget *labelBattery; GtkWidget *labelManufacturer; GtkWidget *labelDeviceVer; GtkWidget *label26; GtkWidget *label29; GtkWidget *label28; GtkWidget *label27; GtkWidget *label17; GtkWidget *label25; GtkWidget *label18; GtkWidget *label19; GtkWidget *label16; GtkWidget *labelStorage; GtkWidget *labelSupportedFormat; GtkWidget *labelSecTime; GtkWidget *labelSyncPartner; GtkWidget *label2; GtkWidget *alignment1; GtkWidget *table1; GtkWidget *label3; GtkWidget *label4; GtkWidget *label5; GtkWidget *label6; GtkWidget *label7; GtkWidget *label8; GtkWidget *labelDeviceVendor; GtkWidget *labelDeviceProduct; GtkWidget *labelVenID; GtkWidget *labelProdID; GtkWidget *labelBusLoc; GtkWidget *labelDevNum; GtkWidget *label1; GtkWidget *hbox2; GtkWidget *buttonClose; GtkWidget *label50; GString *tmp_string2; gchar *tmp_string = NULL; windowDialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gchar * winTitle; winTitle = g_strconcat(DeviceMgr.devicename->str, _(" Properties"), NULL); gtk_window_set_title(GTK_WINDOW(windowDialog), winTitle); gtk_window_set_modal(GTK_WINDOW(windowDialog), TRUE); gtk_window_set_transient_for(GTK_WINDOW(windowDialog), GTK_WINDOW(windowMain)); gtk_window_set_position(GTK_WINDOW(windowDialog), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_resizable(GTK_WINDOW(windowDialog), FALSE); gtk_window_set_type_hint(GTK_WINDOW(windowDialog), GDK_WINDOW_TYPE_HINT_DIALOG); g_free(winTitle); // Main Window vbox1 = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(vbox1), 5); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(windowDialog), vbox1); // Device Properties Pane label2 = gtk_label_new(""); gtk_label_set_markup(GTK_LABEL(label2), _("MTP Device Properties")); gtk_widget_show(label2); alignment2 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment2); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment2), 0, 0, 12, 0); // Raw Device Information Pane label1 = gtk_label_new(""); gtk_label_set_markup(GTK_LABEL(label1), _("Raw Device Information")); gtk_widget_show(label1); alignment1 = gtk_alignment_new(0.5, 0.5, 1, 1); gtk_widget_show(alignment1); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 0, 0, 12, 0); // Build the Notebook. windowNotebook = gtk_notebook_new(); gtk_widget_show(windowNotebook); gtk_notebook_append_page(GTK_NOTEBOOK(windowNotebook), alignment2, label2); gtk_notebook_append_page(GTK_NOTEBOOK(windowNotebook), alignment1, label1); gtk_container_add(GTK_CONTAINER(vbox1), windowNotebook); // Start the Device Properties Pane. table2 = gtk_table_new(10, 2, FALSE); gtk_widget_show(table2); gtk_container_add(GTK_CONTAINER(alignment2), table2); gtk_container_set_border_width(GTK_CONTAINER(table2), 5); gtk_table_set_row_spacings(GTK_TABLE(table2), 5); gtk_table_set_col_spacings(GTK_TABLE(table2), 5); label15 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label15), _("Name:")); gtk_widget_show(label15); gtk_table_attach(GTK_TABLE(table2), label15, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label15), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label15), 0, 1); labelName = gtk_label_new(("label20")); gtk_widget_show(labelName); gtk_table_attach(GTK_TABLE(table2), labelName, 1, 2, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelName), 0, 0.5); labelModel = gtk_label_new(("label21")); gtk_widget_show(labelModel); gtk_table_attach(GTK_TABLE(table2), labelModel, 1, 2, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelModel), 0, 0.5); labelSerial = gtk_label_new(("label22")); gtk_widget_show(labelSerial); gtk_table_attach(GTK_TABLE(table2), labelSerial, 1, 2, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelSerial), 0, 0.5); labelBattery = gtk_label_new(("label24")); gtk_widget_show(labelBattery); gtk_table_attach(GTK_TABLE(table2), labelBattery, 1, 2, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelBattery), 0, 0.5); labelManufacturer = gtk_label_new(("label23")); gtk_widget_show(labelManufacturer); gtk_table_attach(GTK_TABLE(table2), labelManufacturer, 1, 2, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelManufacturer), 0, 0.5); labelDeviceVer = gtk_label_new(("label26")); gtk_widget_show(labelDeviceVer); gtk_table_attach(GTK_TABLE(table2), labelDeviceVer, 1, 2, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelDeviceVer), 0, 0.5); label26 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label26), _("Model Number:")); gtk_widget_show(label26); gtk_table_attach(GTK_TABLE(table2), label26, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label26), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label26), 0, 1); label29 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label29), _("Serial Number:")); gtk_widget_show(label29); gtk_table_attach(GTK_TABLE(table2), label29, 0, 1, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label29), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label29), 0, 1); label28 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label28), _("Device Version:")); gtk_widget_show(label28); gtk_table_attach(GTK_TABLE(table2), label28, 0, 1, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label28), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label28), 0, 1); label27 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label27), _("Manufacturer:")); gtk_widget_show(label27); gtk_table_attach(GTK_TABLE(table2), label27, 0, 1, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label27), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label27), 0, 1); label17 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label17), _("Battery Level:")); gtk_widget_show(label17); gtk_table_attach(GTK_TABLE(table2), label17, 0, 1, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label17), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label17), 0, 1); label25 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label25), _("Storage:")); gtk_widget_show(label25); gtk_table_attach(GTK_TABLE(table2), label25, 0, 1, 6, 7, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label25), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label25), 0, 1); vbox2 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox2); //gtk_container_add (GTK_CONTAINER (windowDialog), vbox1); gtk_table_attach(GTK_TABLE(table2), vbox2, 0, 1, 7, 8, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); label18 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label18), _("Supported Formats:")); gtk_box_pack_start(GTK_BOX(vbox2), label18, FALSE, TRUE, 0); gtk_widget_show(label18); gtk_label_set_justify(GTK_LABEL(label18), GTK_JUSTIFY_RIGHT); //gtk_misc_set_alignment (GTK_MISC (label18), 1, 0); gtk_label_set_line_wrap(GTK_LABEL(label18), TRUE); gtk_misc_set_alignment(GTK_MISC(label18), 0, 1); label50 = gtk_label_new(("")); gtk_box_pack_start(GTK_BOX(vbox2), label50, FALSE, TRUE, 0); gtk_widget_show(label50); label19 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label19), _("Secure Time:")); gtk_widget_show(label19); gtk_table_attach(GTK_TABLE(table2), label19, 0, 1, 8, 9, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label19), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label19), 0, 1); label16 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label16), _("Sync Partner:")); gtk_widget_show(label16); gtk_table_attach(GTK_TABLE(table2), label16, 0, 1, 9, 10, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label16), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label16), 0, 1); labelStorage = gtk_label_new(("label30")); gtk_widget_show(labelStorage); gtk_table_attach(GTK_TABLE(table2), labelStorage, 1, 2, 6, 7, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelStorage), 0, 0.5); labelSupportedFormat = gtk_label_new(("label31")); gtk_widget_show(labelSupportedFormat); gtk_table_attach(GTK_TABLE(table2), labelSupportedFormat, 1, 2, 7, 8, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_line_wrap(GTK_LABEL(labelSupportedFormat), FALSE); gtk_misc_set_alignment(GTK_MISC(labelSupportedFormat), 0, 0.5); labelSecTime = gtk_label_new(("label32")); gtk_widget_show(labelSecTime); gtk_table_attach(GTK_TABLE(table2), labelSecTime, 1, 2, 8, 9, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_line_wrap(GTK_LABEL(labelSecTime), TRUE); gtk_misc_set_alignment(GTK_MISC(labelSecTime), 0, 0.5); labelSyncPartner = gtk_label_new(("label33")); gtk_widget_show(labelSyncPartner); gtk_table_attach(GTK_TABLE(table2), labelSyncPartner, 1, 2, 9, 10, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelSyncPartner), 0, 0.5); // Start the Raw Device Pane. table1 = gtk_table_new(6, 2, FALSE); gtk_widget_show(table1); gtk_container_add(GTK_CONTAINER(alignment1), table1); gtk_container_set_border_width(GTK_CONTAINER(table1), 5); gtk_table_set_row_spacings(GTK_TABLE(table1), 5); gtk_table_set_col_spacings(GTK_TABLE(table1), 5); label3 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label3), _("Vendor:")); gtk_widget_show(label3); gtk_table_attach(GTK_TABLE(table1), label3, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label3), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label3), 0, 1); label4 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label4), _("Product:")); gtk_widget_show(label4); gtk_table_attach(GTK_TABLE(table1), label4, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label4), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label4), 0, 1); label5 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label5), _("Vendor ID:")); gtk_widget_show(label5); gtk_table_attach(GTK_TABLE(table1), label5, 0, 1, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label5), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label5), 0, 1); label6 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label6), _("Product ID:")); gtk_widget_show(label6); gtk_table_attach(GTK_TABLE(table1), label6, 0, 1, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label6), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label6), 0, 1); label7 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label7), _("Device Number:")); gtk_widget_show(label7); gtk_table_attach(GTK_TABLE(table1), label7, 0, 1, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label7), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label7), 0, 1); label8 = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label8), _("Bus Location:")); gtk_widget_show(label8); gtk_table_attach(GTK_TABLE(table1), label8, 0, 1, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(label8), GTK_JUSTIFY_RIGHT); gtk_misc_set_alignment(GTK_MISC(label8), 0, 1); labelDeviceVendor = gtk_label_new(("label9")); gtk_widget_show(labelDeviceVendor); gtk_table_attach(GTK_TABLE(table1), labelDeviceVendor, 1, 2, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelDeviceVendor), 0, 0.5); labelDeviceProduct = gtk_label_new(("label10")); gtk_widget_show(labelDeviceProduct); gtk_table_attach(GTK_TABLE(table1), labelDeviceProduct, 1, 2, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelDeviceProduct), 0, 0.5); labelVenID = gtk_label_new(("label11")); gtk_widget_show(labelVenID); gtk_table_attach(GTK_TABLE(table1), labelVenID, 1, 2, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelVenID), 0, 0.5); labelProdID = gtk_label_new(("label12")); gtk_widget_show(labelProdID); gtk_table_attach(GTK_TABLE(table1), labelProdID, 1, 2, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelProdID), 0, 0.5); labelBusLoc = gtk_label_new(("label13")); gtk_widget_show(labelBusLoc); gtk_table_attach(GTK_TABLE(table1), labelBusLoc, 1, 2, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelBusLoc), 0, 0.5); labelDevNum = gtk_label_new(("label14")); gtk_widget_show(labelDevNum); gtk_table_attach(GTK_TABLE(table1), labelDevNum, 1, 2, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(labelDevNum), 0, 0.5); hbox2 = gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox2); gtk_box_pack_start(GTK_BOX(vbox1), hbox2, TRUE, TRUE, 0); buttonClose = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_widget_show(buttonClose); gtk_box_pack_end(GTK_BOX(hbox2), buttonClose, FALSE, FALSE, 0); g_signal_connect((gpointer) windowDialog, "destroy", G_CALLBACK(on_quitProp_activate), NULL); g_signal_connect((gpointer) buttonClose, "clicked", G_CALLBACK(on_quitProp_activate), NULL); // Now we need to update our strings for the information on the unit. gtk_label_set_text(GTK_LABEL(labelName), DeviceMgr.devicename->str); gtk_label_set_text(GTK_LABEL(labelModel), DeviceMgr.modelname->str); gtk_label_set_text(GTK_LABEL(labelSerial), DeviceMgr.serialnumber->str); if (DeviceMgr.maxbattlevel != 0) { tmp_string = g_strdup_printf("%d / %d (%d%%)", DeviceMgr.currbattlevel, DeviceMgr.maxbattlevel, (int) (((float) DeviceMgr.currbattlevel / (float) DeviceMgr.maxbattlevel) * 100.0)); } else { tmp_string = g_strdup_printf("%d / %d", DeviceMgr.currbattlevel, DeviceMgr.maxbattlevel); } gtk_label_set_text(GTK_LABEL(labelBattery), tmp_string); gtk_label_set_text(GTK_LABEL(labelManufacturer), DeviceMgr.manufacturername->str); gtk_label_set_text(GTK_LABEL(labelDeviceVer), DeviceMgr.deviceversion->str); if (DeviceMgr.storagedeviceID == MTP_DEVICE_SINGLE_STORAGE) { tmp_string = g_strdup_printf(_("%d MB (free) / %d MB (total)"), (int) (DeviceMgr.devicestorage->FreeSpaceInBytes / MEGABYTE), (int) (DeviceMgr.devicestorage->MaxCapacity / MEGABYTE)); gtk_label_set_text(GTK_LABEL(labelStorage), tmp_string); } else { tmp_string2 = g_string_new(""); // Cycle through each storage device and list the name and capacity. LIBMTP_devicestorage_t* deviceStorage = DeviceMgr.device->storage; while (deviceStorage != NULL) { if (tmp_string2->len > 0) tmp_string2 = g_string_append(tmp_string2, "\n"); if (deviceStorage->StorageDescription != NULL) { tmp_string2 = g_string_append(tmp_string2, deviceStorage->StorageDescription); } else { tmp_string2 = g_string_append(tmp_string2, deviceStorage->VolumeIdentifier); } tmp_string = g_strdup_printf(" : %d MB (free) / %d MB (total)", (int) (deviceStorage->FreeSpaceInBytes / MEGABYTE), (int) (deviceStorage->MaxCapacity / MEGABYTE)); tmp_string2 = g_string_append(tmp_string2, tmp_string); deviceStorage = deviceStorage->next; } gtk_label_set_text(GTK_LABEL(labelStorage), tmp_string2->str); g_string_free(tmp_string2, TRUE); } tmp_string2 = g_string_new(""); // Build a string for us to use. gint i = 0; for (i = 0; i < DeviceMgr.filetypes_len; i++) { if (tmp_string2->len > 0) tmp_string2 = g_string_append(tmp_string2, "\n"); tmp_string2 = g_string_append(tmp_string2, LIBMTP_Get_Filetype_Description(DeviceMgr.filetypes[i])); } gtk_label_set_text(GTK_LABEL(labelSupportedFormat), tmp_string2->str); g_string_free(tmp_string2, TRUE); gtk_label_set_text(GTK_LABEL(labelSecTime), DeviceMgr.sectime->str); gtk_label_set_text(GTK_LABEL(labelSyncPartner), DeviceMgr.syncpartner->str); // This is our raw information. gtk_label_set_text(GTK_LABEL(labelDeviceVendor), DeviceMgr.Vendor->str); gtk_label_set_text(GTK_LABEL(labelDeviceProduct), DeviceMgr.Product->str); gtk_label_set_text(GTK_LABEL(labelVenID), g_strdup_printf("0x%x", DeviceMgr.VendorID)); gtk_label_set_text(GTK_LABEL(labelProdID), g_strdup_printf("0x%x", DeviceMgr.ProductID)); gtk_label_set_text(GTK_LABEL(labelBusLoc), g_strdup_printf("0x%x", DeviceMgr.BusLoc)); gtk_label_set_text(GTK_LABEL(labelDevNum), g_strdup_printf("0x%x", DeviceMgr.DeviceID)); return windowDialog; } // ************************************************************************************************ /** * Create a Upload/Download Progress Window. * @param msg Default message to be displayed. * @return */ GtkWidget* create_windowProgressDialog(gchar* msg) { GtkWidget *window1; GtkWidget *vbox1; GtkWidget *hbox1; GtkWidget *label_FileProgress; GtkWidget *label1; GtkWidget *cancelButton; GtkWidget *progressbar_Main; window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL); gchar * winTitle; winTitle = g_strconcat(PACKAGE_TITLE, NULL); gtk_window_set_title(GTK_WINDOW(window1), winTitle); gtk_window_set_position(GTK_WINDOW(window1), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_modal(GTK_WINDOW(window1), TRUE); gtk_window_set_resizable(GTK_WINDOW(window1), FALSE); gtk_window_set_transient_for(GTK_WINDOW(window1), GTK_WINDOW(windowMain)); gtk_window_set_destroy_with_parent(GTK_WINDOW(window1), TRUE); gtk_window_set_type_hint(GTK_WINDOW(window1), GDK_WINDOW_TYPE_HINT_DIALOG); g_free(winTitle); vbox1 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(window1), vbox1); gtk_container_set_border_width(GTK_CONTAINER(vbox1), 10); gtk_box_set_spacing(GTK_BOX(vbox1), 5); label1 = gtk_label_new(NULL); winTitle = g_strconcat("", msg, "", NULL); gtk_label_set_markup(GTK_LABEL(label1), winTitle); gtk_widget_show(label1); gtk_box_pack_start(GTK_BOX(vbox1), label1, TRUE, TRUE, 0); gtk_misc_set_padding(GTK_MISC(label1), 0, 5); gtk_misc_set_alignment(GTK_MISC(label1), 0, 0); g_free(winTitle); label_FileProgress = gtk_label_new(_("file = ( x / x ) x %")); gtk_widget_set_size_request(label_FileProgress, 320, -1); gtk_widget_show(label_FileProgress); gtk_box_pack_start(GTK_BOX(vbox1), label_FileProgress, TRUE, TRUE, 0); gtk_misc_set_padding(GTK_MISC(label_FileProgress), 0, 5); progressbar_Main = gtk_progress_bar_new(); gtk_widget_show(progressbar_Main); gtk_box_pack_start(GTK_BOX(vbox1), progressbar_Main, TRUE, TRUE, 0); // Insert a cancel button. hbox1 = gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox1); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, FALSE, 0); cancelButton = gtk_button_new_from_stock(GTK_STOCK_CANCEL); gtk_widget_show(cancelButton); gtk_box_pack_end(GTK_BOX(hbox1), cancelButton, FALSE, FALSE, 0); progressDialog = window1; progressDialog_Text = label_FileProgress; progressDialog_Bar = progressbar_Main; g_signal_connect((gpointer) cancelButton, "clicked", G_CALLBACK(on_progressDialog_Cancel), NULL); return window1; } // ************************************************************************************************ /** * Display the File Progress Window. * @param msg Message to be displayed. */ void displayProgressBar(gchar *msg) { // No idea how this could come about, but we should take it into account so we don't have a memleak // due to recreating the window multiple times. if (progressDialog != NULL) { destroyProgressBar(); } // create our progress window. progressDialog = create_windowProgressDialog(msg); progressDialog_killed = FALSE; // Attach a callback to get notification that it has closed. g_signal_connect((gpointer) progressDialog, "destroy", G_CALLBACK(on_progressDialog_Close), NULL); // Show the progress window. gtk_widget_show_all(progressDialog); } // ************************************************************************************************ /** * Destroy the Progress Window. */ void destroyProgressBar(void) { if (progressDialog_killed == FALSE) { gtk_widget_hide(progressDialog); gtk_widget_destroy(progressDialog); } g_free(progressDialog_filename); progressDialog = NULL; progressDialog_Text = NULL; progressDialog_Bar = NULL; progressDialog_killed = FALSE; } // ************************************************************************************************ /** * Update the filename displayed in the Progress Window. * @param filename */ void setProgressFilename(gchar* filename) { progressDialog_filename = g_strdup(filename); } // ************************************************************************************************ /** * Callback to handle updating the Progress Window. * @param sent * @param total * @param data * @return */ int fileprogress(const uint64_t sent, const uint64_t total, void const * const data) { gchar* tmp_string; gint percent = (sent * 100) / total; // See if our dialog box was killed, and if so, just return which also kill our download/upload... if (progressDialog_killed == TRUE) return TRUE; // Now update the progress dialog. if (progressDialog != NULL) { if (progressDialog_filename != NULL) { tmp_string = g_strdup_printf(_("%s - %lluKB of %lluKB (%d%%)"), progressDialog_filename, (sent / 1024), (total / 1024), percent); } else { tmp_string = g_strdup_printf(_("%lluKB of %lluKB (%d%%)"), (sent / 1024), (total / 1024), percent); } gtk_label_set_text(GTK_LABEL(progressDialog_Text), tmp_string); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressDialog_Bar), (double) percent / 100.00); while (gtk_events_pending()) gtk_main_iteration(); g_free(tmp_string); } return 0; } // ************************************************************************************************ /** * Display the About Dialog Box. */ void displayAbout(void) { #if GTK_CHECK_VERSION(2,12,0) GtkWidget *dialog; const char *authors[] = { "Development", "Darran Kartaschew (chewy509@mailcity.com)", "\nTranslations", "English - Darran Kartaschew", "Italian - Francesca Ciceri", "French - 'Coug'", "German - Laurenz Kamp", "Spanish - Google Translate", "Danish - Cai Andersen", NULL }; dialog = gtk_about_dialog_new(); gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(dialog), PACKAGE_TITLE); gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), PACKAGE_VERSION); gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog), "Copyright 2009-2012, Darran Kartaschew\nReleased under the BSD License"); gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), _("A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n")); gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(dialog), "gMTP License\n" "------------\n\n" "Copyright (C) 2009-2012, Darran Kartaschew.\n" "All rights reserved.\n\n" "Redistribution and use in source and binary forms, with or without " "modification, are permitted provided that the following conditions are met:\n\n" "* Redistributions of source code must retain the above copyright notice, " "this list of conditions and the following disclaimer.\n\n" "* 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. \n\n" "* Neither the name of \"gMTP Development Team\" nor the names of its " "contributors may be used to endorse or promote products derived from this " "software without specific prior written permission. \n\n" "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"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 OWNER 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."); gtk_about_dialog_set_wrap_license(GTK_ABOUT_DIALOG(dialog), TRUE); gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "http://gmtp.sourceforge.net"); gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), gdk_pixbuf_new_from_file(file_logo_png, NULL)); gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(dialog), authors); gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(dialog), _("translator-credits")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); #else GtkWidget *dialog, *vbox, *label, *label2, *label3, *label4, *label5, *image; gchar *version_string; gchar *gtk_version_string; dialog = gtk_dialog_new_with_buttons(_("About gMTP"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); #if GMTP_USE_GTK2 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); #endif vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox); #endif // Add in our icon. image = gtk_image_new_from_file(file_logo_png); gtk_widget_show(image); gtk_container_add(GTK_CONTAINER(vbox), image); version_string = g_strconcat("", PACKAGE_TITLE, " v", PACKAGE_VERSION, "", NULL); label = gtk_label_new(version_string); gtk_label_set_use_markup(GTK_LABEL(label), TRUE); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(vbox), label); label2 = gtk_label_new(_("A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n")); gtk_label_set_use_markup(GTK_LABEL(label2), TRUE); gtk_label_set_justify(GTK_LABEL(label2), GTK_JUSTIFY_CENTER); gtk_misc_set_padding(GTK_MISC(label2), 5, 0); gtk_widget_show(label2); gtk_container_add(GTK_CONTAINER(vbox), label2); label5 = gtk_label_new("http://gmtp.sourceforge.net\n"); gtk_label_set_use_markup(GTK_LABEL(label5), TRUE); gtk_widget_show(label5); gtk_container_add(GTK_CONTAINER(vbox), label5); label3 = gtk_label_new(_("Copyright 2009-2012, Darran Kartaschew\nReleased under the BSD Licence")); gtk_label_set_use_markup(GTK_LABEL(label3), TRUE); gtk_widget_show(label3); gtk_container_add(GTK_CONTAINER(vbox), label3); gtk_version_string = g_strdup_printf("Built with GTK v%d.%d.%d\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); label4 = gtk_label_new(gtk_version_string); gtk_label_set_use_markup(GTK_LABEL(label4), TRUE); gtk_widget_show(label4); gtk_container_add(GTK_CONTAINER(vbox), label4); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); g_free(version_string); g_free(gtk_version_string); #endif } // ************************************************************************************************ /** * Display an Error Dialog Message Box. * @param msg */ void displayError(gchar* msg) { GtkWidget *dialog; dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(windowMain), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } // ************************************************************************************************ /** * Display an Information Dialog Message Box. * @param msg */ void displayInformation(gchar* msg) { GtkWidget *dialog; dialog = gtk_message_dialog_new(GTK_WINDOW(windowMain), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", msg); gtk_window_set_title(GTK_WINDOW(dialog), _("Information")); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } // ************************************************************************************************ /** * Display the Add New Folder Dialog Box. * @return The name of the folder to be created. */ gchar* displayFolderNewDialog(void) { GtkWidget *dialog, *hbox, *label, *textbox; gchar* textfield; dialog = gtk_dialog_new_with_buttons(_("New Folder"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("Folder Name:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); textbox = gtk_entry_new(); gtk_widget_show(textbox); gtk_entry_set_max_length(GTK_ENTRY(textbox), 64); gtk_entry_set_has_frame(GTK_ENTRY(textbox), TRUE); gtk_entry_set_activates_default(GTK_ENTRY(textbox), TRUE); gtk_container_add(GTK_CONTAINER(hbox), textbox); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { textfield = g_strdup(gtk_entry_get_text(GTK_ENTRY(textbox))); if (strlen(textfield) == 0) { // We have an emtpy string. gtk_widget_destroy(dialog); return NULL; } else { gtk_widget_destroy(dialog); return textfield; } } else { gtk_widget_destroy(dialog); return NULL; } } // ************************************************************************************************ /** * Display the Change Device Name dialog box. * @param devicename The new name of the device. * @return */ gchar* displayChangeDeviceNameDialog(gchar* devicename) { GtkWidget *dialog, *hbox, *label, *textbox; gchar* textfield; dialog = gtk_dialog_new_with_buttons(_("Change Device Name"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("Device Name:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); textbox = gtk_entry_new(); gtk_widget_show(textbox); if (devicename != NULL) { gtk_entry_set_text(GTK_ENTRY(textbox), devicename); } gtk_entry_set_max_length(GTK_ENTRY(textbox), 64); gtk_entry_set_has_frame(GTK_ENTRY(textbox), TRUE); gtk_entry_set_activates_default(GTK_ENTRY(textbox), TRUE); gtk_container_add(GTK_CONTAINER(hbox), textbox); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { textfield = g_strdup(gtk_entry_get_text(GTK_ENTRY(textbox))); if (strlen(textfield) == 0) { // We have an emtpy string. gtk_widget_destroy(dialog); return NULL; } else { gtk_widget_destroy(dialog); return textfield; } } else { gtk_widget_destroy(dialog); return NULL; } } // ************************************************************************************************ /** * Display the rename Filename dialog box. * @param currentfilename The new name of the file. * @return */ gchar* displayRenameFileDialog(gchar* currentfilename) { GtkWidget *dialog, *hbox, *label, *textbox; gchar* textfield; dialog = gtk_dialog_new_with_buttons(_("Rename File/Folder"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("File Name:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); textbox = gtk_entry_new(); gtk_widget_show(textbox); if (currentfilename != NULL) { gtk_entry_set_text(GTK_ENTRY(textbox), currentfilename); } gtk_entry_set_max_length(GTK_ENTRY(textbox), 64); gtk_entry_set_has_frame(GTK_ENTRY(textbox), TRUE); gtk_entry_set_activates_default(GTK_ENTRY(textbox), TRUE); gtk_container_add(GTK_CONTAINER(hbox), textbox); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { textfield = g_strdup(gtk_entry_get_text(GTK_ENTRY(textbox))); if (strlen(textfield) == 0) { // We have an emtpy string. gtk_widget_destroy(dialog); return NULL; } else { gtk_widget_destroy(dialog); return textfield; } } else { gtk_widget_destroy(dialog); return NULL; } } // ************************************************************************************************ /** * Display the Which Device dialog box. * @return */ gint displayMultiDeviceDialog(void) { GtkWidget *dialog, *hbox, *label, *textbox; gchar *tmp_string = NULL; gint dialog_selection = 0; dialog = gtk_dialog_new_with_buttons(_("Connect to which device?"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("Device:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); // Now create the combo box. #if GMTP_USE_GTK2 textbox = gtk_combo_box_new_text(); #else textbox = gtk_combo_box_text_new(); #endif gtk_widget_show(textbox); gtk_container_add(GTK_CONTAINER(hbox), textbox); // Now add in our selection strings. // We should just take straight strings here, but this is quicker/easier. gint i = 0; for (i = 0; i < DeviceMgr.numrawdevices; i++) { if (DeviceMgr.rawdevices[i].device_entry.vendor != NULL || DeviceMgr.rawdevices[i].device_entry.product != NULL) { tmp_string = g_strdup_printf(" %s %s : (%04x:%04x) @ bus %d, dev %d", DeviceMgr.rawdevices[i].device_entry.vendor, DeviceMgr.rawdevices[i].device_entry.product, DeviceMgr.rawdevices[i].device_entry.vendor_id, DeviceMgr.rawdevices[i].device_entry.product_id, DeviceMgr.rawdevices[i].bus_location, DeviceMgr.rawdevices[i].devnum); } else { tmp_string = g_strdup_printf(_("Unknown : %04x:%04x @ bus %d, dev %d"), DeviceMgr.rawdevices[i].device_entry.vendor_id, DeviceMgr.rawdevices[i].device_entry.product_id, DeviceMgr.rawdevices[i].bus_location, DeviceMgr.rawdevices[i].devnum); } #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(textbox), tmp_string); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(textbox), tmp_string); #endif } gtk_combo_box_set_active(GTK_COMBO_BOX(textbox), 0); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { dialog_selection = gtk_combo_box_get_active(GTK_COMBO_BOX(textbox)); } gtk_widget_destroy(dialog); g_free(tmp_string); return dialog_selection; } // ************************************************************************************************ /** * Display the Which Storage Device dialog box. * @return */ gint displayDeviceStorageDialog(void) { GtkWidget *dialog, *hbox, *label, *textbox; LIBMTP_devicestorage_t *devicestorage; gchar *tmp_string = NULL; gint dialog_selection = 0; devicestorage = DeviceMgr.device->storage; dialog = gtk_dialog_new_with_buttons(_("Connect to which storage device?"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); //gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("Storage Device:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); // Now create the combo box. #if GMTP_USE_GTK2 textbox = gtk_combo_box_new_text(); #else textbox = gtk_combo_box_text_new(); #endif gtk_widget_show(textbox); gtk_container_add(GTK_CONTAINER(hbox), textbox); // Now add in our selection strings. while (devicestorage != NULL) { if (devicestorage->StorageDescription != NULL) { #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(textbox), devicestorage->StorageDescription); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(textbox), devicestorage->StorageDescription); #endif } else { tmp_string = g_strdup_printf(_("Unknown id: %d, %llu MB"), devicestorage->id, (devicestorage->MaxCapacity / MEGABYTE)); #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(textbox), tmp_string); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(textbox), tmp_string); #endif } devicestorage = devicestorage->next; } gtk_combo_box_set_active(GTK_COMBO_BOX(textbox), 0); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { dialog_selection = gtk_combo_box_get_active(GTK_COMBO_BOX(textbox)); } gtk_widget_destroy(dialog); g_free(tmp_string); return dialog_selection; } // ************************************************************************************************ /** * Display the Overwrite File dialog box. * @param filename * @return */ gint displayFileOverwriteDialog(gchar *filename) { GtkWidget *dialog; dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("File %s already exists in target folder.\nDo you want to:"), filename); gtk_dialog_add_buttons(GTK_DIALOG(dialog), _("Skip"), MTP_SKIP, _("Skip All"), MTP_SKIP_ALL, _("Overwrite"), MTP_OVERWRITE, _("Overwrite All"), MTP_OVERWRITE_ALL, NULL); gtk_window_set_title(GTK_WINDOW(dialog), _("Question: Confirm Overwrite of Existing File?")); gtk_dialog_set_default_response(GTK_DIALOG(dialog), MTP_OVERWRITE); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return result; } // ************************************************************************************************ /** * Display the Add Album Art dialog box. * @return */ void displayAddAlbumArtDialog(void) { //Album_Struct* albumdetails; GtkWidget *hbox, *label; GtkWidget *buttonBox; LIBMTP_album_t *albuminfo = NULL; LIBMTP_album_t *album_orig = NULL; AlbumArtDialog = gtk_dialog_new_with_buttons(_("Album Art"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); gtk_dialog_set_default_response(GTK_DIALOG(AlbumArtDialog), GTK_RESPONSE_CLOSE); gtk_window_set_resizable(GTK_WINDOW(AlbumArtDialog), FALSE); #if GMTP_USE_GTK2 gtk_dialog_set_has_separator(GTK_DIALOG(AlbumArtDialog), TRUE); #endif // Set some nice 5px spacing. #if GMTP_USE_GTK2 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(AlbumArtDialog)->vbox), 10); #else gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(AlbumArtDialog))), 10); #endif hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(AlbumArtDialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(AlbumArtDialog))), hbox); #endif label = gtk_label_new(_("Album:")); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); // Now create the combo box. #if GMTP_USE_GTK2 textboxAlbumArt = gtk_combo_box_new_text(); #else textboxAlbumArt = gtk_combo_box_text_new(); #endif gtk_widget_show(textboxAlbumArt); gtk_box_pack_start(GTK_BOX(hbox), textboxAlbumArt, TRUE, TRUE, 0); // Now add in our selection strings. albuminfo = LIBMTP_Get_Album_List_For_Storage(DeviceMgr.device, DeviceMgr.devicestorage->id); // Better check to see if we actually have anything? if (albuminfo == NULL) { // we have no albums. displayInformation(_("No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n")); gtk_widget_destroy(AlbumArtDialog); AlbumArtImage = NULL; AlbumArtDialog = NULL; textboxAlbumArt = NULL; return; } album_orig = albuminfo; while (albuminfo != NULL) { if (albuminfo->name != NULL) #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(textboxAlbumArt), albuminfo->name); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(textboxAlbumArt), albuminfo->name); #endif albuminfo = albuminfo->next; } // End add selection. gtk_combo_box_set_active(GTK_COMBO_BOX(textboxAlbumArt), 0); // Add in a image view of the current uploaded album art. AlbumArtImage = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_DIALOG); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(AlbumArtDialog)->vbox), AlbumArtImage); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(AlbumArtDialog))), AlbumArtImage); #endif gtk_widget_show(AlbumArtImage); // Add in the album art operations area. buttonBox = gtk_hbutton_box_new(); gtk_box_set_spacing(GTK_BOX(buttonBox), 5); gtk_widget_show(buttonBox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(AlbumArtDialog)->vbox), buttonBox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(AlbumArtDialog))), buttonBox); #endif buttonAlbumAdd = gtk_button_new_with_mnemonic(_("Upload")); gtk_widget_show(buttonAlbumAdd); gtk_box_pack_start(GTK_BOX(buttonBox), buttonAlbumAdd, TRUE, TRUE, 0); buttonAlbumDelete = gtk_button_new_with_mnemonic(_("Remove")); gtk_widget_show(buttonAlbumDelete); gtk_box_pack_start(GTK_BOX(buttonBox), buttonAlbumDelete, TRUE, TRUE, 0); gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDelete), FALSE); buttonAlbumDownload = gtk_button_new_with_mnemonic(_("Download")); gtk_widget_show(buttonAlbumDownload); gtk_box_pack_start(GTK_BOX(buttonBox), buttonAlbumDownload, TRUE, TRUE, 0); gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDownload), FALSE); // Now, update the stock image with one from the selected album. AlbumArtUpdateImage(album_orig); g_signal_connect((gpointer) textboxAlbumArt, "changed", G_CALLBACK(on_albumtextbox_activate), NULL); g_signal_connect((gpointer) buttonAlbumAdd, "clicked", G_CALLBACK(on_buttonAlbumArtAdd_activate), NULL); g_signal_connect((gpointer) buttonAlbumDelete, "clicked", G_CALLBACK(on_buttonAlbumArtDelete_activate), NULL); g_signal_connect((gpointer) buttonAlbumDownload, "clicked", G_CALLBACK(on_buttonAlbumArtDownload_activate), NULL); gtk_dialog_run(GTK_DIALOG(AlbumArtDialog)); gtk_widget_destroy(AlbumArtDialog); clearAlbumStruc(album_orig); //Clean up global pointers. AlbumArtImage = NULL; AlbumArtDialog = NULL; textboxAlbumArt = NULL; } // ************************************************************************************************ /** * Set the image in the AddAlbumDialog to be that supplied with the album information. * @return */ void AlbumArtUpdateImage(LIBMTP_album_t* selectedAlbum) { LIBMTP_filesampledata_t *imagedata = NULL; GdkPixbufLoader *BufferLoader = NULL; GdkPixbuf *gdk_image = NULL; GdkPixbuf *gdk_image_scale = NULL; // Ensure our widget exists. if (AlbumArtImage == NULL) return; // Ensure we have a selected album. if (selectedAlbum == NULL) { AlbumArtSetDefault(); return; } imagedata = albumGetArt(selectedAlbum); if (imagedata != NULL) { if (imagedata->size != 0) { // We have our image data. // Create a GdkPixbuf BufferLoader = gdk_pixbuf_loader_new(); if (gdk_pixbuf_loader_write(BufferLoader, (const guchar*) imagedata->data, imagedata->size, NULL) == TRUE) { // Set the GtkImage to use that GdkPixbuf. gdk_image = gdk_pixbuf_loader_get_pixbuf(BufferLoader); gdk_pixbuf_loader_close(BufferLoader, NULL); gdk_image_scale = gdk_pixbuf_scale_simple(gdk_image, ALBUM_SIZE, ALBUM_SIZE, GDK_INTERP_BILINEAR); gtk_image_set_from_pixbuf(GTK_IMAGE(AlbumArtImage), gdk_image_scale); g_object_unref(gdk_image); g_object_unref(gdk_image_scale); // Set button states, so we can do stuff on the image. gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDownload), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDelete), TRUE); } else { AlbumArtSetDefault(); } } else { AlbumArtSetDefault(); } // Clean up the image buffer. LIBMTP_destroy_filesampledata_t(imagedata); } else { AlbumArtSetDefault(); } } // ************************************************************************************************ /** * Set the album art to be the default image. */ void AlbumArtSetDefault(void) { //gtk_image_set_from_stock(GTK_IMAGE(AlbumArtImage), GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_DIALOG ); GdkPixbuf *gdk_image = NULL; GdkPixbuf *gdk_image_scale = NULL; gdk_image = gtk_widget_render_icon(GTK_WIDGET(AlbumArtImage), GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_DIALOG, "gtk-missing-image"); gdk_image_scale = gdk_pixbuf_scale_simple(gdk_image, ALBUM_SIZE, ALBUM_SIZE, GDK_INTERP_BILINEAR); gtk_image_set_from_pixbuf(GTK_IMAGE(AlbumArtImage), gdk_image_scale); g_object_unref(gdk_image); g_object_unref(gdk_image_scale); // Disable the buttons, since we have a default image. gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDownload), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(buttonAlbumDelete), FALSE); } // ************************************************************************************************ /** * Create the Context Menu widget. * @return */ GtkWidget* create_windowMainContextMenu(void) { GtkWidget* menu; //GtkWidget* cfileAdd; GtkWidget* cfileRename; GtkWidget* cfileRemove; GtkWidget* cfileDownload; GtkWidget* cfileMove; //GtkWidget* cfileNewFolder; GtkWidget* cfileRemoveFolder; GtkWidget* cfileRescan; GtkWidget* cfileAddToPlaylist; GtkWidget* cfileRemoveFromPlaylist; GtkWidget* menuseparator1; GtkWidget* menuseparator2; GtkWidget* menuseparator3; menu = gtk_menu_new(); cfileAdd = gtk_menu_item_new_with_label(_("Add Files")); gtk_widget_show(cfileAdd); gtk_container_add(GTK_CONTAINER(menu), cfileAdd); cfileRemove = gtk_menu_item_new_with_label(_("Delete Files")); gtk_widget_show(cfileRemove); gtk_container_add(GTK_CONTAINER(menu), cfileRemove); cfileDownload = gtk_menu_item_new_with_label(_("Download Files")); gtk_widget_show(cfileDownload); gtk_container_add(GTK_CONTAINER(menu), cfileDownload); cfileRename = gtk_menu_item_new_with_label(_("Rename File")); gtk_widget_show(cfileRename); gtk_container_add(GTK_CONTAINER(menu), cfileRename); cfileMove = gtk_menu_item_new_with_label(_("Move To...")); gtk_widget_show(cfileMove); gtk_container_add(GTK_CONTAINER(menu), cfileMove); menuseparator3 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator3); gtk_container_add(GTK_CONTAINER(menu), menuseparator3); cfileAddToPlaylist = gtk_menu_item_new_with_label(_("Add To Playlist")); gtk_widget_show(cfileAddToPlaylist); gtk_container_add(GTK_CONTAINER(menu), cfileAddToPlaylist); cfileRemoveFromPlaylist = gtk_menu_item_new_with_label(_("Remove From Playlist")); gtk_widget_show(cfileRemoveFromPlaylist); gtk_container_add(GTK_CONTAINER(menu), cfileRemoveFromPlaylist); menuseparator1 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator1); gtk_container_add(GTK_CONTAINER(menu), menuseparator1); cfileNewFolder = gtk_menu_item_new_with_label(_("Create Folder")); gtk_widget_show(cfileNewFolder); gtk_container_add(GTK_CONTAINER(menu), cfileNewFolder); cfileRemoveFolder = gtk_menu_item_new_with_label(_("Delete Folder")); gtk_widget_show(cfileRemoveFolder); gtk_container_add(GTK_CONTAINER(menu), cfileRemoveFolder); menuseparator2 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator2); gtk_container_add(GTK_CONTAINER(menu), menuseparator2); cfileRescan = gtk_menu_item_new_with_label(_("Refresh Device")); gtk_widget_show(cfileRescan); gtk_container_add(GTK_CONTAINER(menu), cfileRescan); // Now our call backs. g_signal_connect((gpointer) cfileAdd, "activate", G_CALLBACK(on_filesAdd_activate), NULL); g_signal_connect((gpointer) cfileDownload, "activate", G_CALLBACK(on_filesDownload_activate), NULL); g_signal_connect((gpointer) cfileRename, "activate", G_CALLBACK(on_fileRenameFile_activate), NULL); g_signal_connect((gpointer) cfileMove, "activate", G_CALLBACK(on_fileMoveFile_activate), NULL); g_signal_connect((gpointer) cfileRemove, "activate", G_CALLBACK(on_filesDelete_activate), NULL); g_signal_connect((gpointer) cfileNewFolder, "activate", G_CALLBACK(on_fileNewFolder_activate), NULL); g_signal_connect((gpointer) cfileRemoveFolder, "activate", G_CALLBACK(on_fileRemoveFolder_activate), NULL); g_signal_connect((gpointer) cfileRescan, "activate", G_CALLBACK(on_deviceRescan_activate), NULL); g_signal_connect((gpointer) cfileAddToPlaylist, "activate", G_CALLBACK(on_fileAddToPlaylist_activate), NULL); g_signal_connect((gpointer) cfileRemoveFromPlaylist, "activate", G_CALLBACK(on_fileRemoveFromPlaylist_activate), NULL); return menu; } // ************************************************************************************************ /** * Create the Context Menu widget. * @return */ GtkWidget* create_windowMainColumnContextMenu(void) { GtkWidget* menu; menu = gtk_menu_new(); cViewSize = gtk_check_menu_item_new_with_label(_("File Size")); gtk_widget_show(cViewSize); gtk_container_add(GTK_CONTAINER(menu), cViewSize); cViewType = gtk_check_menu_item_new_with_label(_("File Type")); gtk_widget_show(cViewType); gtk_container_add(GTK_CONTAINER(menu), cViewType); cViewTrackNumber = gtk_check_menu_item_new_with_label(_("Track Number")); gtk_widget_show(cViewTrackNumber); gtk_container_add(GTK_CONTAINER(menu), cViewTrackNumber); cViewTrackName = gtk_check_menu_item_new_with_label(_("Track Name")); gtk_widget_show(cViewTrackName); gtk_container_add(GTK_CONTAINER(menu), cViewTrackName); cViewArtist = gtk_check_menu_item_new_with_label(_("Artist")); gtk_widget_show(cViewArtist); gtk_container_add(GTK_CONTAINER(menu), cViewArtist); cViewAlbum = gtk_check_menu_item_new_with_label(_("Album")); gtk_widget_show(cViewAlbum); gtk_container_add(GTK_CONTAINER(menu), cViewAlbum); cViewYear = gtk_check_menu_item_new_with_label(_("Year")); gtk_widget_show(cViewYear); gtk_container_add(GTK_CONTAINER(menu), cViewYear); cViewGenre = gtk_check_menu_item_new_with_label(_("Genre")); gtk_widget_show(cViewGenre); gtk_container_add(GTK_CONTAINER(menu), cViewGenre); cViewDuration = gtk_check_menu_item_new_with_label(_("Duration")); gtk_widget_show(cViewDuration); gtk_container_add(GTK_CONTAINER(menu), cViewDuration); // Now our call backs. g_signal_connect((gpointer) cViewSize, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewTrackNumber, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewTrackName, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewType, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewArtist, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewAlbum, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewYear, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewGenre, "toggled", G_CALLBACK(on_view_activate), NULL); g_signal_connect((gpointer) cViewDuration, "toggled", G_CALLBACK(on_view_activate), NULL); return menu; } // ************************************************************************************************ /** * Create the Context Menu widget. * @return */ GtkWidget* create_windowFolderContextMenu(void) { GtkWidget* menu; GtkWidget* menuseparator1; menu = gtk_menu_new(); cfFolderAdd = gtk_menu_item_new_with_label(_("Create Folder")); gtk_widget_show(cfFolderAdd); gtk_container_add(GTK_CONTAINER(menu), cfFolderAdd); cfFolderRename = gtk_menu_item_new_with_label(_("Rename Folder")); gtk_widget_show(cfFolderRename); gtk_container_add(GTK_CONTAINER(menu), cfFolderRename); cfFolderDelete = gtk_menu_item_new_with_label(_("Delete Folder")); gtk_widget_show(cfFolderDelete); gtk_container_add(GTK_CONTAINER(menu), cfFolderDelete); cfFolderMove = gtk_menu_item_new_with_label(_("Move To...")); gtk_widget_show(cfFolderMove); gtk_container_add(GTK_CONTAINER(menu), cfFolderMove); menuseparator1 = gtk_separator_menu_item_new(); gtk_widget_show(menuseparator1); gtk_container_add(GTK_CONTAINER(menu), menuseparator1); cfFolderRefresh = gtk_menu_item_new_with_label(_("Refresh Device")); gtk_widget_show(cfFolderRefresh); gtk_container_add(GTK_CONTAINER(menu), cfFolderRefresh); // Now our call backs. g_signal_connect((gpointer) cfFolderRefresh, "activate", G_CALLBACK(on_deviceRescan_activate), NULL); g_signal_connect((gpointer) cfFolderRename, "activate", G_CALLBACK(on_folderRenameFolder_activate), NULL); g_signal_connect((gpointer) cfFolderDelete, "activate", G_CALLBACK(on_folderRemoveFolder_activate), NULL); g_signal_connect((gpointer) cfFolderMove, "activate", G_CALLBACK(on_folderMoveFolder_activate), NULL); g_signal_connect((gpointer) cfFolderAdd, "activate", G_CALLBACK(on_folderNewFolder_activate), NULL); return menu; } // ************************************************************************************************ // Playlist support /** * Create the Playlist Editor Window. * @return */ GtkWidget* create_windowPlaylist(void) { GtkWidget *window_playlist; GtkWidget *vbox1; GtkWidget *hbox1; GtkWidget *label_Playlist; GtkWidget *button_Add_Playlist; GtkWidget *button_Import_Playlist; GtkWidget *alignment2; GtkWidget *hbox3; GtkWidget *image2; GtkWidget *label3; GtkWidget *alignment1; GtkWidget *hbox2; GtkWidget *image1; GtkWidget *label2; GtkWidget *hbox4; GtkWidget *scrolledwindow2; GtkWidget *vbuttonbox1; GtkWidget *alignment6; GtkWidget *hbox8; GtkWidget *image6; GtkWidget *label10; GtkWidget *alignment7; GtkWidget *hbox9; GtkWidget *image7; GtkWidget *label11; GtkWidget *scrolledwindow3; GtkWidget *vbuttonbox2; GtkWidget *hbuttonbox1; GtkWidget *button_Close; #if GMTP_USE_GTK2 GtkTooltips *tooltips; tooltips = gtk_tooltips_new(); #endif window_playlist = gtk_window_new(GTK_WINDOW_TOPLEVEL); gchar * winTitle; winTitle = g_strconcat(PACKAGE_TITLE, _(" Playlists"), NULL); gtk_window_set_title(GTK_WINDOW(window_playlist), winTitle); gtk_window_set_modal(GTK_WINDOW(window_playlist), TRUE); gtk_window_set_default_size(GTK_WINDOW(window_playlist), 760, 400); gtk_window_set_transient_for(GTK_WINDOW(window_playlist), GTK_WINDOW(windowMain)); gtk_window_set_position(GTK_WINDOW(window_playlist), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window_playlist), TRUE); gtk_window_set_type_hint(GTK_WINDOW(window_playlist), GDK_WINDOW_TYPE_HINT_DIALOG); g_free(winTitle); vbox1 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(window_playlist), vbox1); hbox1 = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox1); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, TRUE, 5); label_Playlist = gtk_label_new(_("Current Playlist: ")); gtk_widget_show(label_Playlist); gtk_box_pack_start(GTK_BOX(hbox1), label_Playlist, FALSE, FALSE, 5); gtk_misc_set_padding(GTK_MISC(label_Playlist), 5, 0); #if GMTP_USE_GTK2 comboboxentry_playlist = gtk_combo_box_new_text(); #else comboboxentry_playlist = gtk_combo_box_text_new(); #endif gtk_widget_show(comboboxentry_playlist); gtk_box_pack_start(GTK_BOX(hbox1), comboboxentry_playlist, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(comboboxentry_playlist), 5); button_Add_Playlist = gtk_button_new(); gtk_widget_show(button_Add_Playlist); gtk_box_pack_start(GTK_BOX(hbox1), button_Add_Playlist, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(button_Add_Playlist), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Add_Playlist, _("Add New Playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Add_Playlist, _("Add New Playlist")); #endif alignment2 = gtk_alignment_new(0.5, 0.5, 0, 0); gtk_widget_show(alignment2); gtk_container_add(GTK_CONTAINER(button_Add_Playlist), alignment2); hbox3 = gtk_hbox_new(FALSE, 2); gtk_widget_show(hbox3); gtk_container_add(GTK_CONTAINER(alignment2), hbox3); image2 = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); gtk_widget_show(image2); gtk_box_pack_start(GTK_BOX(hbox3), image2, FALSE, FALSE, 0); label3 = gtk_label_new_with_mnemonic(_("Add")); gtk_widget_show(label3); gtk_box_pack_start(GTK_BOX(hbox3), label3, FALSE, FALSE, 0); button_Del_Playlist = gtk_button_new(); gtk_widget_show(button_Del_Playlist); gtk_box_pack_start(GTK_BOX(hbox1), button_Del_Playlist, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(button_Del_Playlist), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Del_Playlist, _("Remove Current Selected Playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Del_Playlist, _("Remove Current Selected Playlist")); #endif alignment1 = gtk_alignment_new(0.5, 0.5, 0, 0); gtk_widget_show(alignment1); gtk_container_add(GTK_CONTAINER(button_Del_Playlist), alignment1); hbox2 = gtk_hbox_new(FALSE, 2); gtk_widget_show(hbox2); gtk_container_add(GTK_CONTAINER(alignment1), hbox2); image1 = gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON); gtk_widget_show(image1); gtk_box_pack_start(GTK_BOX(hbox2), image1, FALSE, FALSE, 0); label2 = gtk_label_new_with_mnemonic(_("Del")); gtk_widget_show(label2); gtk_box_pack_start(GTK_BOX(hbox2), label2, FALSE, FALSE, 0); // Import Button button_Import_Playlist = gtk_button_new_from_stock(GTK_STOCK_OPEN); gtk_widget_show(button_Import_Playlist); gtk_box_pack_start(GTK_BOX(hbox1), button_Import_Playlist, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(button_Import_Playlist), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Import_Playlist, _("Import Playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Import_Playlist, _("Import Playlist")); #endif // Export Button button_Export_Playlist = gtk_button_new_from_stock(GTK_STOCK_SAVE_AS); gtk_widget_show(button_Export_Playlist); gtk_box_pack_start(GTK_BOX(hbox1), button_Export_Playlist, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(button_Export_Playlist), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Export_Playlist, _("Export Playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Export_Playlist, _("Export Playlist")); #endif // Scrolled Window. hbox4 = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox4); gtk_box_pack_start(GTK_BOX(vbox1), hbox4, TRUE, TRUE, 0); scrolledwindow2 = gtk_scrolled_window_new(NULL, NULL); gtk_widget_show(scrolledwindow2); gtk_box_pack_start(GTK_BOX(hbox4), scrolledwindow2, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(scrolledwindow2), 5); treeview_Avail_Files = gtk_tree_view_new(); gtk_widget_show(treeview_Avail_Files); gtk_container_add(GTK_CONTAINER(scrolledwindow2), treeview_Avail_Files); gtk_container_set_border_width(GTK_CONTAINER(treeview_Avail_Files), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, treeview_Avail_Files, _("Device Audio Tracks"), NULL); #else gtk_widget_set_tooltip_text(treeview_Avail_Files, _("Device Audio Tracks")); #endif playlist_TrackSelection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_Avail_Files)); gtk_tree_selection_set_mode(playlist_TrackSelection, GTK_SELECTION_MULTIPLE); playlist_TrackList = gtk_list_store_new(NUM_TCOLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING); setupTrackList(GTK_TREE_VIEW(treeview_Avail_Files)); gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_Avail_Files), GTK_TREE_MODEL(playlist_TrackList)); g_object_unref(playlist_TrackList); vbuttonbox1 = gtk_vbutton_box_new(); gtk_widget_show(vbuttonbox1); gtk_box_pack_start(GTK_BOX(hbox4), vbuttonbox1, FALSE, FALSE, 0); gtk_button_box_set_layout(GTK_BUTTON_BOX(vbuttonbox1), GTK_BUTTONBOX_SPREAD); button_Add_Files = gtk_button_new(); gtk_widget_show(button_Add_Files); gtk_container_add(GTK_CONTAINER(vbuttonbox1), button_Add_Files); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Add_Files, _("Add file to playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Add_Files, _("Add file to playlist")); #endif alignment6 = gtk_alignment_new(0.5, 0.5, 0, 0); gtk_widget_show(alignment6); gtk_container_add(GTK_CONTAINER(button_Add_Files), alignment6); hbox8 = gtk_hbox_new(FALSE, 2); gtk_widget_show(hbox8); gtk_container_add(GTK_CONTAINER(alignment6), hbox8); image6 = gtk_image_new_from_stock(GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON); gtk_widget_show(image6); gtk_box_pack_start(GTK_BOX(hbox8), image6, FALSE, FALSE, 0); label10 = gtk_label_new_with_mnemonic(_("Add File")); gtk_widget_show(label10); gtk_box_pack_start(GTK_BOX(hbox8), label10, FALSE, FALSE, 0); button_Del_File = gtk_button_new(); gtk_widget_show(button_Del_File); gtk_container_add(GTK_CONTAINER(vbuttonbox1), button_Del_File); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_Del_File, _("Remove file from playlist"), NULL); #else gtk_widget_set_tooltip_text(button_Del_File, _("Remove file from playlist")); #endif alignment7 = gtk_alignment_new(0.5, 0.5, 0, 0); gtk_widget_show(alignment7); gtk_container_add(GTK_CONTAINER(button_Del_File), alignment7); hbox9 = gtk_hbox_new(FALSE, 2); gtk_widget_show(hbox9); gtk_container_add(GTK_CONTAINER(alignment7), hbox9); image7 = gtk_image_new_from_stock(GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON); gtk_widget_show(image7); gtk_box_pack_start(GTK_BOX(hbox9), image7, FALSE, FALSE, 0); label11 = gtk_label_new_with_mnemonic(_("Del File")); gtk_widget_show(label11); gtk_box_pack_start(GTK_BOX(hbox9), label11, FALSE, FALSE, 0); scrolledwindow3 = gtk_scrolled_window_new(NULL, NULL); gtk_widget_show(scrolledwindow3); gtk_box_pack_start(GTK_BOX(hbox4), scrolledwindow3, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(scrolledwindow3), 5); treeview_Playlist_Files = gtk_tree_view_new(); gtk_widget_show(treeview_Playlist_Files); gtk_container_add(GTK_CONTAINER(scrolledwindow3), treeview_Playlist_Files); gtk_container_set_border_width(GTK_CONTAINER(treeview_Playlist_Files), 5); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, treeview_Playlist_Files, _("Playlist Audio Tracks"), NULL); #else gtk_widget_set_tooltip_text(treeview_Playlist_Files, _("Playlist Audio Tracks")); #endif playlist_PL_Selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_Playlist_Files)); gtk_tree_selection_set_mode(playlist_PL_Selection, GTK_SELECTION_MULTIPLE); playlist_PL_List = gtk_list_store_new(NUM_PL_COLUMNS, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING); setup_PL_List(GTK_TREE_VIEW(treeview_Playlist_Files)); gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_Playlist_Files), GTK_TREE_MODEL(playlist_PL_List)); g_object_unref(playlist_PL_List); vbuttonbox2 = gtk_vbutton_box_new(); gtk_widget_show(vbuttonbox2); gtk_box_pack_start(GTK_BOX(hbox4), vbuttonbox2, FALSE, FALSE, 5); gtk_button_box_set_layout(GTK_BUTTON_BOX(vbuttonbox2), GTK_BUTTONBOX_SPREAD); button_File_Move_Up = gtk_button_new_from_stock(GTK_STOCK_GO_UP); gtk_widget_show(button_File_Move_Up); gtk_container_add(GTK_CONTAINER(vbuttonbox2), button_File_Move_Up); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_File_Move_Up, _("Move selected file up in the playlist"), NULL); #else gtk_widget_set_tooltip_text(button_File_Move_Up, _("Move selected file up in the playlist")); #endif button_File_Move_Down = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN); gtk_widget_show(button_File_Move_Down); gtk_container_add(GTK_CONTAINER(vbuttonbox2), button_File_Move_Down); #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, button_File_Move_Down, _("Move selected file down in the playlist"), NULL); #else gtk_widget_set_tooltip_text(button_File_Move_Down, _("Move selected file down in the playlist")); #endif hbuttonbox1 = gtk_hbutton_box_new(); gtk_widget_show(hbuttonbox1); gtk_box_pack_start(GTK_BOX(vbox1), hbuttonbox1, FALSE, FALSE, 5); gtk_button_box_set_layout(GTK_BUTTON_BOX(hbuttonbox1), GTK_BUTTONBOX_END); button_Close = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_widget_show(button_Close); gtk_container_add(GTK_CONTAINER(hbuttonbox1), button_Close); gtk_container_set_border_width(GTK_CONTAINER(button_Close), 5); g_signal_connect((gpointer) window_playlist, "destroy", G_CALLBACK(on_quitPlaylist_activate), NULL); g_signal_connect((gpointer) button_Close, "clicked", G_CALLBACK(on_quitPlaylist_activate), NULL); g_signal_connect((gpointer) button_Add_Playlist, "clicked", G_CALLBACK(on_Playlist_NewPlaylistButton_activate), NULL); g_signal_connect((gpointer) button_Import_Playlist, "clicked", G_CALLBACK(on_Playlist_ImportPlaylistButton_activate), NULL); g_signal_connect((gpointer) button_Export_Playlist, "clicked", G_CALLBACK(on_Playlist_ExportPlaylistButton_activate), NULL); g_signal_connect((gpointer) button_Del_Playlist, "clicked", G_CALLBACK(on_Playlist_DelPlaylistButton_activate), NULL); g_signal_connect((gpointer) button_Del_File, "clicked", G_CALLBACK(on_Playlist_DelFileButton_activate), NULL); g_signal_connect((gpointer) button_Add_Files, "clicked", G_CALLBACK(on_Playlist_AddFileButton_activate), NULL); g_signal_connect((gpointer) button_File_Move_Up, "clicked", G_CALLBACK(on_Playlist_FileUpButton_activate), NULL); g_signal_connect((gpointer) button_File_Move_Down, "clicked", G_CALLBACK(on_Playlist_FileDownButton_activate), NULL); g_signal_connect((gpointer) comboboxentry_playlist, "changed", G_CALLBACK(on_Playlist_Combobox_activate), NULL); return window_playlist; } // ************************************************************************************************ /** * Display the Playlist Editor. */ void displayPlaylistDialog(void) { //LIBMTP_playlist_t* tmpplaylist; LIBMTP_track_t* tmptrack; GtkTreeIter rowIter; gchar * tmp_string; if (windowPlaylistDialog != NULL) { gtk_widget_hide(windowPlaylistDialog); gtk_widget_destroy(windowPlaylistDialog); } windowPlaylistDialog = create_windowPlaylist(); playlist_number = 0; // Clear the track and playlist lists; gtk_list_store_clear(GTK_LIST_STORE(playlist_PL_List)); gtk_list_store_clear(GTK_LIST_STORE(playlist_TrackList)); // Populate the playlist changebox. devicePlayLists = getPlaylists(); deviceTracks = getTracks(); setPlayListComboBox(); // Populate the available track list. if (deviceTracks != NULL) { // Populate the track list; tmptrack = deviceTracks; while (tmptrack != NULL) { if ((tmptrack->storage_id == DeviceMgr.devicestorage->id) && (LIBMTP_FILETYPE_IS_AUDIO(tmptrack->filetype))) { gtk_list_store_append(GTK_LIST_STORE(playlist_TrackList), &rowIter); tmp_string = g_strdup_printf("%d:%.2d", (int) ((tmptrack->duration / 1000) / 60), (int) ((tmptrack->duration / 1000) % 60)); gtk_list_store_set(GTK_LIST_STORE(playlist_TrackList), &rowIter, COL_ARTIST, tmptrack->artist, COL_ALBUM, tmptrack->album, COL_TRACKID, tmptrack->item_id, COL_TRACKNAME, tmptrack->title, COL_TRACKDURATION, tmp_string, -1); g_free(tmp_string); tmp_string = NULL; } tmptrack = tmptrack->next; } } gtk_widget_show(GTK_WIDGET(windowPlaylistDialog)); // Save the current selected playlist if needed. } // ************************************************************************************************ /** * Setup the display for the Playlist. * @param treeviewFiles */ void setupTrackList(GtkTreeView *treeviewFiles) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; // Artist renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Artist"), renderer, "text", COL_ARTIST, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_sort_column_id(column, COL_ARTIST); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); // Album column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Album"), renderer, "text", COL_ALBUM, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_sort_column_id(column, COL_ALBUM); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); // Folder/FileID column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Object ID", renderer, "text", COL_TRACKID, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // Track column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Track"), renderer, "text", COL_TRACKNAME, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_sort_column_id(column, COL_TRACKNAME); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_visible(column, TRUE); // Track Duration renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Duration"), renderer, "text", COL_TRACKDURATION, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_visible(column, TRUE); } // ************************************************************************************************ /** * Setup the list of tracks in the current playlist. * @param treeviewFiles */ void setup_PL_List(GtkTreeView *treeviewFiles) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; // Order Num renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Num"), renderer, "text", COL_PL_ORDER_NUM, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); //gtk_tree_view_column_set_sort_column_id(column, COL_PL_ORDER_NUM); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); // Artist renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Artist"), renderer, "text", COL_PL_ARTIST, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); //gtk_tree_view_column_set_sort_column_id(column, COL_PL_ARTIST); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); // Album column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Album"), renderer, "text", COL_PL_ALBUM, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); //gtk_tree_view_column_set_sort_column_id(column, COL_PL_ALBUM); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_spacing(column, 5); // Folder/FileID column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("Object ID", renderer, "text", COL_PL_TRACKID, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_visible(column, FALSE); // Track column renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Track"), renderer, "text", COL_PL_TRACKNAME, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); //gtk_tree_view_column_set_sort_column_id(column, COL_TRACKNAME); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_visible(column, TRUE); // Track Duration renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Duration"), renderer, "text", COL_PL_TRACKDURATION, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(treeviewFiles), column); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_visible(column, TRUE); } // ************************************************************************************************ /** * Set the state of the buttons within the playlist editor. * @param state */ void SetPlaylistButtonState(gboolean state) { gtk_widget_set_sensitive(GTK_WIDGET(button_Del_Playlist), state); gtk_widget_set_sensitive(GTK_WIDGET(button_Export_Playlist), state); gtk_widget_set_sensitive(GTK_WIDGET(button_File_Move_Up), state); gtk_widget_set_sensitive(GTK_WIDGET(button_File_Move_Down), state); gtk_widget_set_sensitive(GTK_WIDGET(button_Del_File), state); gtk_widget_set_sensitive(GTK_WIDGET(button_Add_Files), state); gtk_widget_set_sensitive(GTK_WIDGET(treeview_Avail_Files), state); gtk_widget_set_sensitive(GTK_WIDGET(treeview_Playlist_Files), state); } // ************************************************************************************************ /** * Setup the Playlist selection Combo Box in the playlist editor. */ void setPlayListComboBox(void) { LIBMTP_playlist_t* tmpplaylist = NULL; comboboxentry_playlist_entries = 0; // We need to remove all entries in the combo box before starting. // This is a little bit of a hack - but does work. gtk_list_store_clear(GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(comboboxentry_playlist)))); if (devicePlayLists != NULL) { // Populate the playlist dropdown box; //comboboxentry_playlist; tmpplaylist = devicePlayLists; while (tmpplaylist != NULL) { if (tmpplaylist->storage_id == DeviceMgr.devicestorage->id) { #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(comboboxentry_playlist), g_strdup(tmpplaylist->name)); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(comboboxentry_playlist), g_strdup(tmpplaylist->name)); #endif comboboxentry_playlist_entries++; } tmpplaylist = tmpplaylist->next; } } if (devicePlayLists != NULL) { // Set our playlist to the first one. gtk_combo_box_set_active(GTK_COMBO_BOX(comboboxentry_playlist), 0); playlist_number = 0; // Now populate the playlist screen with it's details. setPlaylistField(0); } else { playlist_number = -1; } // If no playlists set parts of dialog to disabled. if (devicePlayLists == NULL) { SetPlaylistButtonState(FALSE); } else { SetPlaylistButtonState(TRUE); } } // ************************************************************************************************ /** * Setup the list of tracks in the current selected playlist. * @param PlayListID */ void setPlaylistField(gint PlayListID) { // This function will populate the playlist_PL_List widget with the // details of the selected playlist. LIBMTP_playlist_t* tmpplaylist = devicePlayLists; gint tmpplaylistID = PlayListID; guint trackID = 0; GtkTreeIter rowIter; gchar * tmp_string = NULL; playlist_track_count = 0; gtk_list_store_clear(GTK_LIST_STORE(playlist_PL_List)); if (PlayListID > 0) { while (tmpplaylistID--) if (tmpplaylist->next != NULL) tmpplaylist = tmpplaylist->next; } // tmpplaylist points to our playlist; for (trackID = 0; trackID < tmpplaylist->no_tracks; trackID++) { LIBMTP_track_t *trackinfo; trackinfo = LIBMTP_Get_Trackmetadata(DeviceMgr.device, tmpplaylist->tracks[trackID]); if (trackinfo != NULL) { playlist_track_count++; gtk_list_store_append(GTK_LIST_STORE(playlist_PL_List), &rowIter); tmp_string = g_strdup_printf("%d:%.2d", (int) ((trackinfo->duration / 1000) / 60), (int) ((trackinfo->duration / 1000) % 60)); gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &rowIter, COL_PL_ORDER_NUM, playlist_track_count, COL_PL_ARTIST, trackinfo->artist, COL_PL_ALBUM, trackinfo->album, COL_PL_TRACKID, trackinfo->item_id, COL_PL_TRACKNAME, trackinfo->title, COL_PL_TRACKDURATION, tmp_string, -1); g_free(tmp_string); tmp_string = NULL; LIBMTP_destroy_track_t(trackinfo); } else { LIBMTP_Dump_Errorstack(DeviceMgr.device); LIBMTP_Clear_Errorstack(DeviceMgr.device); } } } // ************************************************************************************************ /** * Display the New Playlist Dialog box. * @return The name of the new playlist. */ gchar* displayPlaylistNewDialog(void) { GtkWidget *dialog, *hbox, *label, *textbox; gchar* textfield; dialog = gtk_dialog_new_with_buttons(_("New Playlist"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif label = gtk_label_new(_("Playlist Name:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); textbox = gtk_entry_new(); gtk_widget_show(textbox); gtk_entry_set_max_length(GTK_ENTRY(textbox), 64); gtk_entry_set_has_frame(GTK_ENTRY(textbox), TRUE); gtk_entry_set_activates_default(GTK_ENTRY(textbox), TRUE); gtk_container_add(GTK_CONTAINER(hbox), textbox); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { textfield = g_strdup(gtk_entry_get_text(GTK_ENTRY(textbox))); if (strlen(textfield) == 0) { // We have an emtpy string. gtk_widget_destroy(dialog); return NULL; } else { gtk_widget_destroy(dialog); return textfield; } } else { gtk_widget_destroy(dialog); return NULL; } } // ************************************************************************************************ /** * Get the list of selected tracks in the playlist editor. * @return */ GList* playlist_PL_ListGetSelection() { GList *selectedFiles, *ptr; GtkTreeRowReference *ref; GtkTreeModel *model; // Lets clear up the old list. g_list_free(playlist_Selection_PL_RowReferences); playlist_Selection_PL_RowReferences = NULL; if (gtk_tree_selection_count_selected_rows(playlist_PL_Selection) == 0) { // We have no rows. return NULL; } // So now we must convert each selection to a row reference and store it in a new GList variable // which we will return below. model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview_Playlist_Files)); selectedFiles = gtk_tree_selection_get_selected_rows(playlist_PL_Selection, &model); ptr = selectedFiles; while (ptr != NULL) { ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(playlist_PL_List), (GtkTreePath*) ptr->data); playlist_Selection_PL_RowReferences = g_list_append(playlist_Selection_PL_RowReferences, gtk_tree_row_reference_copy(ref)); gtk_tree_row_reference_free(ref); ptr = ptr->next; } g_list_foreach(selectedFiles, (GFunc) gtk_tree_path_free, NULL); g_list_free(selectedFiles); return playlist_Selection_PL_RowReferences; } // ************************************************************************************************ /** * Clear the selection of tracks in the playlist. * @return */ gboolean playlist_PL_ListClearSelection() { if (playlist_PL_Selection != NULL) gtk_tree_selection_unselect_all(playlist_PL_Selection); return TRUE; } // ************************************************************************************************ /** * Remove the selected tracks from the playlist. * @param List * @return */ gboolean playlist_PL_ListRemove(GList *List) { GtkTreeIter iter; gint tracknumber = 1; playlist_PL_ListClearSelection(); g_list_foreach(List, (GFunc) __playlist_PL_Remove, NULL); // Now reorder all tracks in this playlist. if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &iter, COL_PL_ORDER_NUM, tracknumber, -1); tracknumber++; while (gtk_tree_model_iter_next(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &iter, COL_PL_ORDER_NUM, tracknumber, -1); tracknumber++; } } return TRUE; } // ************************************************************************************************ /** * Remove the track from the current playlist. * @param Row */ void __playlist_PL_Remove(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist_PL_List), &iter, path); // We have our Iter now. gtk_list_store_remove(GTK_LIST_STORE(playlist_PL_List), &iter); playlist_track_count--; } // ************************************************************************************************ /** * Get the selection of tracks in the current playlist. * @return */ GList* playlist_TrackList_GetSelection() { GList *selectedFiles, *ptr; GtkTreeRowReference *ref; GtkTreeModel *model; // Lets clear up the old list. g_list_free(playlist_Selection_TrackRowReferences); playlist_Selection_TrackRowReferences = NULL; if (gtk_tree_selection_count_selected_rows(playlist_TrackSelection) == 0) { // We have no rows. return NULL; } // So now we must convert each selection to a row reference and store it in a new GList variable // which we will return below. model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview_Avail_Files)); selectedFiles = gtk_tree_selection_get_selected_rows(playlist_TrackSelection, &model); ptr = selectedFiles; while (ptr != NULL) { ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(playlist_TrackList), (GtkTreePath*) ptr->data); playlist_Selection_TrackRowReferences = g_list_append(playlist_Selection_TrackRowReferences, gtk_tree_row_reference_copy(ref)); gtk_tree_row_reference_free(ref); ptr = ptr->next; } g_list_foreach(selectedFiles, (GFunc) gtk_tree_path_free, NULL); g_list_free(selectedFiles); return playlist_Selection_TrackRowReferences; } // ************************************************************************************************ /** * Add the list of tracks to the selected playlist. * @param List * @return */ gboolean playlist_TrackList_Add(GList *List) { g_list_foreach(List, (GFunc) __playlist_TrackList_Add, NULL); return TRUE; } // ************************************************************************************************ /** * Add the individual track to the playlist. * @param Row */ void __playlist_TrackList_Add(GtkTreeRowReference *Row) { GtkTreePath *path = NULL; GtkTreeIter iter; GtkTreeIter PL_rowIter; gchar* artist = NULL; gchar* album = NULL; gchar* title = NULL; gint item_id = 0; gchar * duration = NULL; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist_TrackList), &iter, path); // We have our Iter now, so get the required information from the track treeview. gtk_tree_model_get(GTK_TREE_MODEL(playlist_TrackList), &iter, COL_ARTIST, &artist, COL_ALBUM, &album, COL_TRACKID, &item_id, COL_TRACKNAME, &title, COL_TRACKDURATION, & duration, -1); // Now store our information in the playlist treeview. playlist_track_count++; gtk_list_store_append(GTK_LIST_STORE(playlist_PL_List), &PL_rowIter); gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &PL_rowIter, COL_PL_ORDER_NUM, playlist_track_count, COL_PL_ARTIST, artist, COL_PL_ALBUM, album, COL_PL_TRACKID, item_id, COL_PL_TRACKNAME, title, COL_PL_TRACKDURATION, duration, -1); //Need to free our string values g_free(artist); g_free(album); g_free(title); g_free(duration); } // ************************************************************************************************ /** * Reorder the tracks within the playlist. * @param direction * @return */ gboolean playlist_move_files(gint direction) { GList * playlist_files = NULL; GtkTreeIter iter; gint tracknumber = 1; // Get our files... playlist_files = playlist_PL_ListGetSelection(); if (playlist_files == NULL) return FALSE; // If we are moving files down we need to reverse the rows references... if (direction == 1) { playlist_files = g_list_reverse(playlist_files); g_list_foreach(playlist_files, (GFunc) __playlist_move_files_down, NULL); } else { g_list_foreach(playlist_files, (GFunc) __playlist_move_files_up, NULL); } // Now reorder all tracks in this playlist. if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &iter, COL_PL_ORDER_NUM, tracknumber, -1); tracknumber++; while (gtk_tree_model_iter_next(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_list_store_set(GTK_LIST_STORE(playlist_PL_List), &iter, COL_PL_ORDER_NUM, tracknumber, -1); tracknumber++; } } return TRUE; } // ************************************************************************************************ /** * Move the selected track up in the playlist. * @param Row */ void __playlist_move_files_up(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; GtkTreeIter iter2; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist_PL_List), &iter, path); // We have our Iter now. // Now get it's prev path and turn it into a iter if (gtk_tree_path_prev(path) == TRUE) { // we have a previous entry... gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist_PL_List), &iter2, path); gtk_list_store_swap(GTK_LIST_STORE(playlist_PL_List), &iter, &iter2); } } // ************************************************************************************************ /** * Move the selected track down in the playlist. * @param Row */ void __playlist_move_files_down(GtkTreeRowReference *Row) { GtkTreePath *path; GtkTreeIter iter; GtkTreeIter iter2; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist_PL_List), &iter, path); // We have our Iter now. iter2 = iter; if (gtk_tree_model_iter_next(GTK_TREE_MODEL(playlist_PL_List), &iter2) == TRUE) { // we have something to swap with... gtk_list_store_swap(GTK_LIST_STORE(playlist_PL_List), &iter, &iter2); } } // ************************************************************************************************ /** * Save the current selected playlist to the device. * @param PlayListID */ void playlist_SavePlaylist(gint PlayListID) { LIBMTP_playlist_t* tmpplaylist = devicePlayLists; gint tmpplaylistID = PlayListID; gint item_id = 0; GtkTreeIter iter; uint32_t *tmp = NULL; if (PlayListID > 0) { while (tmpplaylistID--) if (tmpplaylist->next != NULL) tmpplaylist = tmpplaylist->next; } // tmpplaylist points to our playlist; // So all we need to do is - update our current structure with the new details if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_tree_model_get(GTK_TREE_MODEL(playlist_PL_List), &iter, COL_PL_TRACKID, &item_id, -1); tmpplaylist->no_tracks = 1; // item_id = our track num... so append to tmpplaylist->tracks if ((tmp = g_realloc(tmpplaylist->tracks, sizeof (uint32_t) * (tmpplaylist->no_tracks))) == NULL) { g_fprintf(stderr, _("realloc in savePlayList failed\n")); displayError(_("Updating playlist failed? 'realloc in savePlayList'\n")); return; } tmpplaylist->tracks = tmp; tmpplaylist->tracks[(tmpplaylist->no_tracks - 1)] = item_id; //tmpplaylist->no_tracks++; while (gtk_tree_model_iter_next(GTK_TREE_MODEL(playlist_PL_List), &iter)) { gtk_tree_model_get(GTK_TREE_MODEL(playlist_PL_List), &iter, COL_PL_TRACKID, &item_id, -1); tmpplaylist->no_tracks++; // item_id = our track num... so append to tmpplaylist->tracks if ((tmp = g_realloc(tmpplaylist->tracks, sizeof (uint32_t) * (tmpplaylist->no_tracks))) == NULL) { g_fprintf(stderr, _("realloc in savePlayList failed\n")); displayError(_("Updating playlist failed? 'realloc in savePlayList'\n")); return; } tmpplaylist->tracks = tmp; tmpplaylist->tracks[(tmpplaylist->no_tracks - 1)] = item_id; //tmpplaylist->no_tracks++; } } // get libmtp to save it. playlistUpdate(tmpplaylist); // Update our own metadata. devicePlayLists = getPlaylists(); } // ************************************************************************************************ /** * Creates the Format Device Dialog box * @return Widget of completed dialog box. */ GtkWidget* create_windowFormat(void) { GtkWidget* windowFormat; GtkWidget* label1; GtkWidget* vbox1; windowFormat = gtk_window_new(GTK_WINDOW_TOPLEVEL); gchar * winTitle; winTitle = g_strconcat(PACKAGE_TITLE, NULL); gtk_window_set_title(GTK_WINDOW(windowFormat), winTitle); gtk_window_set_position(GTK_WINDOW(windowFormat), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_modal(GTK_WINDOW(windowFormat), TRUE); //gtk_window_set_resizable(GTK_WINDOW(window1), FALSE); gtk_window_set_transient_for(GTK_WINDOW(windowFormat), GTK_WINDOW(windowMain)); gtk_window_set_destroy_with_parent(GTK_WINDOW(windowFormat), TRUE); gtk_window_set_type_hint(GTK_WINDOW(windowFormat), GDK_WINDOW_TYPE_HINT_DIALOG); gtk_window_set_default_size(GTK_WINDOW(windowFormat), 200, 60); g_free(winTitle); vbox1 = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox1); gtk_container_add(GTK_CONTAINER(windowFormat), vbox1); gtk_container_set_border_width(GTK_CONTAINER(vbox1), 10); gtk_box_set_spacing(GTK_BOX(vbox1), 5); label1 = gtk_label_new("Formatting..."); gtk_widget_show(label1); gtk_box_pack_start(GTK_BOX(vbox1), label1, TRUE, TRUE, 0); gtk_misc_set_padding(GTK_MISC(label1), 0, 5); gtk_misc_set_alignment(GTK_MISC(label1), 0, 0); formatDialog_progressBar1 = gtk_progress_bar_new(); gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(formatDialog_progressBar1), 0.05); gtk_widget_show(formatDialog_progressBar1); gtk_box_pack_start(GTK_BOX(vbox1), formatDialog_progressBar1, TRUE, TRUE, 0); return windowFormat; } // ************************************************************************************************ /** * Displays the playlist selection dialog used to auto add track to playlist option. * @return The playlist MTP Object ID of the selected playlist, or GMTP_NO_PLAYLIST if none selected. */ int32_t displayAddTrackPlaylistDialog(gboolean showNew /* = TRUE */) { GtkWidget *dialog, *hbox, *label, *buttonNewPlaylist; LIBMTP_playlist_t* tmpplaylist = NULL; gint selectedPlaylist = 0; #if GMTP_USE_GTK2 GtkTooltips *tooltips; tooltips = gtk_tooltips_new(); #endif dialog = gtk_dialog_new_with_buttons(_("Playlists"), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); hbox = gtk_hbox_new(FALSE, 5); gtk_widget_show(hbox); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox); #endif // Add in the label label = gtk_label_new(_("Playlist Name:")); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(hbox), label); gtk_misc_set_padding(GTK_MISC(label), 5, 0); // Add in the combobox #if GMTP_USE_GTK2 combobox_AddTrackPlaylist = gtk_combo_box_new_text(); #else combobox_AddTrackPlaylist = gtk_combo_box_text_new(); #endif gtk_widget_show(combobox_AddTrackPlaylist); gtk_box_pack_start(GTK_BOX(hbox), combobox_AddTrackPlaylist, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(combobox_AddTrackPlaylist), 5); // Add in the new playlist button. buttonNewPlaylist = gtk_button_new_from_stock(GTK_STOCK_ADD); if (showNew == TRUE) { gtk_widget_show(buttonNewPlaylist); gtk_container_add(GTK_CONTAINER(hbox), buttonNewPlaylist); gtk_container_set_border_width(GTK_CONTAINER(buttonNewPlaylist), 5); } #if GMTP_USE_GTK2 gtk_tooltips_set_tip(tooltips, buttonNewPlaylist, _("Add New Playlist"), NULL); #else gtk_widget_set_tooltip_text(buttonNewPlaylist, _("Add New Playlist")); #endif // Assign the callback for the new playlist button. g_signal_connect((gpointer) buttonNewPlaylist, "clicked", G_CALLBACK(on_TrackPlaylist_NewPlaylistButton_activate), NULL); // Populate the combobox with the current playlists. // We need to remove all entries in the combo box before starting. // This is a little bit of a hack - but does work. gtk_list_store_clear(GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox_AddTrackPlaylist)))); if (devicePlayLists != NULL) { // Populate the playlist dropdown box; //comboboxentry_playlist; tmpplaylist = devicePlayLists; while (tmpplaylist != NULL) { if (tmpplaylist->storage_id == DeviceMgr.devicestorage->id) { #if GMTP_USE_GTK2 gtk_combo_box_append_text(GTK_COMBO_BOX(combobox_AddTrackPlaylist), g_strdup(tmpplaylist->name)); #else gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox_AddTrackPlaylist), g_strdup(tmpplaylist->name)); #endif } tmpplaylist = tmpplaylist->next; } } if (devicePlayLists != NULL) { // Set our playlist to the first one. gtk_combo_box_set_active(GTK_COMBO_BOX(combobox_AddTrackPlaylist), 0); } gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { // Get our playlist ID. selectedPlaylist = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox_AddTrackPlaylist)); // Now cycle through the playlists to get the correct one. tmpplaylist = devicePlayLists; if (selectedPlaylist > 0) { while (selectedPlaylist--) if (tmpplaylist->next != NULL) tmpplaylist = tmpplaylist->next; } gtk_widget_destroy(dialog); return tmpplaylist->playlist_id; } else { gtk_widget_destroy(dialog); return GMTP_NO_PLAYLIST; } } // ************************************************************************************************ /** * Set the title for the main window. * @param foldername - The foldername to be displayed in the application title bar. */ void setWindowTitle(gchar *foldername) { gchar *winTitle; if (foldername == NULL) { winTitle = g_strconcat(PACKAGE_TITLE, NULL); } else { winTitle = g_strconcat(foldername, " - ", PACKAGE_TITLE, NULL); } gtk_window_set_title(GTK_WINDOW(windowMain), (winTitle)); g_free(winTitle); } // ************************************************************************************************ /** * Destroys a file listing object. * @param file - pointer to the FileListStruc object. */ void g_free_search(FileListStruc *file) { if (file != NULL) { if (file->filename != NULL) { g_free(file->filename); } if (file->location != NULL) { g_free(file->location); } } g_free(file); } // ************************************************************************************************ /** * Add a list of file to the nominated playlist. * @param List * @param PlaylistID * @return */ gboolean fileListAddToPlaylist(GList *List, uint32_t PlaylistID) { LIBMTP_playlist_t *playlist = NULL; LIBMTP_playlist_t *node = NULL; node = devicePlayLists; while (node != NULL) { if (node->playlist_id == PlaylistID) { playlist = node; node = NULL; } else { node = node->next; } } if (playlist != NULL) { g_list_foreach(List, (GFunc) __fileAddToPlaylist, (gpointer) & playlist); } return TRUE; } // ************************************************************************************************ /** * Remove a list of files to the nominated playlist. * @param List * @param PlaylistID * @return */ gboolean fileListRemoveFromPlaylist(GList *List, uint32_t PlaylistID) { LIBMTP_playlist_t *playlist = NULL; LIBMTP_playlist_t *node = NULL; node = devicePlayLists; while (node != NULL) { if (node->playlist_id == PlaylistID) { playlist = node; node = NULL; } else { node = node->next; } } if (playlist != NULL) { g_list_foreach(List, (GFunc) __fileRemoveFromPlaylist, (gpointer) & playlist); } return TRUE; } // ************************************************************************************************ /** * Helper to add a single row to playlist. * @param Row * @param playlist */ void __fileAddToPlaylist(GtkTreeRowReference *Row, LIBMTP_playlist_t **playlist) { GtkTreePath *path; GtkTreeIter iter; uint32_t objectID; gboolean isFolder; LIBMTP_track_t *tracks = deviceTracks; LIBMTP_track_t *node = NULL; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { // Now add the file to the playlist. // We need the playlist pointer, and the **track** pointer; while (tracks != NULL) { if (tracks->item_id == objectID) { node = tracks; tracks = NULL; } else { tracks = tracks->next; } } if (node != NULL) { playlistAddTrack(*(playlist), node); } } } // ************************************************************************************************ /** * Helper to remove a single row from a playlist. * @param Row * @param playlist */ void __fileRemoveFromPlaylist(GtkTreeRowReference *Row, LIBMTP_playlist_t **playlist) { GtkTreePath *path; GtkTreeIter iter; uint32_t objectID; gboolean isFolder; LIBMTP_track_t *tracks = deviceTracks; LIBMTP_track_t *node = NULL; // convert the referenece to a path and retrieve the iterator; path = gtk_tree_row_reference_get_path(Row); gtk_tree_model_get_iter(GTK_TREE_MODEL(fileList), &iter, path); // We have our Iter now. gtk_tree_model_get(GTK_TREE_MODEL(fileList), &iter, COL_ISFOLDER, &isFolder, COL_FILEID, &objectID, -1); if (isFolder == FALSE) { // Now add the file to the playlist. // We need the playlist pointer, and the **track** pointer; while (tracks != NULL) { if (tracks->item_id == objectID) { node = tracks; tracks = NULL; } else { tracks = tracks->next; } } if (node != NULL) { playlistRemoveTrack(*(playlist), node, MTP_PLAYLIST_FIRST_INSTANCE); } } } // ************************************************************************************************ /** * Displays a dialog box with all the device folders in it, and prompts the user to select one of * the folders. * @return The object ID of the selected folder. */ int64_t getTargetFolderLocation(void) { GtkWidget *dialog; GtkWidget *treeviewFoldersDialog; GtkTreeStore *folderListDialog; GtkTreeSelection *folderSelectionDialog; GtkWidget *scrolledwindowFoldersDialog; GtkTreeModel *folderListModelDialog; GtkTreeModel *sortmodel; GtkTreeIter iter; GtkTreeIter childiter; uint32_t objectID = 0; dialog = gtk_dialog_new_with_buttons(_("Move To..."), GTK_WINDOW(windowMain), (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE); gtk_window_set_default_size(GTK_WINDOW(dialog), 240, 400); // Actual folder list. scrolledwindowFoldersDialog = gtk_scrolled_window_new(NULL, NULL); gtk_widget_show(scrolledwindowFoldersDialog); #if GMTP_USE_GTK2 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), scrolledwindowFoldersDialog); #else gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), scrolledwindowFoldersDialog); gtk_widget_set_vexpand(scrolledwindowFoldersDialog, TRUE); #endif gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindowFoldersDialog), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); treeviewFoldersDialog = gtk_tree_view_new(); gtk_widget_show(treeviewFoldersDialog); gtk_container_add(GTK_CONTAINER(scrolledwindowFoldersDialog), treeviewFoldersDialog); gtk_container_set_border_width(GTK_CONTAINER(treeviewFoldersDialog), 5); folderSelectionDialog = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeviewFoldersDialog)); gtk_tree_selection_set_mode(folderSelectionDialog, GTK_SELECTION_SINGLE); folderListDialog = gtk_tree_store_new(NUM_FOL_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, GDK_TYPE_PIXBUF); setupFolderList(GTK_TREE_VIEW(treeviewFoldersDialog)); folderListModelDialog = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(folderListDialog)); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(folderListModelDialog), COL_FOL_NAME_HIDDEN, GTK_SORT_ASCENDING); gtk_tree_view_set_model(GTK_TREE_VIEW(treeviewFoldersDialog), GTK_TREE_MODEL(folderListModelDialog)); folderListAddDialog(deviceFolders, NULL, folderListDialog); gtk_tree_view_expand_all(GTK_TREE_VIEW(treeviewFoldersDialog)); g_object_unref(folderListDialog); gint result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_OK) { // Get our selected row. sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeviewFoldersDialog)); if (gtk_tree_selection_get_selected(folderSelectionDialog, &sortmodel, &iter)) { gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(sortmodel), &childiter, &iter); gtk_tree_model_get(GTK_TREE_MODEL(folderListDialog), &childiter, COL_FOL_ID, &objectID, -1); gtk_widget_destroy(dialog); return objectID; } } gtk_widget_destroy(dialog); return -1; } // ************************************************************************************************ /** * Add folders to the folder list in dialog window. */ gboolean folderListAddDialog(LIBMTP_folder_t *folders, GtkTreeIter *parent, GtkTreeStore *fl) { GtkTreeIter rowIter; GdkPixbuf *image = NULL; if (parent == NULL) { // Add in the root node. image = gdk_pixbuf_new_from_file(file_folder_png, NULL); // Now add in the row information. gtk_tree_store_append(GTK_TREE_STORE(fl), &rowIter, parent); gtk_tree_store_set(GTK_TREE_STORE(fl), &rowIter, //COL_FOL_NAME, folders->name, COL_FOL_NAME_HIDDEN, "/", COL_FOL_ID, 0, COL_FOL_ICON, image, -1); // Indicate we are done with this image. g_object_unref(image); folderListAddDialog(folders, &rowIter, fl); return TRUE; } while (folders != NULL) { // Only add in folder if it's in the current storage device. if (folders->storage_id == DeviceMgr.devicestorage->id) { image = gdk_pixbuf_new_from_file(file_folder_png, NULL); // Now add in the row information. gtk_tree_store_append(GTK_TREE_STORE(fl), &rowIter, parent); gtk_tree_store_set(GTK_TREE_STORE(fl), &rowIter, //COL_FOL_NAME, folders->name, COL_FOL_NAME_HIDDEN, folders->name, COL_FOL_ID, folders->folder_id, COL_FOL_ICON, image, -1); // Indicate we are done with this image. g_object_unref(image); if (folders->child != NULL) { // Call our child. folderListAddDialog(folders->child, &rowIter, fl); } } folders = folders->sibling; } return TRUE; } ***************************************** /** * Set the title for the main window. * @param foldername - The foldername to be displayed in the application title bar. */ void setWindowTitle(gchar *foldername) { gchar *winTitle; if (foldername == NULL) { winTitle = g_strconcat(PACKAGE_TITLE, NULL); } else { winTitle = g_strconcat(foldername, " - ", PACKAGE_TITgMTP/src/metatag_info.h000064401651440000012000000054311176535257100157440ustar00darranstaff00003030200010/* * * File: metatag_info.h * * Copyright (C) 2009-2012 Darran Kartaschew * * This file is part of the gMTP package. * * gMTP is free software; you can redistribute it and/or modify * it under the terms of the BSD License as included within the * file 'COPYING' located in the root directory * */ #ifndef _METATAG_INFO_H #define _METATAG_INFO_H #ifdef __cplusplus extern "C" { #endif typedef struct { uint32_t bitrate; uint32_t duration; // in milliseconds uint16_t VBR; // 0 = unused, 1 = constant, 2 = VBR, 3 = free uint16_t channels; // 0 = Unknown, 1 = mono, 2 = stereo. } MP3_Info; typedef struct { uint16_t header_sync; uint16_t version; uint16_t layer; uint16_t crc; uint16_t bitrate; uint16_t samplerate; uint16_t padding; uint16_t private_bit; uint16_t channel_mode; uint16_t mode_extension; uint16_t copyright; uint16_t original; uint16_t emphasis; } MP3_header; typedef struct { uint32_t f1; uint16_t f2; uint16_t f3; uint16_t f4; uint8_t f5_1; uint8_t f5_2; uint8_t f5_3; uint8_t f5_4; uint8_t f5_5; uint8_t f5_6; } GUID; // We only include our ASF header objects we want, not all of them static const GUID ASF_header = { 0x75B22630, 0x668E, 0x11CF, 0xA6D9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }; static const GUID ASF_comment_header = { 0x75B22633, 0x668E, 0x11CF, 0xD9A6, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }; static const GUID ASF_extended_content_header = { 0xD2D0A440, 0xE307, 0x11D2, 0xF097, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 }; static const GUID ASF_File_Properties_header = { 0x8CABDCA1, 0xA947, 0x11CF, 0xE48E, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }; static const GUID ASF_Stream_header = { 0xB7DC0791, 0xA9B7, 0x11CF, 0xE68E, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }; static const GUID ASF_Audio_Media_header = { 0xF8699E40, 0x5B4D, 0x11CF, 0xFDA8, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B }; //gchar * ID3_getFrameText(struct id3_tag *tag, char *frame_name); //gchar * FLAC_getFieldText(const FLAC__StreamMetadata *tags, const char *name); //gchar * OGG_getFieldText(const vorbis_comment *comments, const char *name); void get_mp3_info(gchar *filename, MP3_Info *mp3_struct); void get_id3_tags(gchar *filename, LIBMTP_track_t *trackinformation); void get_ogg_tags(gchar *filename, LIBMTP_track_t *trackinformation); void get_flac_tags(gchar *filename, LIBMTP_track_t *trackinformation); void get_asf_tags(gchar *filename, LIBMTP_track_t *trackinformation); #ifdef __cplusplus } #endif #endif /* _METATAG_INFO_H */ gMTP/NEWS000064401651440000012000000000001151405040600130110ustar00darranstaff00003030200010gMTP/po/000075501651440000012000000000001205070637600127545ustar00darranstaff00003030200010gMTP/po/fr.po000064401651440000012000000607721205062516200137300ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr "\nErreur de récupération des fichiers à partir du périphérique MTP.\n" # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr "\nImpossible de supprimer le fichier %s\n" # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr " %s %s : (%04x:%04x) @ bus %d, dev %d" # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr " : %d Mo (gratuit) / %d Mo (total)" # File:interface.c, line:2241 msgid " Playlists" msgstr " Liste d'écoute" # File:interface.c, line:982 msgid " Preferences" msgstr " Préférences" # File:interface.c, line:1226 msgid " Properties" msgstr " Propriétés" # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr "%d Mo (libre) / %d Mo (total)" # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr "%lluKo de %lluKo (%d%%)" # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr "%s - %lluKo de %lluKo (%d%%)" # File:mtp.c, line:461 msgid "" msgstr "" # File:interface.c, line:1665 msgid "" msgstr "" # File:interface.c, line:1346 msgid "Battery Level:" msgstr "Niveau de la batterie:" # File:interface.c, line:1501 msgid "Bus Location:" msgstr "Situation Bus:" # File:interface.c, line:1492 msgid "Device Number:" msgstr "Numéro du périphérique:" # File:interface.c, line:1328 msgid "Device Version:" msgstr "Version de l'appareil:" # File:interface.c, line:1009 msgid "Device" msgstr "Périphérique" # File:interface.c, line:1041 msgid "File Operations" msgstr "Opérations sur les fichiers" # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr "Chemin de fichiers sur PC" # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr "Propriétés du périphérique MTP" # File:interface.c, line:1337 msgid "Manufacturer:" msgstr "Fabricant:" # File:interface.c, line:1310 msgid "Model Number:" msgstr "Numéro de modèle:" # File:interface.c, line:1259 msgid "Name:" msgstr "Nom:" # File:interface.c, line:1483 msgid "Product ID:" msgstr "Identifiant du produit:" # File:interface.c, line:1465 msgid "Product:" msgstr "Produit:" # File:interface.c, line:1551 msgid "Raw Device Information" msgstr "Information brute sur le périphérique" # File:interface.c, line:1386 msgid "Secure Time:" msgstr "Temps sécuritaire:" # File:interface.c, line:1319 msgid "Serial Number:" msgstr "Numéro de série:" # File:interface.c, line:1355 msgid "Storage:" msgstr "Stockage:" # File:interface.c, line:1371 msgid "Supported Formats:" msgstr "Formats supportés:" # File:interface.c, line:1395 msgid "Sync Partner:" msgstr "Partenaire de synchronisation:" # File:interface.c, line:1474 msgid "Vendor ID:" msgstr "Identifiant vendeur:" # File:interface.c, line:1456 msgid "Vendor:" msgstr "Vendeur:" # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr "Copyright 2009-2011, Darran Kartaschew\nParu sous la Licence BSD\n" # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr "Un simple lecteur MP3 pour Solaris 10\net d'autres UNIX / systèmes de type UNIX\n" # File:interface.c, line:1737 msgid "About gMTP" msgstr "A propos de gMTP" # File:interface.c, line:308 msgid "Add" msgstr "Ajouter" # File:interface.c, line:249 msgid "Add Album Art" msgstr "Ajouter une pochette d'album" # File:interface.c, line:2358 msgid "Add File" msgstr "Ajouter un fichier" # File:interface.c, line:179 msgid "Add Files" msgstr "Ajouter des fichiers" # File:interface.c, line:311 msgid "Add Files to your device." msgstr "Ajouter des fichiers à votre périphérique." # File:interface.c, line:2273 msgid "Add New Playlist" msgstr "Ajouter une nouvelle liste d'écoute" # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr "Ajouter une variété de fichiers sur votre périphérique dans le dossier courant." # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr "Ajouter et modifier des listes d'écoute." # File:interface.c, line:2344 msgid "Add file to playlist" msgstr "Ajouter un fichier à la liste d'écoute" # File:interface.c, line:2522 msgid "Album" msgstr "Album" # File:interface.c, line:334 msgid "Album Art" msgstr "Pochette d'album" # File:interface.c, line:2038 msgid "Album:" msgstr "Album:" # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr "Toujours afficher le chemin de téléchargement?" # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr "Êtes-vous sûr de vouloir supprimer ces fichiers?" # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr "Êtes-vous sûr de vouloir supprimer ce dossier (et tout son contenu)?" # File:interface.c, line:2512 msgid "Artist" msgstr "Artiste" # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr "Tentative de connexion au périphérique lors du démarrage" # File:interface.c, line:241 msgid "Change Device Name" msgstr "Changer le nom du périphérique" # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr "Confirmer la suppression" # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr "Confirmer la suppression du fichier/dossier" # File:callbacks.c, line:165 msgid "Connect" msgstr "Connecter" # File:callbacks.c, line:170 msgid "Connect Device" msgstr "Connecter le périphérique" # File:interface.c, line:1897 msgid "Connect to which device?" msgstr "Connecter à quel périphérique?" # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr "Connecter à quel périphérique de stockage?" # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr "Connexion/déconnexion à votre périphérique." # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr "Connecté à %s (%s) - %d Mo d'espace libre" # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr "Connecté à %s - %d Mo d'espace libre" # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr "Impossible de créer l'objet dans la liste d'écoute\n" # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr "Impossible de supprimer le dossier %s (%x)\n" # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr "Impossible d'ouvrir le fichier image %s\n" # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr "Impossible d'envoyer des pochettes d'album\n" # File:interface.c, line:199 msgid "Create Folder" msgstr "Créer un dossier" # File:interface.c, line:2259 msgid "Current Playlist: " msgstr "Liste d'écoute actuelle: " # File:interface.c, line:2309 msgid "Del" msgstr "Supprimer" # File:interface.c, line:2379 msgid "Del File" msgstr "Supprimer le fichier" # File:interface.c, line:315 msgid "Delete" msgstr "Supprimer" # File:interface.c, line:185 msgid "Delete Files" msgstr "Supprimer les fichiers" # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr "Supprimer les fichiers/dossiers de votre périphérique." # File:interface.c, line:206 msgid "Delete Folder" msgstr "Supprimer le dossier" # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr "Échec de la suppression de la liste d'écoute?\n" # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr "Détecté: une erreur d'allocation de mémoire a été rencontrée. \n" # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr "Détecté: Aucun périphérique brut trouvé.\n" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr "Détecté: Il ya eu une erreur de connexion.\n" # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr "Détecté: Impossible d'ouvrir le périphérique brut?\n" # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr "Pistes audio du périphérique" # File:interface.c, line:1861 msgid "Device Name:" msgstr "Nom du périphérique:" # File:interface.c, line:222 msgid "Device Properties" msgstr "Propriétés du périphérique" # File:interface.c, line:1908 msgid "Device:" msgstr "Périphérique:" # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr "DevicePropeties: Comment ai-je été appelé?\n" # File:callbacks.c, line:146 msgid "Disconnect" msgstr "Déconnecter" # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr "Déconnecter le périphérique" # File:interface.c, line:322 msgid "Download" msgstr "Télécharger" # File:interface.c, line:191 msgid "Download Files" msgstr "Télécharger des fichiers" # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr "Télécharger des fichiers de votre périphérique à votre PC hôte." # File:interface.c, line:1071 msgid "Download Path:" msgstr "Chemin de téléchargement:" # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the preferences dialog." msgstr "Télécharger des fichiers de votre périphérique à votre PC. Le chemin de téléchargement par défaut est défini dans la boîte de dialogue préférences." # File:interface.c, line:2550 msgid "Duration" msgstr "Durée" # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr "Échec d'allocation de mémoire dans albumAddArt (): ERREUR\n" # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr "Échec d'allocation de mémoire dans albumAddTrackToAlbum (): ERREUR\n" # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr "Éditer la/les liste(s) d'écoute" # File:interface.c, line:1784 msgid "Error" msgstr "Erreur" # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr "Code d'erreur %d d'envoi de la piste au périphérique: %s" # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr "Erreur de création ou de mise à jour de l'album.\n(Cela pourrait être dû au fait que votre périphérique ne supporte pas les albums.)\n" # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr "Erreur de récupération des fichiers du périphérique MTP." # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr "Erreur d'envoi du fichier %s.\n" # File:mtp.c, line:504 msgid "Error sending file:" msgstr "Erreur d'envoi du fichier:" # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr "Erreur en envoyant la piste.\n" # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr "Erreur: Impossible de définir le nom du périphérique à %s\n" # File:mtp.c, line:530 msgid "Failed to delete file" msgstr "Impossible de supprimer le fichier" # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr "Le fichier %s existe déjà dans le dossier cible.\nVoulez-vous:" # File:mtp.c, line:405 msgid "File Upload" msgstr "Exportation de fichier" # File:mtp.c, line:538 msgid "File download" msgstr "Téléchargement de fichier" # File:interface.c, line:604 msgid "FileSize Hidden" msgstr "Cacher la taile du fichier" # File:interface.c, line:568 msgid "Filename" msgstr "Nom de fichier" # File:interface.c, line:2073 msgid "Filename:" msgstr "Nom de fichier:" # File:interface.c, line:1817 msgid "Folder Name:" msgstr "Nom du dossier:" # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr "Échec de la création du dossier:" # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr "Échec de la création du dossier: %s\n" # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr "Je ne sais pas comment supprimer une référence à un dossier parent?\n" # File:interface.c, line:1796 msgid "Information" msgstr "Information" # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr "Descendre le fichier choisi dans la liste d'écoute" # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr "Monter le fichier choisi dans la liste d'écoute" # File:callbacks.c, line:367 msgid "N/A" msgstr "Non applicable" # File:interface.c, line:1805 msgid "New Folder" msgstr "Nouveau dossier" # File:interface.c, line:2714 msgid "New Playlist" msgstr "Nouvelle liste d'écoute" # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr "Aucun album disponible pour définir la pochette d'album. Soit:\n1. Vous n'avez pas de fichier de musique exporté?\n2. Votre périphérique ne supporte pas les albums?\n3. Les applications précédentes utilisées pour exporter des fichiers ne crées pas automatiquement d'albums pour vous ou ne supportent pas les métadonnées pour ces fichiers afin de créer des albums pour vous?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "Aucun périphérique connecté" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "Pas de fichiers/dossiers sélectionnés?" # File:interface.c, line:2566 msgid "Num" msgstr "Nombre" # File:interface.c, line:588 msgid "Object ID" msgstr "Identifiant d'objet" # File:interface.c, line:2008 msgid "Overwrite" msgstr "Écraser" # File:interface.c, line:2009 msgid "Overwrite All" msgstr "Remplacer tous" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr "Supprimer définitivement les fichiers / dossiers de votre périphérique. Note: Les albums sont stockés dans des fichiers *. alb." # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr "Audio des pistes de la liste d'écoute" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Nom de la liste d'écoute:" # File:interface.c, line:341 msgid "Playlists" msgstr "Listes d'écoute" # File:interface.c, line:367 msgid "Preferences" msgstr "Préférences" # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr "Demander pour écraser le fichier s'il existe déjà" # File:interface.c, line:360 msgid "Properties" msgstr "Propriétés" # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr "Question: Confirmer l'écrasement du fichier existant?" # File:interface.c, line:379 msgid "Quit" msgstr "Quitter" # File:interface.c, line:382 msgid "Quit gMTP." msgstr "Quitter gMTP." # File:interface.c, line:348 msgid "Refresh" msgstr "Actualiser" # File:interface.c, line:216 msgid "Refresh Device" msgstr "Actualiser le périphérique" # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr "Actualiser les fichiers/dossiers de la liste d'écoute." # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr "Supprimer la liste d'écoute présentement sélectionnée" # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr "Supprimer le fichier de la liste d'écoute" # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr "Rescan: Comment ai-je été appelé?\n" # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr "Choisir le fichier de la pochette d'album" # File:interface.c, line:624 msgid "Select Files to Add" msgstr "Choisir les fichiers à ajouter" # File:interface.c, line:711 msgid "Select Path to Download" msgstr "Choisir le chemin du téléchargement" # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr "Choisir le chemin du téléchargement sur" # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr "Choisir le chemin d'exportation à partir de" # File:interface.c, line:578 msgid "Size" msgstr "Taille" # File:interface.c, line:2006 msgid "Skip" msgstr "Sauter" # File:interface.c, line:2007 msgid "Skip All" msgstr "Sauter tous" # File:interface.c, line:1968 msgid "Storage Device:" msgstr "Périphérique de stockage:" # File:interface.c, line:2540 msgid "Track" msgstr "Piste" # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %l, freespace = %l\n" msgstr "Impossible d'ajouter %s car l'espace est insuffisant: taille du fichier = %lu, espace libre = %lu\n" # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr "Impossible d'ajouter le fichier car l'espace est insuffisant" # File:mtp.c, line:158 msgid "Unknown" msgstr "Inconnu" # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr "Inconnu : %04x:%04x @ bus %d, dev %d" # File:interface.c, line:1981 msgid "Unknown id: %d, %l MB" msgstr "Identifiant inconnu: %d, %l MB" # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr "Échec de la mise à jour de la liste d'écoute?\n" # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr "Échec de la mise à jour de la liste d'écoute? 'relocalisation dans savePlayList '\n" # File:interface.c, line:1078 msgid "Upload Path:" msgstr "Chemin d'exportation:" # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr "Exporter un fichier JPG et l'assigner comme pochette d'album." # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr "Exporter un fichier image comme pochette d'album." # File:interface.c, line:363 msgid "View Device Properties." msgstr "Voir les propriétés du périphérique." # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr "Voir/Modifier les préférences gMTP." # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr "AVERTISSEMENT: schéma gconf invalide, revenir aux réglages par défaut. S'il vous plaît s'assurer que schéma est chargé dans la base de données gconf.\n" # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr "AVERTISSEMENT: schéma gconf invalide, incapable de sauver! S'il vous plaît s'assurer que le schéma est chargé dans la base de données gconf.\n" # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr "AVERTISSEMENT: gconf_callback_func () a échoué - nous avons eu un rappel pour une clé qui ne nous appartient pas?\n" # File:interface.c, line:274 msgid "_About" msgstr "_A propos de" # File:interface.c, line:234 msgid "_Edit" msgstr "_Modifier" # File:interface.c, line:160 msgid "_File" msgstr "_Fichier" # File:interface.c, line:267 msgid "_Help" msgstr "_Aide" # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr "fichier = ( x / x ) x %" # File:dnd.c, line:83 msgid "file://" msgstr "fichier://" # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr "Échec de la relocalisation dans savePlayList\n" # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr "setDeviceName: Comment ai-je été appelé?\n" msgid "File Type" msgstr "Type de fichier" msgid "Track Name" msgstr "Nom de la piste" msgid "Year" msgstr "Année" msgid "_View" msgstr "_Voir" msgid "File Size" msgstr "Taille du fichier" msgid "Track Number" msgstr "Numéro de piste" msgid "Genre" msgstr "Genre" ########################################### msgid "Save Album Art File" msgstr "Enregistrer le fichier de la pochette d'album" msgid "Couldn't save image file %s\n" msgstr "Impossible d'enregistrer un fichier image %s\n" msgid "Couldn't save image file\n" msgstr "Impossible d'enregistrer un fichier image\n" msgid "Failed to get storage parameters from the device - need to disconnect." msgstr "Impossible d'obtenir les paramètres de stockage du périphérique - besoin de déconnecter." msgid "Couldn't remove album art\n" msgstr "Impossible de supprimer la pochette d'album\n" msgid "Rename File" msgstr "Renommer le fichier" msgid "Rename File/Folder" msgstr "Renommer le fichier/dossier" msgid "File Name:" msgstr "Nom du fichier:" msgid "Upload" msgstr "Exporter" msgid "Remove" msgstr "Retirer" msgid "Format Device" msgstr "Formatage du périphérique" msgid "Are you sure you want to format this device?" msgstr "Êtes-vous sûr de vouloir formater ce périphérique?" msgid "Format Device failed?\n" msgstr "Échec du formatage du périphérique?\n" msgid "Prompt to add New Music track to existing playlist" msgstr "Demander l'ajout de la piste New Music à une liste d'écoute existante" ########################################################### msgid "Import Playlist" msgstr "Importer la liste d'écoute" msgid "Export Playlist" msgstr "Exporter la liste d'écoute" msgid "Select Playlist to Import" msgstr "Sélectionner la liste d'écoute à importer" msgid "The playlist failed to import correctly.\n" msgstr "La liste d'écoute ne s'est pas importée correctement.\n" msgid "Ignore path information when importing playlist" msgstr "Ignorer les informations du chemin lors de l'importation de la liste d'écoute" msgid "Playlist" msgstr "Liste d'écoute" msgid "Couldn't save playlist file\n" msgstr "Impossible d'enregistrer le fichier de la liste d'écoute\n" msgid "Couldn't save playlist file %s\n" msgstr "Impossible d'enregistrer le fichier de la liste d'écoute %s\n" msgid "Couldn't open playlist file\n" msgstr "Impossible d'ouvrir le fichier de la liste d'écoute\n" msgid "Couldn't open playlist file %s\n" msgstr "Impossible d'ouvrir le fichier de la liste d'écoute %s\n" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "Aucune piste trouvée dans la liste d'écoute qui existe sur ce périphérique. N'a pas importé la liste d'écoute.\n" msgid "Playlist imported.\n" msgstr "Listes d'écoute importées.\n" ########################################################### msgid "Find:" msgstr "Trouver:" msgid "Filenames" msgstr "Noms de fichiers" msgid "Track Information" msgstr "Information de la piste" msgid "Please enter search item." msgstr "S'il vous plaît entrez élément recherché." msgid "Search" msgstr "Rechercher" msgid "Location" msgstr "Lieu" msgid "Found %d items" msgstr "Trouvé %d articles" msgid "Found %d item" msgstr "%d objets trouvés" msgid "Searching..." msgstr "Recherche" msgid "Add To Playlist" msgstr "Ajouter à la liste d'écoute" msgid "Remove From Playlist" msgstr "Supprimer de la liste d'écoute" msgid "Columns" msgstr "Colonnes" ########################################################### msgid "Folders" msgstr "Dossiers" msgid "Folder" msgstr "Dossier" msgid "Rename Folder" msgstr "Renommer le dossier" msgid "Move To..." msgstr "Déplacer vers..." msgid "Unable to move the selected folder underneath itself?\n" msgstr "Impossible de déplacer le dossier sélectionné sous lui-même?\n" msgid "Unable to move the selected folder into itself?\n" msgstr "Impossible de déplacer le dossier sélectionné sur lui-même?\n" msgid "Unable to move the selected file?\n" msgstr "Impossible de déplacer le fichier sélectionné?\n" msgid "Unable to move the selected folder?\n" msgstr "Impossible de déplacer le dossier sélectionné?\n" msgid "Select All" msgstr "Sélectionner tout" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "Je ne sais pas comment télécharger un dossier parent de référence?\n" msgid "Suppress Album Errors" msgstr "Supprimer les erreurs de l'album" msgid "Detect: No available Storage found on device?\n" msgstr "Détecter: Non disponible Entreposage trouvant sur le périphérique?\n" msgid "Utilize alternate access method" msgstr "Utiliser la méthode d'accès de substitution" msgid "Disconnected device due to access method change" msgstr "Périphérique déconnecté en raison d'accéder à un changement de méthode" msgid "The move function is disabled when using the alternate access method for your device." msgstr "La fonction de déplacement est désactivée lorsque vous utilisez la méthode d'accès de rechange pour votre appareil." ace.c,gMTP/po/it.po000064401651440000012000000565701205062514600137400ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr "\nErrore durante il recupero di file da un dispositivo MTP.\n" # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr "\nImpossibile eliminare il file %s\n" # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr " %s %s : (%04x:%04x) @ bus %d, dev %d" # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr " : %d MB (libero) / %d MB (totale)" # File:interface.c, line:2241 msgid " Playlists" msgstr " Playlist" # File:interface.c, line:982 msgid " Preferences" msgstr " Preferenze" # File:interface.c, line:1226 msgid " Properties" msgstr " Proprietà" # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr "%d MB (libero) / %d MB (totale)" # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr "%lluKB di %lluKB (%d%%)" # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr "%s - %lluKB di %lluKB (%d%%)" # File:mtp.c, line:461 msgid "" msgstr "" # File:interface.c, line:1665 msgid "" msgstr "" # File:interface.c, line:1346 msgid "Battery Level:" msgstr "Livello della batteria:" # File:interface.c, line:1501 msgid "Bus Location:" msgstr "Posizione del bus:" # File:interface.c, line:1492 msgid "Device Number:" msgstr "Numero del dispositivo:" # File:interface.c, line:1328 msgid "Device Version:" msgstr "Versione del dispositivo:" # File:interface.c, line:1009 msgid "Device" msgstr "Dispositivo" # File:interface.c, line:1041 msgid "File Operations" msgstr "Operazioni su file" # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr "Percorsi del file su PC" # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr "Proprietà del dispositivo MTP" # File:interface.c, line:1337 msgid "Manufacturer:" msgstr "Produttore:" # File:interface.c, line:1310 msgid "Model Number:" msgstr "Numero del modello:" # File:interface.c, line:1259 msgid "Name:" msgstr "Nome:" # File:interface.c, line:1483 msgid "Product ID:" msgstr "ID del prodotto:" # File:interface.c, line:1465 msgid "Product:" msgstr "Prodotto:" # File:interface.c, line:1551 msgid "Raw Device Information" msgstr "Informazioni sui dispositivi raw" # File:interface.c, line:1386 msgid "Secure Time:" msgstr "Sicuro Tempo:" # File:interface.c, line:1319 msgid "Serial Number:" msgstr "Numero di serie:" # File:interface.c, line:1355 msgid "Storage:" msgstr "Memorizzazione:" # File:interface.c, line:1371 msgid "Supported Formats:" msgstr "Formati supportati:" # File:interface.c, line:1395 msgid "Sync Partner:" msgstr "Sincronizzazione partner:" # File:interface.c, line:1474 msgid "Vendor ID:" msgstr "ID venditore:" # File:interface.c, line:1456 msgid "Vendor:" msgstr "Venditore:" # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr "Copyright 2009-2011, Darran Kartaschew\nRilasciato sotto BSD Licence\n" # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr "Un semplice lettore MP3 per Solaris 10\ne altri sistemi UNIX / UNIX simile\n" # File:interface.c, line:1737 msgid "About gMTP" msgstr "Informazioni su gMTP" # File:interface.c, line:308 msgid "Add" msgstr "Aggiungi" # File:interface.c, line:249 msgid "Add Album Art" msgstr "Aggiungi Album Art" # File:interface.c, line:2358 msgid "Add File" msgstr "Aggiungi file" # File:interface.c, line:179 msgid "Add Files" msgstr "Aggiungi file" # File:interface.c, line:311 msgid "Add Files to your device." msgstr "Aggiungi file al dispositivo." # File:interface.c, line:2273 msgid "Add New Playlist" msgstr "Aggiungi nuova playlist" # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr "Aggiungere una varietà di file sul dispositivo nella cartella corrente" # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr "Aggiungere e modificare playlist." # File:interface.c, line:2344 msgid "Add file to playlist" msgstr "Aggiungere file alla playlist" # File:interface.c, line:2522 msgid "Album" msgstr "Album" # File:interface.c, line:334 msgid "Album Art" msgstr "Album Art" # File:interface.c, line:2038 msgid "Album:" msgstr "Album:" # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr "Mostra sempre il percorso di download?" # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr "Sei sicuro di voler eliminare questi file?" # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr "Sei sicuro di voler eliminare questa cartella (e tutti i contenuti)?" # File:interface.c, line:2512 msgid "Artist" msgstr "Artista" # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr "Tentativo di connessione a periferica all'avvio" # File:interface.c, line:241 msgid "Change Device Name" msgstr "Cambia nome dispositivo" # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr "Conferma eliminazione" # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr "Conferma eliminazione file / cartella" # File:callbacks.c, line:165 msgid "Connect" msgstr "Collegare" # File:callbacks.c, line:170 msgid "Connect Device" msgstr "Collegare il dispositivo" # File:interface.c, line:1897 msgid "Connect to which device?" msgstr "A quale dispositivo ci si desidera collegare?" # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr "A quale periferica di archiviazione ci si desidera collegare?" # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr "Connettersi / disconnettersi al dispositivo." # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr "Connesso a %s (%s) - %d MB di spazio libero" # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr "Connesso a %s - %d MB di spazio libero" # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr "Impossibile creare l'oggetto playlist\n" # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr "Impossibile eliminare cartella %s (%x)\n" # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr "Impossibile aprire il file immagine %s\n" # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr "Impossibile inviare le copertine degli album\n" # File:interface.c, line:199 msgid "Create Folder" msgstr "Crea cartella" # File:interface.c, line:2259 msgid "Current Playlist: " msgstr "Playlist attuale: " # File:interface.c, line:2309 msgid "Del" msgstr "Elimina" # File:interface.c, line:2379 msgid "Del File" msgstr "Elimina file" # File:interface.c, line:315 msgid "Delete" msgstr "Elimina" # File:interface.c, line:185 msgid "Delete Files" msgstr "Elimina file" # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr "Eliminare i file / cartelle dal dispositivo." # File:interface.c, line:206 msgid "Delete Folder" msgstr "Elimina cartella" # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr "Eliminazione della playlist fallita?\n" # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr "Detect: È stato rilevato un errore di allocazione della memoria.\n" # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr "Detect: Nessun dispositivo raw trovato.\n" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr "Detect: C'è stato un errore di connessione.\n" # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr "Detect: impossibile aprire il dispositivo raw?\n" # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr "Tracce audio del dispositivo" # File:interface.c, line:1861 msgid "Device Name:" msgstr "Nome del dispositivo:" # File:interface.c, line:222 msgid "Device Properties" msgstr "Proprietà del dispositivo" # File:interface.c, line:1908 msgid "Device:" msgstr "Dispositivo:" # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr "DevicePropeties: Come ho chiamato?\n" # File:callbacks.c, line:146 msgid "Disconnect" msgstr "Scollegare" # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr "Disconnettere il dispositivo" # File:interface.c, line:322 msgid "Download" msgstr "Download" # File:interface.c, line:191 msgid "Download Files" msgstr "Download dei file" # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr "Download di file dal dispositivo al PC host." # File:interface.c, line:1071 msgid "Download Path:" msgstr "Percorso del download:" # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the preferences dialog." msgstr "Scaricare file dal dispositivo al PC. Scarica percorso predefinito è impostato nella finestra di dialogo prefernces." # File:interface.c, line:2550 msgid "Duration" msgstr "Durata" # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr "Allocazione di memoria non riuscita in albumAddArt(): ERRORE\n" # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr "Allocazione di memoria non riuscita in albumAddTrackToAlbum(): ERRORE\n" # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr "Modifica Playlist" # File:interface.c, line:1784 msgid "Error" msgstr "Errore" # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr "Errore codice %d brano inviare al dispositivo: %s" # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr "Errore durante la creazione o l'aggiornamento di album.\n(Ciò potrebbe essere dovuto al fatto che il dispositivo non supporta album.)\n" # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr "Errore durante il recupero di file da un dispositivo MTP." # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr "Errore durante l'invio del file %s.\n" # File:mtp.c, line:504 msgid "Error sending file:" msgstr "Errore nell'invio del file:" # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr "Errore durante l'invio traccia.\n" # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr "Errore: Impossibile impostare il nome del dispositivo per %s\n" # File:mtp.c, line:530 msgid "Failed to delete file" msgstr "Impossibile eliminare il file" # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr "File %s è già presente nella cartella di destinazione.\nVuoi:" # File:mtp.c, line:405 msgid "File Upload" msgstr "Carica file" # File:mtp.c, line:538 msgid "File download" msgstr "Scarica file" # File:interface.c, line:604 msgid "FileSize Hidden" msgstr "Dimensione del file nascosta" # File:interface.c, line:568 msgid "Filename" msgstr "Nome del file" # File:interface.c, line:2073 msgid "Filename:" msgstr "Nome del file:" # File:interface.c, line:1817 msgid "Folder Name:" msgstr "Nome cartella:" # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr "Creazione della cartella non riuscita:" # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr "Creazione della cartella non riuscita: %s\n" # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr "Non so come eliminare il collegamento alla cartella superiore?\n" # File:interface.c, line:1796 msgid "Information" msgstr "Informazioni" # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr "Spostare file selezionato dalla playlist" # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr "Spostare file selezionato in playlist" # File:callbacks.c, line:367 msgid "N/A" msgstr "Non applicabile" # File:interface.c, line:1805 msgid "New Folder" msgstr "Nuova Cartella" # File:interface.c, line:2714 msgid "New Playlist" msgstr "Nuova Playlist" # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr "Nessun album disponibile per impostare Album Art. O:\n1. Non hai i file musicali caricati?\n2. Il dispositivo non supporta Album?\n3. Domande precedenti utilizzato per caricare i file non autocreate album per voi o il supporto di metadati per i file in modo da creare degli album per voi?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "Nessun dispositivo collegato" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "Nessun file / cartella selezionati?" # File:interface.c, line:2566 msgid "Num" msgstr "Numero" # File:interface.c, line:588 msgid "Object ID" msgstr "Object ID" # File:interface.c, line:2008 msgid "Overwrite" msgstr "Sovrascrivi" # File:interface.c, line:2009 msgid "Overwrite All" msgstr "Sovrascrivi tutti" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr "Rimuovere in modo permanente i file / cartelle dal dispositivo. Nota: gli album sono archiviati come file *. alb." # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr "Tracce audio Playlist" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Nome della playlist:" # File:interface.c, line:341 msgid "Playlists" msgstr "Playlist" # File:interface.c, line:367 msgid "Preferences" msgstr "Preferenze" # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr "Avviso se sovrascrivere il file se esiste già" # File:interface.c, line:360 msgid "Properties" msgstr "Proprietà" # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr "Domanda: Conferma la sovrascrittura di file esistente?" # File:interface.c, line:379 msgid "Quit" msgstr "Esci" # File:interface.c, line:382 msgid "Quit gMTP." msgstr "Esci gMTP." # File:interface.c, line:348 msgid "Refresh" msgstr "Aggiorna" # File:interface.c, line:216 msgid "Refresh Device" msgstr "Aggiorna dispositivo" # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr "Aggiorna file / elenco di cartelle." # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr "Rimuovere la playlist selezionata" # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr "Rimuovere file dalla playlist" # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr "Rescan: Come ho chiamato?\n" # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr "Seleziona file Album Art " # File:interface.c, line:624 msgid "Select Files to Add" msgstr "Seleziona i file da aggiungere" # File:interface.c, line:711 msgid "Select Path to Download" msgstr "Selezione del percorso per il download" # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr "Selezione del percorso da cui scaricare" # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr "Selezione del percorso da cui caricare le foto" # File:interface.c, line:578 msgid "Size" msgstr "Dimensioni" # File:interface.c, line:2006 msgid "Skip" msgstr "Salta" # File:interface.c, line:2007 msgid "Skip All" msgstr "Salta tutto" # File:interface.c, line:1968 msgid "Storage Device:" msgstr "Dispositivo di memorizzazione:" # File:interface.c, line:2540 msgid "Track" msgstr "Traccia" # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %lu, freespace = %lu\n" msgstr "Impossibile aggiungere %s a causa di spazio insufficiente: dimensione del file = &lu, spazio libero = %lu\n" # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr "Impossibile aggiungere file a causa di spazio insufficiente" # File:mtp.c, line:158 msgid "Unknown" msgstr "Sconosciuto" # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr "Sconosciuto : %04x:%04x @ bus %d, dev %d" # File:interface.c, line:1981 msgid "Unknown id: %d, %lu MB" msgstr "Sconosciuto id: %d, %lu MB" # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr "Aggiornamento della playlist fallito?\n" # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr "Aggiornamento della playlist fallito? 'realloc in savePlayList'\n" # File:interface.c, line:1078 msgid "Upload Path:" msgstr "Percorso di upload:" # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr "Carica un file JPG e assegnalo come Album Art." # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr "Carica un file immagine come Album Art." # File:interface.c, line:363 msgid "View Device Properties." msgstr "Visualizza le proprietà del dispositivo." # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr "Visualizza / Cambia preferenze gMTP." # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr "ATTENZIONE: schema gconf non valido, tornare ai valori predefiniti. Assicurarsi che lo schema venga caricato nel database di GConf.\n" # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr "ATTENZIONE: lo schema gconf non è valido e non è possibile salvare! Assicurarsi che lo schema venga caricato nel database di GConf.\n" # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr "ATTENZIONE: gconf_callback_func() non riuscito: abbiamo avuto una chiamata per una chiave che non è la nostra?\n" # File:interface.c, line:274 msgid "_About" msgstr "_Informazioni" # File:interface.c, line:234 msgid "_Edit" msgstr "_Modifica" # File:interface.c, line:160 msgid "_File" msgstr "_File" # File:interface.c, line:267 msgid "_Help" msgstr "_Aiuto" # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr "file = ( x / x ) x %" # File:dnd.c, line:83 msgid "file://" msgstr "file://" # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr "realloc in savePlayList fallito\n" # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr "setDeviceName: Come ho chiamato?\n" msgid "File Type" msgstr "Tipo di file" msgid "Track Name" msgstr "Nome traccia" msgid "Year" msgstr "Anno" msgid "_View" msgstr "_Visualizza" msgid "File Size" msgstr "Dimensione file" msgid "Track Number" msgstr "Numero traccia" msgid "Genre" msgstr "Genere" ########################################### msgid "Save Album Art File" msgstr "Salva Art File Album" msgid "Couldn't save image file %s\n" msgstr "Impossibile salvare il file immagine %s\n" msgid "Couldn't save image file\n" msgstr "Impossibile salvare il file immagine\n" msgid "Failed to get storage parameters from the device - need to disconnect." msgstr "Impossibile ottenere i parametri di memoria dal dispositivo - hanno bisogno di staccare." msgid "Couldn't remove album art\n" msgstr "Impossibile rimuovere le copertine degli album\n" msgid "Rename File" msgstr "Rinominare file" msgid "Rename File/Folder" msgstr "Rinominare file / cartella" msgid "File Name:" msgstr "Nome del file:" msgid "Upload" msgstr "Caricare" msgid "Remove" msgstr "Rimuovere" msgid "Format Device" msgstr "Formato del dispositivo" msgid "Are you sure you want to format this device?" msgstr "Sei sicuro che si desidera formattare questo dispositivo?" msgid "Format Device failed?\n" msgstr "Dispositivo formato fallito?\n" msgid "Prompt to add New Music track to existing playlist" msgstr "Richiesta di aggiungere Nuovo brano musicale alla Playlist esistente" ########################################################### msgid "Import Playlist" msgstr "Importa Playlist" msgid "Export Playlist" msgstr "Esportazione playlist" msgid "Select Playlist to Import" msgstr "Selezionare playlist da importare" msgid "The playlist failed to import correctly.\n" msgstr "La playlist non è riuscito a importare correttamente.\n" msgid "Ignore path information when importing playlist" msgstr "Ignora informazioni sul percorso durante l'importazione di playlist" msgid "Playlist" msgstr "Playlist" msgid "Couldn't save playlist file\n" msgstr "Impossibile salvare il file playlist\n" msgid "Couldn't save playlist file %s\n" msgstr "Impossibile salvare il file playlist %s\n" msgid "Couldn't open playlist file\n" msgstr "Impossibile aprire il file playlist\n" msgid "Couldn't open playlist file %s\n" msgstr "Impossibile aprire il file playlist %s\n" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "Non ha trovato i brani presenti su questo dispositivo. Non importare la playlist.\n" msgid "Playlist imported.\n" msgstr "Playlist importati.\n" ########################################################### msgid "Find:" msgstr "Cerca:" msgid "Filenames" msgstr "I nomi dei file" msgid "Track Information" msgstr "Musica Informazioni" msgid "Please enter search item." msgstr "Inserisci voce di ricerca." msgid "Search" msgstr "ricerca" msgid "Location" msgstr "Posizione" msgid "Found %d items" msgstr "Trovati %d articoli" msgid "Found %d item" msgstr "Trovati %d punto" msgid "Searching..." msgstr "ricerca..." msgid "Add To Playlist" msgstr "Aggiungi a playlist" msgid "Remove From Playlist" msgstr "Rimuovi dalla playlist" msgid "Columns" msgstr "Colonne" ########################################################### msgid "Folders" msgstr "Cartelle" msgid "Folder" msgstr "Cartella" msgid "Rename Folder" msgstr "Rinomina cartella" msgid "Move To..." msgstr "Sposta" msgid "Unable to move the selected folder underneath itself?\n" msgstr "Impossibile spostare la cartella selezionata sotto di sé?\n" msgid "Unable to move the selected folder into itself?\n" msgstr "Impossibile spostare la cartella selezionata in se stesso?\n" msgid "Unable to move the selected file?\n" msgstr "Impossibile spostare il file selezionato?\n" msgid "Unable to move the selected folder?\n" msgstr "Impossibile spostare la cartella selezionata?\n" msgid "translator-credits" msgstr "Francesca Ciceri" msgid "Select All" msgstr "Seleziona tutto" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "Non so come scaricare un riferimento cartella principale?\n" msgid "Suppress Album Errors" msgstr "Eliminare gli errori Album" msgid "Detect: No available Storage found on device?\n" msgstr "Rileva: No Deposito trovato disponibile sul dispositivo?\n" msgid "Utilize alternate access method" msgstr "Utilizzare il metodo di accesso alternativo" msgid "Disconnected device due to access method change" msgstr "Dispositivo disconnesso a causa dell'accesso cambiamento metodo" msgid "The move function is disabled when using the alternate access method for your device." msgstr "La funzione di spostamento è disabilitata quando si utilizza il metodo di accesso alternativo per il dispositivo." sgstr "Sovrascrivi tutti" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are storegMTP/po/es.po000064401651440000012000000576221205062525100137270ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr "\nError al obtener el fichero de dispositivo MTP.\n" # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr "\nNo se pudo eliminar el archivo %s\n" # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr " %s %s : (%04x:%04x) @ bus %d, dev %d" # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr " : %d MB (gratis) / %d MB (total)" # File:interface.c, line:2241 msgid " Playlists" msgstr " Lista de reproducción" # File:interface.c, line:982 msgid " Preferences" msgstr " Preferencias" # File:interface.c, line:1226 msgid " Properties" msgstr " Propiedades" # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr "%d MB (gratis) / %d MB (total)" # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr "%lluKB de %lluKB (%d%%)" # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr "%s - %lluKB de %lluKB (%d%%)" # File:mtp.c, line:461 msgid "" msgstr "" # File:interface.c, line:1665 msgid "" msgstr "" # File:interface.c, line:1346 msgid "Battery Level:" msgstr "De nivel de batería:" # File:interface.c, line:1501 msgid "Bus Location:" msgstr "Autobús Ubicación:" # File:interface.c, line:1492 msgid "Device Number:" msgstr "Número de dispositivo:" # File:interface.c, line:1328 msgid "Device Version:" msgstr "Versión del dispositivo:" # File:interface.c, line:1009 msgid "Device" msgstr "Dispositivo:" # File:interface.c, line:1041 msgid "File Operations" msgstr "Operaciones con archivos:" # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr "Rutas de archivos en el PC:" # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr "Propiedades del dispositivo MTP:" # File:interface.c, line:1337 msgid "Manufacturer:" msgstr "Fabricante:" # File:interface.c, line:1310 msgid "Model Number:" msgstr "Número de modelo:" # File:interface.c, line:1259 msgid "Name:" msgstr "Nombre:" # File:interface.c, line:1483 msgid "Product ID:" msgstr "Identificación de producto:" # File:interface.c, line:1465 msgid "Product:" msgstr "Producto:" # File:interface.c, line:1551 msgid "Raw Device Information" msgstr "Prima la información del dispositivo:" # File:interface.c, line:1386 msgid "Secure Time:" msgstr "Asegure Tiempo:" # File:interface.c, line:1319 msgid "Serial Number:" msgstr "Número de serie:" # File:interface.c, line:1355 msgid "Storage:" msgstr "Almacenamiento:" # File:interface.c, line:1371 msgid "Supported Formats:" msgstr "Formatos:" # File:interface.c, line:1395 msgid "Sync Partner:" msgstr "Sincronización Partner:" # File:interface.c, line:1474 msgid "Vendor ID:" msgstr "Vendor ID:" # File:interface.c, line:1456 msgid "Vendor:" msgstr "Proveedor:" # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr "Copyright 2009-2011, Darran Kartaschew\nDistribuido bajo la BSD Licence\n" # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr "Un sencillo reproductor de mp3 de cliente para\nSolaris 10 y otros sistemas UNIX / similar a UNIX\n" # File:interface.c, line:1737 msgid "About gMTP" msgstr "Acerca de gMTP" # File:interface.c, line:308 msgid "Add" msgstr "Agregar" # File:interface.c, line:249 msgid "Add Album Art" msgstr "Añadir Album Art" # File:interface.c, line:2358 msgid "Add File" msgstr "Agregar archivo" # File:interface.c, line:179 msgid "Add Files" msgstr "Agregar archivos" # File:interface.c, line:311 msgid "Add Files to your device." msgstr "Agregar archivos a su dispositivo." # File:interface.c, line:2273 msgid "Add New Playlist" msgstr "Añadir nueva lista de reproducción" # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr "Añadir una variedad de archivos en el dispositivo en la carpeta actual." # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr "Añadir y modificar las listas de reproducción." # File:interface.c, line:2344 msgid "Add file to playlist" msgstr "Agregar archivo a la lista" # File:interface.c, line:2522 msgid "Album" msgstr "Álbum" # File:interface.c, line:334 msgid "Album Art" msgstr "Album Art" # File:interface.c, line:2038 msgid "Album:" msgstr "Álbum:" # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr "Siempre mostrar el camino de descarga?" # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr "¿Está seguro que desea eliminar estos archivos?" # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr "¿Está seguro que quiere borrar esta carpeta (y todo su contenido)?" # File:interface.c, line:2512 msgid "Artist" msgstr "Artista" # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr "Intenta conectarse a dispositivos en el arranque" # File:interface.c, line:241 msgid "Change Device Name" msgstr "Cambio de nombre de dispositivo" # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr "Confirmar eliminación" # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr "Archivo Confirmar / eliminación de carpetas" # File:callbacks.c, line:165 msgid "Connect" msgstr "Conectar" # File:callbacks.c, line:170 msgid "Connect Device" msgstr "Conecte el dispositivo" # File:interface.c, line:1897 msgid "Connect to which device?" msgstr "Conectar con el dispositivo?" # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr "Conectar con el dispositivo de almacenamiento?" # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr "Conectar / desconectar el dispositivo." # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr "Conectado a %s (%s) - %d MB de espacio libre" # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr "Conectado a %s - %d MB de espacio libre" # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr "No se pudo crear el objeto lista de reproducción\n" # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr "No se puede eliminar la carpeta %s (%x)\n" # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr "No se pudo abrir archivo de imagen %s\n" # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr "No se pudo enviar el álbum de arte\n" # File:interface.c, line:199 msgid "Create Folder" msgstr "Crear carpeta" # File:interface.c, line:2259 msgid "Current Playlist: " msgstr "Actual lista de reproducción: " # File:interface.c, line:2309 msgid "Del" msgstr "Del" # File:interface.c, line:2379 msgid "Del File" msgstr "Eliminar archivos" # File:interface.c, line:315 msgid "Delete" msgstr "Eliminar" # File:interface.c, line:185 msgid "Delete Files" msgstr "Eliminar archivos" # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr "Eliminar archivos / carpetas desde el dispositivo." # File:interface.c, line:206 msgid "Delete Folder" msgstr "Eliminar carpeta" # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr "Borrar lista de temas no?\n" # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr "Detección: Se ha encontrado un error de asignación de memoria. \n" # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr "Detectar: No hay dispositivos en bruto que se encuentran.\n" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr "Detección: Se ha producido un error de conexión.\n" # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr "Detectar: No se puede abrir el dispositivo de primas?\n" # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr "Dispositivo de audio temas" # File:interface.c, line:1861 msgid "Device Name:" msgstr "Nombre del dispositivo:" # File:interface.c, line:222 msgid "Device Properties" msgstr "Propiedades del dispositivo" # File:interface.c, line:1908 msgid "Device:" msgstr "Dispositivo:" # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr "DevicePropeties: ¿Cómo me llaman?\n" # File:callbacks.c, line:146 msgid "Disconnect" msgstr "Desconectar" # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr "Desconecte el dispositivo" # File:interface.c, line:322 msgid "Download" msgstr "Descargar" # File:interface.c, line:191 msgid "Download Files" msgstr "Descargar archivos" # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr "Descargar archivos desde el dispositivo al PC host." # File:interface.c, line:1071 msgid "Download Path:" msgstr "Descargar Ruta de acceso:" # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the preferences dialog." msgstr "Descarga de archivos desde el dispositivo a su PC. Descargar ruta predeterminada se establece en el cuadro de diálogo prefernces" # File:interface.c, line:2550 msgid "Duration" msgstr "Duración" # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr "ERROR: Error de asignación de memoria en albumAddArt()\n" # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr "ERROR: Error de asignación de memoria en albumAddTrackToAlbum()\n" # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr "Editar lista de reproducción(s)" # File:interface.c, line:1784 msgid "Error" msgstr "Error" # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr "Error de código %d pista enviar al dispositivo: %s" # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr "Error al crear o actualizar álbum.\n (Esto puede ser debido a que el dispositivo no es compatible con discos.)\n" # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr "Error al obtener el fichero de dispositivo MTP." # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr "Error al enviar el archivo %s.\n" # File:mtp.c, line:504 msgid "Error sending file:" msgstr "Error al enviar el archivo:" # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr "Error al enviar el tema.\n" # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr "Error: No se pudo establecer el nombre del dispositivo para %s\n" # File:mtp.c, line:530 msgid "Failed to delete file" msgstr "No se pudo eliminar el archivo" # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr "Archivo %s ya existe en la carpeta de destino. ¿Desea:" # File:mtp.c, line:405 msgid "File Upload" msgstr "Carga de archivos" # File:mtp.c, line:538 msgid "File download" msgstr "Descarga de archivos" # File:interface.c, line:604 msgid "FileSize Hidden" msgstr "Tamaño del archivo oculto" # File:interface.c, line:568 msgid "Filename" msgstr "Nombre de archivo" # File:interface.c, line:2073 msgid "Filename:" msgstr "Nombre del archivo:" # File:interface.c, line:1817 msgid "Folder Name:" msgstr "Nombre de la carpeta:" # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr "La creación de carpetas no:" # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr "La creación de carpetas no: %s\n" # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr "No sé cómo eliminar una referencia carpeta principal?\n" # File:interface.c, line:1796 msgid "Information" msgstr "Información" # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr "Mover archivo seleccionado en la lista de temas" # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr "Mover archivo seleccionado en la lista de temas" # File:callbacks.c, line:367 msgid "N/A" msgstr "N/A" # File:interface.c, line:1805 msgid "New Folder" msgstr "Nueva carpeta" # File:interface.c, line:2714 msgid "New Playlist" msgstr "Nueva lista de reproducción" # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr "No hay álbumes disponibles para crear carátulas con. O bien:\n1. Usted no tiene archivos de música subido?\n2. Su dispositivo no es compatible con Picasa?\n3. Solicitudes anteriores para subir archivos no autocreate álbumes para usted o apoyar a los metadatos de los archivos a fin de crear los discos para usted?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "No hay ningún dispositivo conectado" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "No hay archivos / carpetas seleccionadas?" # File:interface.c, line:2566 msgid "Num" msgstr "Número" # File:interface.c, line:588 msgid "Object ID" msgstr "ID del objeto" # File:interface.c, line:2008 msgid "Overwrite" msgstr "Sobrescribir" # File:interface.c, line:2009 msgid "Overwrite All" msgstr "Sobrescribir todos" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr "Permanente eliminar los archivos / carpetas desde el dispositivo. Nota: Los álbumes se guardan como archivos *. alb." # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr "Lista pistas de audio" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Lista Nombre:" # File:interface.c, line:341 msgid "Playlists" msgstr "Listas de reproducción" # File:interface.c, line:367 msgid "Preferences" msgstr "Preferencias" # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr "Preguntar si el archivo para sobrescribir si ya existe" # File:interface.c, line:360 msgid "Properties" msgstr "Propiedades" # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr "Pregunta: Confirmar la sobrescritura del archivo existente?" # File:interface.c, line:379 msgid "Quit" msgstr "Salir" # File:interface.c, line:382 msgid "Quit gMTP." msgstr "Salir gMTP." # File:interface.c, line:348 msgid "Refresh" msgstr "Actualizar" # File:interface.c, line:216 msgid "Refresh Device" msgstr "Actualizar dispositivo" # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr "Actualizar Archivo / lista de carpetas." # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr "Borrar lista de reproducción seleccionada actual" # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr "Eliminar el archivo de lista de reproducción" # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr "Volver a explorar: ¿Cómo me llaman?\n" # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr "Album Art Seleccione Archivo" # File:interface.c, line:624 msgid "Select Files to Add" msgstr "Seleccione los archivos para agregar" # File:interface.c, line:711 msgid "Select Path to Download" msgstr "Seleccionar ruta para descargar" # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr "Seleccionar ruta para descargar a" # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr "Seleccionar ruta para subir de" # File:interface.c, line:578 msgid "Size" msgstr "Tamaño" # File:interface.c, line:2006 msgid "Skip" msgstr "Omitir" # File:interface.c, line:2007 msgid "Skip All" msgstr "Omitir todo" # File:interface.c, line:1968 msgid "Storage Device:" msgstr "Dispositivo de almacenamiento:" # File:interface.c, line:2540 msgid "Track" msgstr "Pista" # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %l, freespace = %l\n" msgstr "No se puede añadir %s debido a la falta de espacio: %l = tamaño del archivo, %l = espacio libre\n" # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr "No se puede agregar el archivo debido a la falta de espacio" # File:mtp.c, line:158 msgid "Unknown" msgstr "Desconocida" # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr "Desconocida: %04x:%04x @ bus %d, dev %d" # File:interface.c, line:1981 msgid "Unknown id: %d, %l MB" msgstr "Desconocida id: %d, %l MB" # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr "Actualizar lista de temas no?\n" # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr "Actualizar lista de temas no? 'realloc en savePlayList'\n" # File:interface.c, line:1078 msgid "Upload Path:" msgstr "Subir Ruta de acceso:" # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr "Subir un archivo JPG y asignarlo como Album Art." # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr "Subir un archivo de imagen como carátula del álbum." # File:interface.c, line:363 msgid "View Device Properties." msgstr "Propiedades de la vista de dispositivos." # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr "Ver / Cambiar preferencias gMTP." # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr "ADVERTENCIA: El esquema de GConf no válido, volviendo a los valores predeterminados. Por favor, asegúrese de esquema se carga en base de datos de GConf.\n" # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr "ADVERTENCIA: El esquema de GConf inválida, incapaz de guardar! Por favor, asegúrese de esquema se carga en base de datos de GConf.\n" # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr "ADVERTENCIA: gconf_callback_func () no - tenemos una devolución de llamada de eso no es nuestra clave?\n" # File:interface.c, line:274 msgid "_About" msgstr "_Acerca de" # File:interface.c, line:234 msgid "_Edit" msgstr "_Editar" # File:interface.c, line:160 msgid "_File" msgstr "A_rchivo" # File:interface.c, line:267 msgid "_Help" msgstr "A_yuda" # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr "archivo = ( x / x ) x %" # File:dnd.c, line:83 msgid "file://" msgstr "file://" # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr "realloc en savePlayList no\n" # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr "setDeviceName: ¿Cómo me llaman?\n" msgid "File Type" msgstr "Tipo de Archivo" msgid "Track Name" msgstr "Nombre de pista" msgid "Year" msgstr "Año" msgid "_View" msgstr "_Ver" msgid "File Size" msgstr "Tamaño del archivo" msgid "Track Number" msgstr "Número de pista" msgid "Genre" msgstr "Género" ########################################### msgid "Save Album Art File" msgstr "Guardar archivo de Album Art" msgid "Couldn't save image file %s\n" msgstr "No se pudo guardar el archivo de imagen %s\n" msgid "Couldn't save image file\n" msgstr "No se pudo guardar el archivo de imagen\n" msgid "Failed to get storage parameters from the device - need to disconnect." msgstr "No se pudo obtener los parámetros de almacenamiento del dispositivo - que desconectar." msgid "Couldn't remove album art\n" msgstr "No se pudo quitar la carátula del álbum\n" msgid "Rename File" msgstr "Cambiar el nombre del archivo" msgid "Rename File/Folder" msgstr "Cambiar el nombre de archivo / carpeta" msgid "File Name:" msgstr "Nombre del archivo:" msgid "Upload" msgstr "Subir" msgid "Remove" msgstr "Eliminar" msgid "Format Device" msgstr "Formato de dispositivos" msgid "Are you sure you want to format this device?" msgstr "¿Está seguro que desea formatear el dispositivo?" msgid "Format Device failed?\n" msgstr "Dispositivo de formato no?\n" msgid "Prompt to add New Music track to existing playlist" msgstr "Del sistema para agregar nueva pista de música a lista de reproducción existente" ########################################################### msgid "Import Playlist" msgstr "Importar lista de reproducción" msgid "Export Playlist" msgstr "Exportar lista de reproducción" msgid "Select Playlist to Import" msgstr "Seleccione Importar lista de reproducción" msgid "The playlist failed to import correctly.\n" msgstr "La lista no se importan correctamente.\n" msgid "Ignore path information when importing playlist\n" msgstr "No haga caso de información de la ruta al importar lista de reproducción\n" msgid "Playlist" msgstr "lista de reproducción" msgid "Couldn't save playlist file\n" msgstr "No se pudo guardar el archivo de lista de reproducción\n" msgid "Couldn't save playlist file %s\n" msgstr "No se pudo guardar el archivo de lista de reproducción %s\n" msgid "Couldn't open playlist file\n" msgstr "No se pudo abrir el archivo de lista de reproducción\n" msgid "Couldn't open playlist file %s\n" msgstr "No se pudo abrir el archivo de lista de reproducción %s\n" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "No encontraron pistas de la lista de reproducción que existen en este dispositivo. No importar la lista de reproducción.\n" msgid "Playlist imported.\n" msgstr "Lista de importación.\n" ########################################################### msgid "Find:" msgstr "Buscar:" msgid "Filenames" msgstr "Los nombres de archivo" msgid "Track Information" msgstr "Información Musical" msgid "Please enter search item." msgstr "Por favor, introduzca el punto de búsqueda." msgid "Search" msgstr "búsqueda" msgid "Location" msgstr "Ubicación" msgid "Found %d items" msgstr "Se han encontrado %d artículos" msgid "Found %d item" msgstr "Encontrado un elemento" msgid "Searching..." msgstr "búsqueda..." msgid "Add To Playlist" msgstr "Añadir a la lista de reproducción" msgid "Remove From Playlist" msgstr "Eliminar de la lista de reproducción" msgid "Columns" msgstr "Columnas" ########################################################### msgid "Folders" msgstr "Carpetas" msgid "Folder" msgstr "Carpeta" msgid "Rename Folder" msgstr "Cambiar el nombre de la carpeta" msgid "Move To..." msgstr "Mover a..." msgid "Unable to move the selected folder underneath itself?\n" msgstr "No se puede mover la carpeta seleccionada por debajo de sí mismo?\n" msgid "Unable to move the selected folder into itself?\n" msgstr "No se puede mover la carpeta seleccionada en sí mismo?\n" msgid "Unable to move the selected file?\n" msgstr "No se puede mover el archivo seleccionado?\n" msgid "Unable to move the selected folder?\n" msgstr "No se puede mover la carpeta seleccionada?\n" msgid "translator-credits" msgstr "Google" msgid "Select All" msgstr "Seleccionar todo" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "No sé cómo descargar una referencia carpeta principal?\n" msgid "Suppress Album Errors" msgstr "Suprimir errores Album" msgid "Detect: No available Storage found on device?\n" msgstr "Detectar: No disponible de almacenamiento se encuentra en el dispositivo?\n" msgid "Utilize alternate access method" msgstr "Utilizar el método de acceso alternativo" msgid "Disconnected device due to access method change" msgstr "Dispositivo desconectado debido a acceder a cambio de método" msgid "The move function is disabled when using the alternate access method for your device." msgstr "La función de desplazamiento se desactiva cuando se utiliza el método de acceso alternativo para el dispositivo." de audio" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Lista Nombre:" # File:interface.c, lingMTP/po/da.po000064401651440000012000000545701205062530400137020ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr "\nFejl ved hentning af filen fra MTP enhed.\n" # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr "\nKunne ikke slette fil %s\n" # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr " %s %s : (%04x:%04x) @ bus %d, dev %d" # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr " : %d MB (gratis) / %d MB (samlede)" # File:interface.c, line:2241 msgid " Playlists" msgstr " Afspilningslister" # File:interface.c, line:982 msgid " Preferences" msgstr " Indstillinger" # File:interface.c, line:1226 msgid " Properties" msgstr " Egenskaber" # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr "%d MB (gratis) / %d MB (samlede)" # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr "%lluKB af %lluKB (%d%%)" # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr "%s - %lluKB af %lluKB (%d%%)" # File:mtp.c, line:461 msgid "" msgstr "" # File:interface.c, line:1665 msgid "" msgstr "" # File:interface.c, line:1346 msgid "Battery Level:" msgstr "Batterilevel:" # File:interface.c, line:1501 msgid "Bus Location:" msgstr "Bus Beliggenhed:" # File:interface.c, line:1492 msgid "Device Number:" msgstr "Enhed nummer:" # File:interface.c, line:1328 msgid "Device Version:" msgstr "Enhed version:" # File:interface.c, line:1009 msgid "Device" msgstr "Enhed" # File:interface.c, line:1041 msgid "File Operations" msgstr "Filhandlinger" # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr "Filstier på PC" # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr "MTP enhed egenskaber" # File:interface.c, line:1337 msgid "Manufacturer:" msgstr "Producent:" # File:interface.c, line:1310 msgid "Model Number:" msgstr "Model nummer:" # File:interface.c, line:1259 msgid "Name:" msgstr "Navn:" # File:interface.c, line:1483 msgid "Product ID:" msgstr "Produkt-ID:" # File:interface.c, line:1465 msgid "Product:" msgstr "Produkt:" # File:interface.c, line:1551 msgid "Raw Device Information" msgstr "Rå enhedsoplysninger" # File:interface.c, line:1386 msgid "Secure Time:" msgstr "Sikker Tid:" # File:interface.c, line:1319 msgid "Serial Number:" msgstr "Serienummer:" # File:interface.c, line:1355 msgid "Storage:" msgstr "Opbevaring:" # File:interface.c, line:1371 msgid "Supported Formats:" msgstr "Understøttede formater:" # File:interface.c, line:1395 msgid "Sync Partner:" msgstr "Synkronisering partner:" # File:interface.c, line:1474 msgid "Vendor ID:" msgstr "Leverandør-id:" # File:interface.c, line:1456 msgid "Vendor:" msgstr "Leverandør:" # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr "Copyright 2009-2011, Darran Kartaschew\nUdgivet under BSD Licence\n" # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr "En simpel MP3-afspiller Client til Solaris 10\nog andre UNIX / UNIX-lignende systemer\n" # File:interface.c, line:1737 msgid "About gMTP" msgstr "Om gMTP" # File:interface.c, line:308 msgid "Add" msgstr "Tilføj" # File:interface.c, line:249 msgid "Add Album Art" msgstr "Tilføj albumcover" # File:interface.c, line:2358 msgid "Add File" msgstr "Tilføj fil" # File:interface.c, line:179 msgid "Add Files" msgstr "Tilføj filer" # File:interface.c, line:311 msgid "Add Files to your device." msgstr "Tilføj filer til enheden." # File:interface.c, line:2273 msgid "Add New Playlist" msgstr "Tilføj Ny afspilningsliste" # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr "Tilføj en række filer til din enhed i den nuværende mappe." # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr "Tilføj og rediger afspilningslister." # File:interface.c, line:2344 msgid "Add file to playlist" msgstr "Tilføj afspilningslisten " # File:interface.c, line:2522 msgid "Album" msgstr "Album" # File:interface.c, line:334 msgid "Album Art" msgstr "Albumcover" # File:interface.c, line:2038 msgid "Album:" msgstr "Album:" # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr "Vis altid download sti?" # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr "Er du sikker på du vil slette disse filer?" # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr "Er du sikker på du vil slette denne mappe (og hele indholdet)?" # File:interface.c, line:2512 msgid "Artist" msgstr "Kunstner" # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr "Forsøg at oprette forbindelse til enheden ved opstart" # File:interface.c, line:241 msgid "Change Device Name" msgstr "Skift Enhedsnavn" # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr "Bekræft sletning" # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr "Bekræft fil / mappe Slet" # File:callbacks.c, line:165 msgid "Connect" msgstr "Tilslut" # File:callbacks.c, line:170 msgid "Connect Device" msgstr "Tilslut enheden" # File:interface.c, line:1897 msgid "Connect to which device?" msgstr "Tilslut til hvilken enhed?" # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr "Tilslut som lagerenhed?" # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr "Tilslut / Afbryd til enheden." # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr "Forbundet til %s (%s) - %d MB fri" # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr "Forbundet til %s - %d MB fri" # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr "Kunne ikke oprette afspilningsliste objekt\n" # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr "Kunne ikke slette mappe %s (%x)\n" # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr "Kunne ikke åbne billedfil %s\n" # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr "Kunne ikke sende albumcover\n" # File:interface.c, line:199 msgid "Create Folder" msgstr "Opret mappe" # File:interface.c, line:2259 msgid "Current Playlist: " msgstr "Nuværende spilleliste: " # File:interface.c, line:2309 msgid "Del" msgstr "Slet" # File:interface.c, line:2379 msgid "Del File" msgstr "Slet fil" # File:interface.c, line:315 msgid "Delete" msgstr "Slet" # File:interface.c, line:185 msgid "Delete Files" msgstr "Slette filer" # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr "Slet filer / mapper fra enheden." # File:interface.c, line:206 msgid "Delete Folder" msgstr "Slet mappe" # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr "Sletning afspilningsliste mislykkedes?\n" # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr "Detect: Der opstod en Memory Fordeling Fejl. \n" # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr "Detect: Ingen rå enheder fundet.\n" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr "Detect: Der har været en fejl forbinder.\n" # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr "Detect: Kan ikke åbne rå enhed?\n" # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr "Enhed lydspor" # File:interface.c, line:1861 msgid "Device Name:" msgstr "Enhedens navn:" # File:interface.c, line:222 msgid "Device Properties" msgstr "Enhed Egenskaber" # File:interface.c, line:1908 msgid "Device:" msgstr "Enhed:" # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr "DevicePropeties: Hvordan fik jeg hedder?\n" # File:callbacks.c, line:146 msgid "Disconnect" msgstr "Afbryd" # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr "Afbryd enhed" # File:interface.c, line:322 msgid "Download" msgstr "Download" # File:interface.c, line:191 msgid "Download Files" msgstr "Download filer" # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr "Download filer fra enheden til din Host PC." # File:interface.c, line:1071 msgid "Download Path:" msgstr "Download-sti:" # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the preferences dialog." msgstr "Downloade filer fra din enhed til din pc. Standard Download sti er fastsat i preferences dialogen." # File:interface.c, line:2550 msgid "Duration" msgstr "Varighed" # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr "FEJL: Kunne ikke hukommelse tildeling i albumAddArt ()\n" # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr "FEJL: Kunne ikke hukommelse tildeling i albumAddTrackToAlbum ()\n" # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr "Rediger afspilningsliste(r)" # File:interface.c, line:1784 msgid "Error" msgstr "Fejl" # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr "Fejlkode %d sendes spor til enheden: %s" # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr "Fejl ved oprettelse eller opdatering album.\n(Dette kunne skyldes, at enheden ikke understøtter album.)\n" # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr "Fejl ved hentning af filen fra MTP enhed." # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr "Fejl ved afsendelse af filen %s.\n" # File:mtp.c, line:504 msgid "Error sending file:" msgstr "Fejl ved afsendelse af fil:" # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr "Fejl under afsendelse spor.\n" # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr "Fejl: Kunne ikke sætte enheden navn til %s\n" # File:mtp.c, line:530 msgid "Failed to delete file" msgstr "Kunne ikke slette fil" # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr "Fil %s eksisterer allerede i destinationsmappen. Ønsker du at:" # File:mtp.c, line:405 msgid "File Upload" msgstr "Fil Upload" # File:mtp.c, line:538 msgid "File download" msgstr "Fil download" # File:interface.c, line:604 msgid "FileSize Hidden" msgstr "FileSize Hidden" # File:interface.c, line:568 msgid "Filename" msgstr "Filnavn" # File:interface.c, line:2073 msgid "Filename:" msgstr "Filnavn:" # File:interface.c, line:1817 msgid "Folder Name:" msgstr "Mappe Navn:" # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr "Oprettelse af mapper mislykkedes:" # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr "Oprettelse af mapper mislykkedes: %s\n" # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr "Jeg ved ikke, hvordan man sletter en overordnet mappe reference?\n" # File:interface.c, line:1796 msgid "Information" msgstr "Informationer" # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr "Flyt valgte fil ned i spillelisten" # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr "Flyt valgte fil op i spillelisten" # File:callbacks.c, line:367 msgid "N/A" msgstr "ikke relevant" # File:interface.c, line:1805 msgid "New Folder" msgstr "Ny Mappe" # File:interface.c, line:2714 msgid "New Playlist" msgstr "Ny spilleliste" # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr "Ingen Albums rådighed for at indstille Album Art med. Enten:\n1. Du har ingen musikfiler uploadet?\n2. Enheden understøtter ikke albums?\n3. Tidligere programmer, der bruges til at uploade filer ikke autocreate album for dig eller støtter metadata for disse filer for at skabe album for dig?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "Ingen tilsluttede enhed" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "Ingen filer / mapper valgt?" # File:interface.c, line:2566 msgid "Num" msgstr "Nummer" # File:interface.c, line:588 msgid "Object ID" msgstr "Object ID" # File:interface.c, line:2008 msgid "Overwrite" msgstr "Overskriv" # File:interface.c, line:2009 msgid "Overwrite All" msgstr "Overskriv alle" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr "Permanent fjerne filer / mapper fra enheden. Bemærk: Albums gemmes som *. alb-filer." # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr "Afspilningsliste lydspor" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Afspilningsliste Navn:" # File:interface.c, line:341 msgid "Playlists" msgstr "Afspilningslister" # File:interface.c, line:367 msgid "Preferences" msgstr "Indstillinger" # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr "Spørg hvis du vil overskrive filen, hvis der allerede eksisterer" # File:interface.c, line:360 msgid "Properties" msgstr "Egenskaber" # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr "Spørgsmål: Bekræft Overskriv af eksisterende fil?" # File:interface.c, line:379 msgid "Quit" msgstr "Afslut" # File:interface.c, line:382 msgid "Quit gMTP." msgstr "Afslut gMTP." # File:interface.c, line:348 msgid "Refresh" msgstr "Genopfriske" # File:interface.c, line:216 msgid "Refresh Device" msgstr "Genopfriske enhed" # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr "Genopfriske fil / Mappe notering." # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr "Fjern aktuelt valgte spilleliste" # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr "Fjern fil fra afspilningslisten" # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr "Genskan: Hvordan fik jeg hedder?\n" # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr "Vælg albumcover fil" # File:interface.c, line:624 msgid "Select Files to Add" msgstr "Vælge filer at tilføje" # File:interface.c, line:711 msgid "Select Path to Download" msgstr "Vælg sti til Download" # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr "Vælg Sti til Download til" # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr "Vælg sti til Upload Fra" # File:interface.c, line:578 msgid "Size" msgstr "Størrelse" # File:interface.c, line:2006 msgid "Skip" msgstr "Spring" # File:interface.c, line:2007 msgid "Skip All" msgstr "Spring Alle" # File:interface.c, line:1968 msgid "Storage Device:" msgstr "Lagerenhed:" # File:interface.c, line:2540 msgid "Track" msgstr "Spor" # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %lu, freespace = %lu\n" msgstr "Kan ikke tilføje %s på grund af utilstrækkelig plads: Filstørrelse = %lu, ledig plads = %lu\n" # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr "Kunne ikke tilføje filen på grund af utilstrækkelig plads" # File:mtp.c, line:158 msgid "Unknown" msgstr "Ukendt" # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr "Ukendt : %04x:%04x @ bus %d, dev %d" # File:interface.c, line:1981 msgid "Unknown id: %d, %lu MB" msgstr "Ukendt ID: %d, %lu MB" # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr "Opdatering afspilningsliste mislykkedes?\n" # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr "Opdatering afspilningsliste mislykkedes? 'realloc i savePlayList'\n" # File:interface.c, line:1078 msgid "Upload Path:" msgstr "Upload Sti:" # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr "Upload en JPG-fil og tildele den som albumcover." # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr "Upload en billedfil som albumcover." # File:interface.c, line:363 msgid "View Device Properties." msgstr "Se Enhed Egenskaber." # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr "Se / Ændre gMTP indstillinger." # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr "ADVARSEL: GConf skema ugyldig, vender tilbage til standardindstillinger. Du sikre skema er lagt i GConf database.\n" # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr "ADVARSEL: GConf skema ugyldig, ude af stand til at redde! Du sikre skema er lagt i GConf database.\n" # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr "ADVARSEL: gconf_callback_func () mislykkedes - vi fik en tilbagekaldsanmodning for en nøgle thats ikke vores?\n" # File:interface.c, line:274 msgid "_About" msgstr "_Om" # File:interface.c, line:234 msgid "_Edit" msgstr "_Rediger" # File:interface.c, line:160 msgid "_File" msgstr "_Fil" # File:interface.c, line:267 msgid "_Help" msgstr "_Hjælp" # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr "file = ( x / x ) x %" # File:dnd.c, line:83 msgid "file://" msgstr "file://" # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr "realloc i savePlayList mislykkedes\n" # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr "setDeviceName: Hvordan fik jeg hedder?\n" msgid "File Type" msgstr "Filtype" msgid "Track Name" msgstr "Spor Navn" msgid "Year" msgstr "År" msgid "_View" msgstr "_Vis" msgid "File Size" msgstr "Filstørrelse" msgid "Track Number" msgstr "Spornummer" msgid "Genre" msgstr "Genren" ########################################### msgid "Save Album Art File" msgstr "Gem Album Art fil" msgid "Couldn't save image file %s\n" msgstr "Kunne ikke gemme billedfil %s\n" msgid "Couldn't save image file\n" msgstr "Kunne ikke gemme billedfil\n" msgid "Failed to get storage parameters from the device - need to disconnect." msgstr "Kunne ikke få storage parametre fra enheden - har brug for at afbryde." msgid "Couldn't remove album art\n" msgstr "Kunne ikke fjerne albumcover\n" msgid "Rename File" msgstr "Omdøb fil" msgid "Rename File/Folder" msgstr "Rename File/Folder" msgid "File Name:" msgstr "File Name:" msgid "Upload" msgstr "Upload" msgid "Remove" msgstr "Fjern" msgid "Format Device" msgstr "Format enhed" msgid "Are you sure you want to format this device?" msgstr "Er du sikker på du vil formatere denne enhed?" msgid "Format Device failed?\n" msgstr "Format Enhed mislykkedes?\n" msgid "Prompt to add New Music track to existing playlist" msgstr "Spørg for at tilføje New Music spor til eksisterende afspilningsliste" ########################################################### msgid "Import Playlist" msgstr "Import Playlist" msgid "Export Playlist" msgstr "Eksport Playlist" msgid "Select Playlist to Import" msgstr "Vælg Spilleliste til Import" msgid "The playlist failed to import correctly.\n" msgstr "Afspilningslisten undladt at importere korrekt.\n" msgid "Ignore path information when importing playlist" msgstr "Ignorer stioplysninger ved import afspilningsliste" msgid "Playlist" msgstr "Playlist" msgid "Couldn't save playlist file\n" msgstr "Kunne ikke gemme playlist fil\n" msgid "Couldn't save playlist file %s\n" msgstr "Kunne ikke gemme playlist fil %s\n" msgid "Couldn't open playlist file\n" msgstr "Kunne ikke åbne filen med afspilningslisten\n" msgid "Couldn't open playlist file %s\n" msgstr "Kunne ikke åbne filen med afspilningslisten %s\n" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "Fandt ingen spor i spillelisten, der findes på denne enhed. Har ikke importere afspilningslisten.\n" msgid "Playlist imported.\n" msgstr "Playlist importeret.\n" ########################################################### msgid "Find:" msgstr "Find:" msgid "Filenames" msgstr "Filnavne" msgid "Track Information" msgstr "spor Information" msgid "Please enter search item." msgstr "Indtast venligst søgeemnet." msgid "Search" msgstr "Søg" msgid "Location" msgstr "Placering" msgid "Found %d items" msgstr "Fandt %d emner" msgid "Found %d item" msgstr "Fandt %d emne" msgid "Searching..." msgstr "Søger ..." msgid "Add To Playlist" msgstr "Add To Playlist" msgid "Remove From Playlist" msgstr "Fjern fra afspilningsliste" msgid "Columns" msgstr "Kolonner" ########################################################### msgid "Folders" msgstr "Folders" msgid "Folder" msgstr "Folder" msgid "Rename Folder" msgstr "Omdøb mappe" msgid "Move To..." msgstr "Flyt til" msgid "Unable to move the selected folder underneath itself?\n" msgstr "Kan ikke flytte den valgte mappe nedenunder sig selv?\n" msgid "Unable to move the selected folder into itself?\n" msgstr "Kan ikke flytte den valgte mappe ind i sig selv?\n" msgid "Unable to move the selected file?\n" msgstr "Kan ikke flytte den valgte fil?\n" msgid "Unable to move the selected folder?\n" msgstr "Ude af stand til at flytte den valgte mappe?\n" msgid "translator-credits" msgstr "Cai Andersen" msgid "Select All" msgstr "Vælg alle" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "Jeg ved ikke, hvordan du henter en overordnet mappe reference?\n" msgid "Suppress Album Errors" msgstr "Undertryk Album Fejl" msgid "Detect: No available Storage found on device?\n" msgstr "Detect: Ingen tilgængelig opbevaring findes på enhed?\n" msgid "Utilize alternate access method" msgstr "Udnyt alternativ adgang metode" msgid "Disconnected device due to access method change" msgstr "Afbrudt enhed på grund af adgang metode forandring" msgid "The move function is disabled when using the alternate access method for your device." msgstr "Flytningen er deaktiveret, når du bruger den alternative adgang for enheden." # File:callbacks.c, line:167 msgid "No device attached" msgstr "Ingen tilsluttede enhed" # File:callbacks.c, line:99 msgid "No files/gMTP/po/de.po000064401651440000012000000550041205062527000137010ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr "\nFehler beim Abrufen der Datei aus MTP-Gerät.\n" # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr "\nKonnte die Datei nicht löschen %s\n" # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr " %s %s : (%04x:%04x) @ bus %d, dev %d" # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr " : %d MB (frei) / %d MB (gesamt)" # File:interface.c, line:2241 msgid " Playlists" msgstr " Playlisten" # File:interface.c, line:982 msgid " Preferences" msgstr " Einstellungen" # File:interface.c, line:1226 msgid " Properties" msgstr " Eigenschaften" # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr "%d MB (frei) / %d MB (gesamt)" # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr "%lluKB von %lluKB (%d%%)" # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr "%s - %lluKB von %lluKB (%d%%)" # File:mtp.c, line:461 msgid "" msgstr "" # File:interface.c, line:1665 msgid "" msgstr "" # File:interface.c, line:1346 msgid "Battery Level:" msgstr "Batterie-Füllstand:" # File:interface.c, line:1501 msgid "Bus Location:" msgstr "Bus Adresse:" # File:interface.c, line:1492 msgid "Device Number:" msgstr "Device-Nummer:" # File:interface.c, line:1328 msgid "Device Version:" msgstr "Device Version:" # File:interface.c, line:1009 msgid "Device" msgstr "Device" # File:interface.c, line:1041 msgid "File Operations" msgstr "Dateioperationen" # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr "Dateipfade auf dem PC" # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr "MTP-Gerät Eigenschaften" # File:interface.c, line:1337 msgid "Manufacturer:" msgstr "Hersteller:" # File:interface.c, line:1310 msgid "Model Number:" msgstr "Modellnummer:" # File:interface.c, line:1259 msgid "Name:" msgstr "Name:" # File:interface.c, line:1483 msgid "Product ID:" msgstr "Produkt-ID:" # File:interface.c, line:1465 msgid "Product:" msgstr "Produkt:" # File:interface.c, line:1551 msgid "Raw Device Information" msgstr "Rohe Geräte-Informationen" # File:interface.c, line:1386 msgid "Secure Time:" msgstr "Sichere Zeit:" # File:interface.c, line:1319 msgid "Serial Number:" msgstr "Seriennummer:" # File:interface.c, line:1355 msgid "Storage:" msgstr "Speicherplatz:" # File:interface.c, line:1371 msgid "Supported Formats:" msgstr "Unterstützte Formate:" # File:interface.c, line:1395 msgid "Sync Partner:" msgstr "Synchronisations-Partner:" # File:interface.c, line:1474 msgid "Vendor ID:" msgstr "Vendor-ID:" # File:interface.c, line:1456 msgid "Vendor:" msgstr "Hersteller:" # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr "Copyright 2009-2011, Darran Kartaschew\nunter der BSD Licence veröffentlicht\n" # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr "Ein einfacher MP3-Player-Client für Solaris 10\nund andere UNIX/UNIX-ähnlichen Systeme\n" # File:interface.c, line:1737 msgid "About gMTP" msgstr "Über gMTP" # File:interface.c, line:308 msgid "Add" msgstr "Hinzufügen" # File:interface.c, line:249 msgid "Add Album Art" msgstr "Album-Cover hinzufügen" # File:interface.c, line:2358 msgid "Add File" msgstr "Datei hinzufügen" # File:interface.c, line:179 msgid "Add Files" msgstr "Dateien hinzufügen" # File:interface.c, line:311 msgid "Add Files to your device." msgstr "Hinzufügen von Dateien zu Ihrem Gerät." # File:interface.c, line:2273 msgid "Add New Playlist" msgstr "Neue Playlist einfügen" # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr "Füge mehrere Dateien auf Ihr Gerät im aktuellen Ordner hinzu." # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr "Hinzufügen und Ändern von Playlisten." # File:interface.c, line:2344 msgid "Add file to playlist" msgstr "Datei zu Playlist hinzufügen" # File:interface.c, line:2522 msgid "Album" msgstr "Album" # File:interface.c, line:334 msgid "Album Art" msgstr "Album Cover" # File:interface.c, line:2038 msgid "Album:" msgstr "Album:" # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr "Download-Pfad immer anzeigen?" # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr "Sind Sie sicher, dass Sie diese Dateien löschen möchten?" # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr "Sie sind sicher, dass Sie diesen Ordner löschen möchten (und alle Inhalte)?" # File:interface.c, line:2512 msgid "Artist" msgstr "Künstler" # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr "Versuchen, beim Start zum Gerät zu verbinden" # File:interface.c, line:241 msgid "Change Device Name" msgstr "Geräte-Namen ändern" # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr "Löschen bestätigen" # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr "Löschen von Datei/Ordner bestätigen" # File:callbacks.c, line:165 msgid "Connect" msgstr "Verbinden" # File:callbacks.c, line:170 msgid "Connect Device" msgstr "Gerät verbinden" # File:interface.c, line:1897 msgid "Connect to which device?" msgstr "Verbinden mit welchem Gerät?" # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr "Verbinden mit welchem Speichergerät?" # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr "Verbinden/Trennen von Gerät." # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr "Verbunden mit %s (%s) - %d MB frei" # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr "Verbunden mit %s - %d MB frei" # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr "Playlist konnte nicht erstellt werden\n" # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr "Ordner konnte nicht gelöscht werden %s (%x)\n" # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr "Bild-Datei konnte nicht geöffnet werden %s\n" # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr "Album-Cover konnte nicht gesendet werden\n" # File:interface.c, line:199 msgid "Create Folder" msgstr "Ordner erstellen" # File:interface.c, line:2259 msgid "Current Playlist: " msgstr "Aktuelle Playlist: " # File:interface.c, line:2309 msgid "Del" msgstr "Löschen" # File:interface.c, line:2379 msgid "Del File" msgstr "Datei löschen" # File:interface.c, line:315 msgid "Delete" msgstr "Löschen" # File:interface.c, line:185 msgid "Delete Files" msgstr "Dateien löschen" # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr "Löschen von Dateien/Ordnern vom Gerät." # File:interface.c, line:206 msgid "Delete Folder" msgstr "Ordner löschen" # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr "Löschen von Playlist gescheitert?\n" # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr "Fehler: Speicherzugriffsfehler.\n" # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr "Fehler: Kein Gerät gefunden.\n" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr "Fehler: Fehler beim verbinden.\n" # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr "Fehler: Verbindung zu Gerät nicht möglich.\n" # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr "Audio-Dateien" # File:interface.c, line:1861 msgid "Device Name:" msgstr "Gerätename:" # File:interface.c, line:222 msgid "Device Properties" msgstr "Geräteeigenschaften" # File:interface.c, line:1908 msgid "Device:" msgstr "Gerät:" # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr "Geräteeigenschaften: Wie werde ich genannt?\n" # File:callbacks.c, line:146 msgid "Disconnect" msgstr "Trennen" # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr "Gerät trennen" # File:interface.c, line:322 msgid "Download" msgstr "Download" # File:interface.c, line:191 msgid "Download Files" msgstr "Dateien herunterladen" # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr "Dateien vom Gerät zum PC herunterladen." # File:interface.c, line:1071 msgid "Download Path:" msgstr "Download-Pfad:" # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the prefernces dialog." msgstr "Herunterladen von Dateien von Ihrem Gerät auf Ihren PC. Standard Download-Pfad ist im Einstellungen-Dialog eingestellt." # File:interface.c, line:2550 msgid "Duration" msgstr "Dauer" # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr "ERROR: Speicherzuweisung in albumAddArt() gescheitert\n" # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr "ERROR: Speicherzuweisung in albumAddTrackToAlbum() gescheitert\n" # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr "Playliste bearbeiten" # File:interface.c, line:1784 msgid "Error" msgstr "Fehler" # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr "Fehlercode %d beim Senden zum Gerät: %s" # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr "Fehler beim Erstellen oder Aktualisieren von Album.\n(Dies könnte darauf zurückzuführen sein, dass Ihr Gerät keine Alben unterstützt.)\n" # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr "Fehler beim Abrufen der Datei vom MTP-Gerät." # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr "Fehler beim Senden der Datei %s.\n" # File:mtp.c, line:504 msgid "Error sending file:" msgstr "Fehler beim Senden der Datei:" # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr "Fehler beim Senden der Musiktitel.\n" # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr "Fehler: Konnte Gerätenamen nicht setzen zu %s\n" # File:mtp.c, line:530 msgid "Failed to delete file" msgstr "Konnte die Datei nicht löschen" # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr "Datei %s existiert bereits in Zielordner.\nWollen Sie:" # File:mtp.c, line:405 msgid "File Upload" msgstr "Datei-Upload" # File:mtp.c, line:538 msgid "File download" msgstr "Datei herunterladen" # File:interface.c, line:604 msgid "FileSize Hidden" msgstr "Dateigröße versteckt" # File:interface.c, line:568 msgid "Filename" msgstr "Dateiname" # File:interface.c, line:2073 msgid "Filename:" msgstr "Dateiname:" # File:interface.c, line:1817 msgid "Folder Name:" msgstr "Ordnername:" # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr "Ordner erstellen fehlgeschlagen:" # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr "Ordner erstellen fehlgeschlagen: %s\n" # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr "Ich weiß nicht, wie man einen übergeordneten Ordnerverweis lösche?\n" # File:interface.c, line:1796 msgid "Information" msgstr "Information" # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr "Bewege die ausgewählte Datei herunter in der Playlist" # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr "Bewege die ausgewählte Datei herauf in der Playlist" # File:callbacks.c, line:367 msgid "N/A" msgstr "N/A" # File:interface.c, line:1805 msgid "New Folder" msgstr "Neuer Ordner" # File:interface.c, line:2714 msgid "New Playlist" msgstr "Neue Playlist" # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr "Keine Alben zur Verfügung um Cover zu ändern. Entweder:\n1. Sie haben keine Musik-Dateien hochgeladen?\n2. Ihr Gerät unterstützt keine Alben?\n3. Vorherige Anwendungen haben Dateien hochgeladen ohne Alben zu erstellen oder unterstützen keine Meta-Daten, um Alben zu erstellen?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "Kein Gerät angeschlossen" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "Keine Dateien/Ordner ausgewählt?" # File:interface.c, line:2566 msgid "Num" msgstr "Nummer" # File:interface.c, line:588 msgid "Object ID" msgstr "Objekt-ID" # File:interface.c, line:2008 msgid "Overwrite" msgstr "Überschreiben" # File:interface.c, line:2009 msgid "Overwrite All" msgstr "Alle überschreiben" # File:interface.c, line:318 msgid "Permanently remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr "Dateien/Ordner dauerhaft von Ihrem Gerät entfernen. Hinweis: Die Alben werden als *.alb-Dateien gespeichert." # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr "Titel in Playlist" # File:interface.c, line:2726 msgid "Playlist Name:" msgstr "Name der Playlist:" # File:interface.c, line:341 msgid "Playlists" msgstr "Playlisten" # File:interface.c, line:367 msgid "Preferences" msgstr "Einstellungen" # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr "Fragen, wenn Dateien überschrieben werden, die bereits existieren" # File:interface.c, line:360 msgid "Properties" msgstr "Eigenschaften" # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr "Frage: Bestätigen beim Überschreiben von Dateien?" # File:interface.c, line:379 msgid "Quit" msgstr "Beenden" # File:interface.c, line:382 msgid "Quit gMTP." msgstr "gMTP beenden" # File:interface.c, line:348 msgid "Refresh" msgstr "Aktualisieren" # File:interface.c, line:216 msgid "Refresh Device" msgstr "Gerät aktualisieren" # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr "Aktualisieren von Dateien und Ordnern." # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr "Entfernen Sie aktuell ausgewählte Playlist" # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr "Entferne Datei von Playlist" # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr "Rescan: Wie wurde ich genannt?\n" # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr "Wählen Sie die Cover-Datei" # File:interface.c, line:624 msgid "Select Files to Add" msgstr "Dateien zum Hinzufügen auswählen" # File:interface.c, line:711 msgid "Select Path to Download" msgstr "Wählen Sie den Pfad zum Herunterladen" # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr "Wählen Sie Pfad, zu dem heruntergeladen werden soll" # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr "Wählen Sie Pfad, von dem hochgeladen werden soll" # File:interface.c, line:578 msgid "Size" msgstr "Größe" # File:interface.c, line:2006 msgid "Skip" msgstr "Überspringen" # File:interface.c, line:2007 msgid "Skip All" msgstr "Alle überspringen" # File:interface.c, line:1968 msgid "Storage Device:" msgstr "Speichergerät:" # File:interface.c, line:2540 msgid "Track" msgstr "Titel" # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %lu, freespace = %lu\n" msgstr "Kann %s nicht hinzuzufügen wegen Platzmangels: Dateigröße = %lu, freier Speicherplatz = %lu\n" # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr "Datei konnte wegen Platzmangels nicht hinzugefügt werden" # File:mtp.c, line:158 msgid "Unknown" msgstr "Unbekannt" # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr "Unbekannt : %04x:%04x @ bus %d, dev %d" # File:interface.c, line:1981 msgid "Unknown id: %d, %lu MB" msgstr "Unbekannt id: %d, %lu MB" # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr "Aktualisieren der Playlist gescheitert?\n" # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr "Aktualisieren der Playlist gescheitert? 'realloc in savePlayList'\n" # File:interface.c, line:1078 msgid "Upload Path:" msgstr "Upload Pfad:" # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr "Laden Sie ein JPG-Datei hoch und weisen Sie es als Album-Cover zu." # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr "Hochladen einer Bilddatei als Album-Cover." # File:interface.c, line:363 msgid "View Device Properties." msgstr "Geräte Eigenschaften." # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr "Anzeigen/ändern von gMTP Einstellungen." # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr "WARNUNG: gconf Schema ungültig, Rückgriff auf Standardwerte. Bitte stellen Sie sicher dass das Schema in der gconf-Datenbank geladen ist.\n" # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr "WARNUNG: gconf Schema ungültig, kann nicht speichern! Bitte stellen Sie sicher dass das Schema in der gconf-Datenbank geladen ist.\n" # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr "WARNUNG: gconf_callback_func () failed - wir haben einen Callback für einen Key der nicht uns gehört?\n" # File:interface.c, line:274 msgid "_About" msgstr "_Über" # File:interface.c, line:234 msgid "_Edit" msgstr "_Bearbeiten" # File:interface.c, line:160 msgid "_File" msgstr "_Datei" # File:interface.c, line:267 msgid "_Help" msgstr "_Hilfe" # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr "file = ( x / x ) x %" # File:dnd.c, line:83 msgid "file://" msgstr "file://" # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr "realloc in savePlayList gescheitert\n" # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr "setDeviceName: Wie wurde ich genannt?\n" msgid "File Type" msgstr "Dateityp" msgid "Track Name" msgstr "Titelname" msgid "Year" msgstr "Jahr" msgid "_View" msgstr "_Anzeigen" msgid "File Size" msgstr "Datei-Größe" msgid "Track Number" msgstr "Titelnummer" msgid "Genre" msgstr "Genre" ########################################### msgid "Save Album Art File" msgstr "Album-Cover speichern" msgid "Couldn't save image file %s\n" msgstr "Konnte Bild-Datei %s nicht speichern\n" msgid "Couldn't save image file\n" msgstr "Konnte Bild-Datei nicht speichern\n" msgid "Failed to get storage parameters from the device - need to disconnect." msgstr "Fehler beim Abrufen von Speicher-Parametern vom Gerät - Verbindung muss getrennt werden." msgid "Couldn't remove album art\n" msgstr "Konnte Album-Cover nicht entfernen\n" msgid "Rename File" msgstr "Datei umbenennen" msgid "Rename File/Folder" msgstr "Datei/Ordner umbenennen" msgid "File Name:" msgstr "Dateiname:" msgid "Upload" msgstr "Hochladen" msgid "Remove" msgstr "Entfernen" msgid "Format Device" msgstr "Gerät formatieren" msgid "Are you sure you want to format this device?" msgstr "Sind Sie sicher, dass Sie dieses Gerät formatieren möchten?" msgid "Format Device failed?\n" msgstr "Formatierung gescheitert?\n" msgid "Prompt to add New Music track to existing playlist" msgstr "Fragen beim Hinzufügen von neuen Titeln zu bestehender Playlist" ########################################################### msgid "Import Playlist" msgstr "Playlist importieren" msgid "Export Playlist" msgstr "Playlist exportieren" msgid "Select Playlist to Import" msgstr "Wählen Sie die zu importierende Playlist" msgid "The playlist failed to import correctly.\n" msgstr "Die Playlist konnte nicht korrekt importiert werden.\n" msgid "Ignore path information when importing playlist" msgstr "Pfadinformationen beim Importieren der Playlist ignorieren" msgid "Playlist" msgstr "Playlist" msgid "Couldn't save playlist file\n" msgstr "Konnte Playlist-Datei nicht speichern\n" msgid "Couldn't save playlist file %s\n" msgstr "Konnte Playlist-Datei %s nicht speichern\n" msgid "Couldn't open playlist file\n" msgstr "Konnte Playlist-Datei nicht öffnen\n" msgid "Couldn't open playlist file %s\n" msgstr "Konnte Playlist-Datei %s nicht öffnen\n" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "Konnte keine Titel in der Playlist finden, die auf dem Gerät existieren. Playlist wurde nicht importiert.\n" msgid "Playlist imported.\n" msgstr "Playlist importiert.\n" ########################################################### msgid "Folders" msgstr "Ordnern" msgid "Folder" msgstr "Ordner" msgid "Rename Folder" msgstr "Ordner umbenennen" msgid "Move To..." msgstr "Umzug nach..." msgid "Unable to move the selected folder underneath itself?\n" msgstr "Unfähig, die ausgewählten Ordner unter sich zu bewegen?\n" msgid "Unable to move the selected folder into itself?\n" msgstr "Unfähig, die ausgewählten Ordner in sich selbst zu bewegen?\n" msgid "Unable to move the selected file?\n" msgstr "Unfähig, die ausgewählte Datei zu bewegen?\n" msgid "Unable to move the selected folder?\n" msgstr "Unfähig, die ausgewählten Ordner zu verschieben?\n" msgid "translator-credits" msgstr "Laurenz Kamp" msgid "Select All" msgstr "Select All" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "Ich weiß nicht, wie man einen übergeordneten Ordner Referenz herunterladen?\n" msgid "Suppress Album Errors" msgstr "Unterdrückt Album Errors" msgid "Detect: No available Storage found on device?\n" msgstr "Fehler: Keine verfügbaren Speicher gefunden auf device \n" msgid "Utilize alternate access method" msgstr "Nutzen Sie alternative Zugriffsmethode" msgid "Disconnected device due to access method change" msgstr "Getrennt Geräts durch Methodenwechsel zugreifen" msgid "The move function is disabled when using the alternate access method for your device." msgstr "Die Move-Funktion ist deaktiviert, wenn die alternative Zugriffsmethode für Ihr Gerät." ne Alben?\n3. Vorherige Anwendungen haben Dateien hochgeladen ohne Alben zu erstellen oder unterstützen keine Meta-Daten, um Alben zu erstellen?\n" # File:callbacks.c, line:167 msgid "No device attached" msgstr "Kein Gerät angeschlossen" # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr "Keine Dateien/Ordner ausgewählt?" # File:interface.c, line:2566 msgid "Num" msgstr "Nummer" # File:interface.c, line:588 msgid "Object ID" msgstr "Objekt-ID" # File:interface.c, line:2008 msgigMTP/po/gMTP.po000064401651440000012000000364261205044006400141230ustar00darranstaff00003030200010domain "gmtp" # File:mtp.c, line:544 msgid "\nError getting file from MTP device.\n" msgstr # File:mtp.c, line:529 msgid "\nFailed to delete file %s\n" msgstr # File:interface.c, line:1922 msgid " %s %s : (%04x:%04x) @ bus %d, dev %d" msgstr # File:interface.c, line:1601 msgid " : %d MB (free) / %d MB (total)" msgstr # File:interface.c, line:2241 msgid " Playlists" msgstr # File:interface.c, line:982 msgid " Preferences" msgstr # File:interface.c, line:1226 msgid " Properties" msgstr # File:interface.c, line:1587 msgid "%d MB (free) / %d MB (total)" msgstr # File:interface.c, line:1723 msgid "%lluKB of %lluKB (%d%%)" msgstr # File:interface.c, line:1721 msgid "%s - %lluKB of %lluKB (%d%%)" msgstr # File:mtp.c, line:461 msgid "" msgstr # File:interface.c, line:1665 msgid "" msgstr # File:interface.c, line:1346 msgid "Battery Level:" msgstr # File:interface.c, line:1501 msgid "Bus Location:" msgstr # File:interface.c, line:1492 msgid "Device Number:" msgstr # File:interface.c, line:1328 msgid "Device Version:" msgstr # File:interface.c, line:1009 msgid "Device" msgstr # File:interface.c, line:1041 msgid "File Operations" msgstr # File:interface.c, line:1111 msgid "Filepaths on PC" msgstr # File:interface.c, line:1433 msgid "MTP Device Properties" msgstr # File:interface.c, line:1337 msgid "Manufacturer:" msgstr # File:interface.c, line:1310 msgid "Model Number:" msgstr # File:interface.c, line:1259 msgid "Name:" msgstr # File:interface.c, line:1483 msgid "Product ID:" msgstr # File:interface.c, line:1465 msgid "Product:" msgstr # File:interface.c, line:1551 msgid "Raw Device Information" msgstr # File:interface.c, line:1386 msgid "Secure Time:" msgstr # File:interface.c, line:1319 msgid "Serial Number:" msgstr # File:interface.c, line:1355 msgid "Storage:" msgstr # File:interface.c, line:1371 msgid "Supported Formats:" msgstr # File:interface.c, line:1395 msgid "Sync Partner:" msgstr # File:interface.c, line:1474 msgid "Vendor ID:" msgstr # File:interface.c, line:1456 msgid "Vendor:" msgstr # File:interface.c, line:1767 msgid "Copyright 2009-2011, Darran Kartaschew\nReleased under the BSD Licence\n" msgstr # File:interface.c, line:1759 msgid "A simple MP3 Player Client for Solaris 10\nand other UNIX / UNIX-like systems\n" msgstr # File:interface.c, line:1737 msgid "About gMTP" msgstr # File:interface.c, line:308 msgid "Add" msgstr # File:interface.c, line:249 msgid "Add Album Art" msgstr # File:interface.c, line:2358 msgid "Add File" msgstr # File:interface.c, line:179 msgid "Add Files" msgstr # File:interface.c, line:311 msgid "Add Files to your device." msgstr # File:interface.c, line:2273 msgid "Add New Playlist" msgstr # File:interface.c, line:311 msgid "Add a varity of Files to your device in the current folder." msgstr # File:interface.c, line:344 msgid "Add and Modify Playlists." msgstr # File:interface.c, line:2344 msgid "Add file to playlist" msgstr # File:interface.c, line:2522 msgid "Album" msgstr # File:interface.c, line:334 msgid "Album Art" msgstr # File:interface.c, line:2038 msgid "Album:" msgstr # File:interface.c, line:1060 msgid "Always show Download Path?" msgstr # File:callbacks.c, line:111 msgid "Are you sure you want to delete these files?" msgstr # File:callbacks.c, line:342 msgid "Are you sure you want to delete this folder (and all contents)?" msgstr # File:interface.c, line:2512 msgid "Artist" msgstr # File:interface.c, line:1005 msgid "Attempt to connect to Device on startup" msgstr # File:interface.c, line:241 msgid "Change Device Name" msgstr # File:callbacks.c, line:112 msgid "Confirm Delete" msgstr # File:interface.c, line:1033 msgid "Confirm File/Folder Delete" msgstr # File:callbacks.c, line:165 msgid "Connect" msgstr # File:callbacks.c, line:170 msgid "Connect Device" msgstr # File:interface.c, line:1897 msgid "Connect to which device?" msgstr # File:interface.c, line:1957 msgid "Connect to which storage device?" msgstr # File:interface.c, line:299 msgid "Connect/Disconnect to your device." msgstr # File:callbacks.c, line:47 msgid "Connected to %s (%s) - %d MB free" msgstr # File:callbacks.c, line:44 msgid "Connected to %s - %d MB free" msgstr # File:mtp.c, line:816 msgid "Couldn't create playlist object\n" msgstr # File:mtp.c, line:585 msgid "Couldn't delete folder %s (%x)\n" msgstr # File:mtp.c, line:760 msgid "Couldn't open image file %s\n" msgstr # File:mtp.c, line:777 msgid "Couldn't send album art\n" msgstr # File:interface.c, line:199 msgid "Create Folder" msgstr # File:interface.c, line:2259 msgid "Current Playlilst: " msgstr # File:interface.c, line:2309 msgid "Del" msgstr # File:interface.c, line:2379 msgid "Del File" msgstr # File:interface.c, line:315 msgid "Delete" msgstr # File:interface.c, line:185 msgid "Delete Files" msgstr # File:interface.c, line:318 msgid "Delete Files/Folders from your device." msgstr # File:interface.c, line:206 msgid "Delete Folder" msgstr # File:mtp.c, line:825 msgid "Deleting playlist failed?\n" msgstr # File:mtp.c, line:92 msgid "Detect: Encountered a Memory Allocation Error. \n" msgstr # File:mtp.c, line:84 msgid "Detect: No raw devices found.\n" msgstr # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr # File:interface.c, line:1861 msgid "Device Name:" msgstr # File:interface.c, line:222 msgid "Device Properties" msgstr # File:interface.c, line:1908 msgid "Device:" msgstr # File:mtp.c, line:259 msgid "DevicePropeties: How did I get called?\n" msgstr # File:callbacks.c, line:146 msgid "Disconnect" msgstr # File:callbacks.c, line:161 msgid "Disconnect Device" msgstr # File:interface.c, line:322 msgid "Download" msgstr # File:interface.c, line:191 msgid "Download Files" msgstr # File:interface.c, line:325 msgid "Download Files from your device to your Host PC." msgstr # File:interface.c, line:1071 msgid "Download Path:" msgstr # File:interface.c, line:325 msgid "Download files from your device to your PC. Default Download path is set in the preferences dialog." msgstr # File:interface.c, line:2550 msgid "Duration" msgstr # File:mtp.c, line:755 msgid "ERROR: Failed memory allocation in albumAddArt()\n" msgstr # File:mtp.c, line:700 msgid "ERROR: Failed memory allocation in albumAddTrackToAlbum()\n" msgstr # File:interface.c, line:255 msgid "Edit Playlist(s)" msgstr # File:interface.c, line:1784 msgid "Error" msgstr # File:mtp.c, line:482 msgid "Error code %d sending track to device: %s" msgstr # File:mtp.c, line:732 msgid "Error creating or updating album.\n(This could be due to that your device does not support albums.)\n" msgstr # File:mtp.c, line:545 msgid "Error getting file from MTP device." msgstr # File:mtp.c, line:503 msgid "Error sending file %s.\n" msgstr # File:mtp.c, line:504 msgid "Error sending file:" msgstr # File:mtp.c, line:481 msgid "Error sending track.\n" msgstr # File:mtp.c, line:637 msgid "Error: Couldn't set device name to %s\n" msgstr # File:mtp.c, line:530 msgid "Failed to delete file" msgstr # File:interface.c, line:2004 msgid "File %s already exists in target folder.\nDo you want to:" msgstr # File:mtp.c, line:405 msgid "File Upload" msgstr # File:mtp.c, line:538 msgid "File download" msgstr # File:interface.c, line:604 msgid "FileSize Hidden" msgstr # File:interface.c, line:568 msgid "Filename" msgstr # File:interface.c, line:2073 msgid "Filename:" msgstr # File:interface.c, line:1817 msgid "Folder Name:" msgstr # File:mtp.c, line:557 msgid "Folder creation failed:" msgstr # File:mtp.c, line:556 msgid "Folder creation failed: %s\n" msgstr # File:interface.c, line:864 msgid "I don't know how to delete a parent folder reference?\n" msgstr # File:interface.c, line:1796 msgid "Information" msgstr # File:interface.c, line:2415 msgid "Move selected file down in the playlist" msgstr # File:interface.c, line:2410 msgid "Move selected file up in the playlist" msgstr # File:callbacks.c, line:367 msgid "N/A" msgstr # File:interface.c, line:1805 msgid "New Folder" msgstr # File:interface.c, line:2714 msgid "New Playlist" msgstr # File:interface.c, line:2054 msgid "No Albums available to set Album Art with. Either:\n1. You have no music files uploaded?\n2. Your device does not support Albums?\n3. Previous applications used to upload files do not autocreate albums for you or support the metadata for those files in order to create the albums for you?\n" msgstr # File:callbacks.c, line:167 msgid "No device attached" msgstr # File:callbacks.c, line:99 msgid "No files/folders selected?" msgstr # File:interface.c, line:2566 msgid "Num" msgstr # File:interface.c, line:588 msgid "Object ID" msgstr # File:interface.c, line:2008 msgid "Overwrite" msgstr # File:interface.c, line:2009 msgid "Overwrite All" msgstr # File:interface.c, line:318 msgid "Permantly remove files/folders from your device. Note: Albums are stored as *.alb files." msgstr # File:interface.c, line:2392 msgid "Playlist Audio Tracks" msgstr # File:interface.c, line:2726 msgid "Playlist Name:" msgstr # File:interface.c, line:341 msgid "Playlists" msgstr # File:interface.c, line:367 msgid "Preferences" msgstr # File:interface.c, line:1037 msgid "Prompt if to Overwrite file if already exists" msgstr # File:interface.c, line:360 msgid "Properties" msgstr # File:interface.c, line:2011 msgid "Question: Confirm Overwrite of Existing File?" msgstr # File:interface.c, line:379 msgid "Quit" msgstr # File:interface.c, line:382 msgid "Quit gMTP." msgstr # File:interface.c, line:348 msgid "Refresh" msgstr # File:interface.c, line:216 msgid "Refresh Device" msgstr # File:interface.c, line:351 msgid "Refresh File/Folder listing." msgstr # File:interface.c, line:2295 msgid "Remove Current Selected Playlist" msgstr # File:interface.c, line:2365 msgid "Remove file from playlist" msgstr # File:mtp.c, line:310 msgid "Rescan: How did I get called?\n" msgstr # File:callbacks.c, line:418 msgid "Select Album Art File" msgstr # File:interface.c, line:624 msgid "Select Files to Add" msgstr # File:interface.c, line:711 msgid "Select Path to Download" msgstr # File:callbacks.c, line:238 msgid "Select Path to Download to" msgstr # File:callbacks.c, line:264 msgid "Select Path to Upload From" msgstr # File:interface.c, line:578 msgid "Size" msgstr # File:interface.c, line:2006 msgid "Skip" msgstr # File:interface.c, line:2007 msgid "Skip All" msgstr # File:interface.c, line:1968 msgid "Storage Device:" msgstr # File:interface.c, line:2540 msgid "Track" msgstr # File:mtp.c, line:399 msgid "Unable to add %s due to insufficient space: filesize = %l, freespace = %l\n" msgstr # File:mtp.c, line:400 msgid "Unable to add file due to insufficient space" msgstr # File:mtp.c, line:158 msgid "Unknown" msgstr # File:interface.c, line:1930 msgid "Unknown : %04x:%04x @ bus %d, dev %d" msgstr # File:interface.c, line:1981 msgid "Unknown id: %d, %l MB" msgstr # File:mtp.c, line:834 msgid "Updating playlist failed?\n" msgstr # File:interface.c, line:2957 msgid "Updating playlist failed? 'realloc in savePlayList'\n" msgstr # File:interface.c, line:1078 msgid "Upload Path:" msgstr # File:interface.c, line:337 msgid "Upload a JPG file and assign it as Album Art." msgstr # File:interface.c, line:337 msgid "Upload an image file as Album Art." msgstr # File:interface.c, line:363 msgid "View Device Properties." msgstr # File:interface.c, line:370 msgid "View/Change gMTP Preferences." msgstr # File:prefs.c, line:57 msgid "WARNING: gconf schema invalid, reverting to defaults. Please ensure schema is loaded in gconf database.\n" msgstr # File:prefs.c, line:76 msgid "WARNING: gconf schema invalid, unable to save! Please ensure schema is loaded in gconf database.\n" msgstr # File:prefs.c, line:127 msgid "WARNING: gconf_callback_func() failed - we got a callback for a key thats not ours?\n" msgstr # File:interface.c, line:274 msgid "_About" msgstr # File:interface.c, line:234 msgid "_Edit" msgstr # File:interface.c, line:160 msgid "_File" msgstr # File:interface.c, line:267 msgid "_Help" msgstr # File:interface.c, line:1673 msgid "file = ( x / x ) x %" msgstr # File:dnd.c, line:83 msgid "file://" msgstr # File:interface.c, line:2956 msgid "realloc in savePlayList failed\n" msgstr # File:mtp.c, line:643 msgid "setDeviceName: How did I get called?\n" msgstr ########################################### msgid "File Type" msgstr msgid "Track Name" msgstr msgid "Year" msgstr msgid "_View" msgstr msgid "File Size" msgstr msgid "Track Number" msgstr msgid "Genre" msgstr ########################################### msgid "Select Album Art File" msgstr msgid "Save Album Art File" msgstr msgid "Couldn't save image file %s\n" msgstr msgid "Couldn't save image file\n" msgstr msgid "Failed to get storage parameters from the device - need to disconnect." msgstr msgid "Couldn't remove album art\n" msgstr msgid "Rename File" msgstr msgid "Rename File/Folder" msgstr msgid "File Name:" msgstr msgid "Upload" msgstr msgid "Remove" msgstr msgid "Format Device" msgstr msgid "Are you sure you want to format this device?" msgstr msgid "Format Device failed?\n" msgstr msgid "Prompt to add New Music track to existing playlist" msgstr ########################################################### msgid "Import Playlist" msgstr "" msgid "Export Playlist" msgstr "" msgid "Select Playlist to Import" msgstr "" msgid "The playlist failed to import correctly.\n" msgstr "" msgid "Ignore path information when importing playlist" msgstr "" msgid "Playlist" msgstr "" msgid "Couldn't save playlist file\n" msgstr "" msgid "Couldn't save playlist file %s\n" msgstr "" msgid "Couldn't open playlist file\n" msgstr "" msgid "Couldn't open playlist file %s\n" msgstr "" msgid "Found no tracks within the playlist that exist on this device. Did not import the playlist.\n" msgstr "" msgid "Playlist imported.\n" msgstr "" ########################################################### msgid "Find:" msgstr "" msgid "Filenames" msgstr "" msgid "Track Information" msgstr "" msgid "Please enter search item." msgstr "" msgid "Search" msgstr "" msgid "Location" msgstr "" msgid "Found %d items" msgstr "" msgid "Found %d item" msgstr "" msgid "Searching..." msgstr "" msgid "Add To Playlist" msgstr "" msgid "Remove From Playlist" msgstr "" msgid "Columns" msgstr "" ########################################################### msgid "Folders" msgstr "" msgid "Folder" msgstr "" msgid "Rename Folder" msgstr "" msgid "Move To..." msgstr "" msgid "Unable to move the selected folder underneath itself?\n" msgstr "" msgid "Unable to move the selected folder into itself?\n" msgstr "" msgid "Unable to move the selected file?\n" msgstr "" msgid "Unable to move the selected folder?\n" msgstr "" msgid "Select All" msgstr "" ########################################################### msgid "I don't know how to download a parent folder reference?\n" msgstr "" msgid "Suppress Album Errors" msgstr "" msgid "Detect: No available Storage found on device?\n" msgstr "" msgid "Utilize alternate access method" msgstr "" msgid "Disconnected device due to access method change" msgstr "" msgid "The move function is disabled when using the alternate access method for your device." msgstr "" # File:mtp.c, line:88 msgid "Detect: There has been an error connecting. \n" msgstr # File:mtp.c, line:106 msgid "Detect: Unable to open raw device?\n" msgstr # File:interface.c, line:2326 msgid "Device Audio Tracks" msgstr # FilegMTP/README000064401651440000012000000221161205070601200132030ustar00darranstaff00003030200010gMTP v1.3.4 A basic MP3 Player client for Oracle Solaris 10. Supports MTP devices including those with multiple storage devices (typically mobile phones). Supports Drag'n'Drop interface for upload/download of files. See INSTALL for build/install instructions. FAQ Q. What is MTP? A. MTP = Media Transfer Protocol. MTP has been adopted by most major MP3 and Mobile Phone manufacturers as the method of talking to devices to upload/download files to/from a PC. See http://en.wikipedia.org/wiki/Media_Transfer_Protocol for more information. Q. Why doesn't gMTP support my iPod or Creative Nomad player? A. These devices do not use MTP for moving data to/from a device. Apple iPod uses it's own custom protocol in additional to USB Mass Storage and Creative devices use NJB. Q. I have a MTP enabled device and it is connected to my PC, but it doesn't get detected by gMTP? A. Most devices are capable of using different modes to talk to your PC, so ensure that the device is in MTP mode.
    A. Or, libmtp doesn't know about your device or how to handle it correctly. Run '$ mtp-detect' to see if it can be found. Q. I get asked which storage device to connect to when I connect to my mobile phone? A. Some devices (notably mobile phones), have both internal storage (non-removable) and external storage (removable storage) in the form of a micro-SD card or M2 card, and gMTP will treat these as different storage devices. Q. Does gMTP support Albums and uploading Album Art? A. Yes. Album data is autocreated/updated when you upload a MP3 (or other supported audio file) by using information contained within the audio file, eg. Using the ID3 Tag information in an MP3 file. Once the Album has been created, you can upload the album art via the 'Edit / Album Art' menu option. Q. Will this software work on OpenSolaris, Linux, *BSD or other POSIX Operating System? A. I have reports that it runs successfully on OpenSolaris, Arch Linux, Debian and Ubuntu. Q. What about SPARC, ARM or other non-x86 systems? A. It should work fine but is untested. (If libmtp and libid3tag work fine on your platform, then gMTP should as well). Q. Do I need root access to use gMTP? A. On Solaris 10, in general No. (If you do need root access, then double check your RBAC setup for your user then). On Linux, in general No, as libmtp should have set your udev rules correctly for libmtp known devices. Q. In the file view or playlist editor, tracks have a length of 0:00? A. The length field displayed is reliant on the track data being set correctly when the audio file was uploaded. Some file transfer utilies do not set this information correctly (and earlier versions of gMTP are also guilty of this). Simply download and re-upload the audio file using gMTP to correct the track data on the player. Q. I have the same audio file loaded on my device in different formats, but the song duration is different between them? A. WMA, FLAC and OGG all store the song duration in header information, and this is set by the encoder used to create the file. It may be a bug with the encoder? With MP3 files, the track duration is calculated when the file is uploaded, so this information should be correct unless you have a corrupt MP3 file. Q. The translations are pretty awful or just plain wrong, or why don't you have xyz language? A. The initial translations were done using Google Translate services, so accuracy is not 100%. Please email me with corrections to existing translations. If you would like a particular language added, and are happy to assist, please let me know or simply email me with the correct *.po file with the translations for your langauge. Q. I'm using French Canadian (fr_CA.UTF-8) as my locale on Solaris 10, but I don't get French translations? A. This is due to an idiosyncrasy on Solaris 10 and language translations. Either: 1. Copy the gmtp.mo file from /usr/local/share/locale/fr/LC_MESSAGES to /usr/local/share/locale/fr.UTF-8/LC_MESSAGES, and restart gmtp. 2. or, create a symlink fr.UTF-8 pointing to fr in /usr/local/share/locale using 'ln -s fr fr.UTF-8'. French translations should now be present. (Technical information: On Solaris, the gettext() call will only look in the current locale folder as defined by the LC_MESSAGES environment variable and not the base language folder as well for translations, so if the locale is set to 'fr.UTF-8', gettext() will only look in that locale folder and not 'fr' as well - which is what the GNU version of gettext() does). This applies to all languages on Solaris 10. For Linux/FreeBSD uses this should not be an issue as most will use the GNU version of gettext(). Q. The column view options do not appear to be working? A. The gconf schema was updated in v0.8. Please update your local schema file. Q. I'm attempting to move some files, and I always get an error. What's the issue? A. gMTP uses the MTP function 'moveObject' to perform move operations. However only a few devices actually support this function, and if they do actually advertise it supports the function, it may be horribly broken. Basically complain to your device manufacturer that it doesn't support this function, and that they should add it in. To see if you device has this option available, run 'mtp-detect' and look under the supported commands for command '1019: MoveObject'. The other method I could use is to download the files/folders to your PC, re-upload them again and delete the originals, however this is very time intensive, and in these cases it's better than the user do this themselves. Q. I have an Android device and .... isn't working? A. There are a few answers or explanations with Android. 1. Some device vendors have opted to use their own MTP software stack with their device, and some of these implementations are horribly broken. Contact your device manufacturer for further assistance. (AFAIK, this is primarily Samsung with Android 2.x devices, and some lesser known Chinese developed handsets often rebadged as Carrier own-brand handsets). 2. Android 1.x-2.x doesn't have a native MTP implementation, so if your device has MTP functionality, see the above comment. 3. Android 3.x-4.x has native MTP functionality, but is missing some features and does have some bugs in the implementation. Some noted issues: a. Does not support albums or album metadata. So you can't upload custom album art. b. Some users have reported issues with Playlist support. (Try to get a newer revision of your version of Android). c. Android 3.2 has a nasty bug, in that an application is unable to call the Storage APIs more than once in a session. gMTP 1.3.2 works around this by caching the device storage information. (but not the file listing or related metadata). d. Samsung Galaxy and Google Nexus devices have connectivity issues, that I'm hoping can be resolved via patches to libmtp. Sorry, there is nothing I can do about that situation. Complain to Samsung and Google, and try to get them actively involved in libmtp. Also try the alternate access method, as this may improve things. Q. What is the alternate access method? A. There are two main methods of accessing MTP based devices, either cached or uncached. gMTP originally only used the cached method as this worked well for the majority of devices. (All device information including file/track information was cached in the application for performance reasons). With the introduction of Google's MTP stack in Android 3.x, it changed many things, in particular that it only worked well in uncached mode. (Android's MTP stack as far as I know is server implementation that shares the underlying resource with the device and the host PC, unlike the usual MP3 scenario, where the once the host device took connect it was given sole access to the media storage). Because of this shared nature, using cached information in the application is a "really bad idea", since you're relying on information that may change. So gMTP now has the ability to use the uncached mode as well, which should improve stability with Android 3.x and newer devices that use MTP. The downside to the uncached mode, is that every action now requires getting data from the device, which may be painful on slower or congested USB busses... (I would rather stability for users, and have them wait 0.5secs over an unstable and poor experience using the application). (Alternate access method = uncached mode). Q. What does the "g" part in gMTP stand for? It isn't to denote that you're a GNOME based application, is it? (If it does, that's lame). A. While many GNOME applications have a leading G, and likewise many KDE applications have a K, the "g" actually stands for "graphical", as in: "graphical MTP", since its a graphical interface for MTP based devices... Sorry to disappoint people. TODO List 1. Bugfixes as needed. 2. Further translations. was uploaded. Some file transfer utilies do not set this information correctly (and earlier versions of gMTP are also guilty of this). Simply download and re-upload the audio file using gMTP to correct the track data on the player. Q. I have the same audio file loaded on my device in different formats, but the song duration is different between them? A. WMA, FLAC and OGG all store the song duration in header informatgMTP/COPYING000064401651440000012000000030661204772526000133750ustar00darranstaff00003030200010 gMTP License ------------ Copyright (C) 2009-2012, Darran Kartaschew. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of "gMTP Development Team" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 OWNER 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. gMTP/images/000075501651440000012000000000001205070636500136015ustar00darranstaff00003030200010gMTP/images/image-x-generic.png000064401651440000012000000012371157712272100172540ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8MNSan/p QӠ!:e7"Ld N@H#Z~`w~#ό11Mf89HtKvvvF@)u#q^G9L1ycyn7y-V!jL=2vHI@&e@,޼zs9Gw0`~-L/ АZ*#ad WY̼r$ZTFNUN;|G ibݕ]Nk 8xt:3@&I-h Mg z{g_ Y >0ŝX %DQD[̌WX쎃qV@!Jp\&xrsKF N17SL!Ta03fK. ED&' r'h[J)x!Ěq-7 KblzX&t ET:IENDB`gMTP/images/video-x-generic.png000064401651440000012000000013431157712272100172760ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<`IDAT8nGW$K5 E)| n E@ԨDH\Ν lw`s3gԋETRUxv{-)ߍ1n{%?>?]Ҡ HO 9~hquuޟV+xﱵJ b*tV+'4k:qf\C]hkѥ!%cG{pRR5ʹM8[لfZS5{=]Mixn?{5"* )Х,e{@PT6S1(޳\.פH1R"Ō )beVg "qp>"G@\>lr!E{PĄ-"HbXBnnn8::btmϊ} : |j6YkDs.+nLS~[M mKtY|a_yxxiLCUUkֹEe FapTF!rGUU>w]tY~`9RJ֯s\6IENDB`-)ߍ1n{%?>?]Ҡ HO 9~hquuޟV+xﱵJ b*tV+'4k:qf\C]hkѥ!%cG{pRR5ʹM8[لfZS5{=]Mixn?{5"* )Х,e{@PT6S1(޳\.פH1R"Ō )beVg gMTP/images/icon.png000064401651440000012000000064571160207254700152530ustar00darranstaff00003030200010PNG  IHDR00WbKGD pHYs  tIME 2x/ IDAThy]}?yo3=v0;)$R *jT'j?"O )U7PUjVmQPKl"lv0^d88ؓ& iA.uIdZhkLR8T*wy5P*;kqQיVQOξ+g} ?"\5 8܎:|f&,¢q}WX+ 'B*`%c.* E$I0`)];0׊yo1$[Qe' "8٭tU&q9T1R-Nyx( IE+C9D2 8ୃW&#FoZ(PdD8I"&͢N{$lnp)Cf6yiJQKkn8k/?r dQC@ o|"6\sBN%8eH0m 8Աa=TрHJv"E@ p *<RՆD4N4V'PwPM3ւNɟ4Hծ *7!&BMJPCH#i6,f33+aBfBdK VZdo(ɀOI$4oYU"YV#uj1R:R.#\kdeRWƄ2Gvh(I9&CV5F"Mpkf U.4ڌ4M #b3AB$6h8EQW dTLIl-aG+ysLI?a  QEB}ڎ|. hy+.8]aTg{R7JkP [Bʓ˳9D([F^(Ch^DiM~j6I? M$!ISl"EUJg (áצ qG_CHP6`O]hqVhkϫ 6!s:::B6>Y^V$KE aqAHڠTpF<+>oݲ﮻522ruooo^74. iɈTzmK| j@glw^"OncaFFFZvhvvggg_hZ ~ik׮6m= m}0;;'x;ƶ=TJ㜣P,roz'}4< ݨ]{6CY)mg1gwPRJtV080@>j?؇zbDT<< Nu`6@,"Bd$7) FGG`tt ?rȃaڏSO=#ۓ:=rAcZ˔(T*N<8kg)Njʝ oX\ZbA_{I^}lzǵu7xmc|iV sj}n'AhR(j7ZH+xxy` g5VCgdx&'YZ1q""T;s{N8ɓDQt9ܿ9G(s~Cs3iWYݳ02v` Jg5J:+L#'So6)xǍBWBﰲqSNM7'i"FgGx#LL=|t ؾ} g3233Kx@rX e&;'099,Lgvv$N(Ħ;vǏ1=3$gϞ-q͟oV%C_}^8}fm[8}zUbn&~M MU),MR.g0`br_ckpĻIz4a!sDQD$h+Ƙ?9C/~i+J?5g\,>55啃bxhj Q& JkjJYAkCϒ^ؾ-B19G_ai@aA)'YV>wȫns;ipX~T,0`vvMC]aaVT*tvuZN}==vyԱm%v ;) 0V4IqbSN23=5`/dW}_-[䖳gIZ{|.Iʥ2V`yi5ژ-}}\}f "z (B+EEa&<Hmy0UL<<\}R{.wq-ӲT._PS*8ʲ,$76yh?@e1UOIENDB`gMTP/images/media-cdrom-audio.png000064401651440000012000000017621157712272000175750ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<oIDAT8]K#W9I&7M2ys3/n,ELK W$I[!!DW*NN{zz⊢D766)"H7HFQj 9Oٶ]S{{-..?zŻ}{c>ͫ+:R۷m9OIP]bO@݁J$IM&Sϟ?dP(wJd΂+ H c sʘMTATF:.;8:Todx, !(eROҩxZm\}'eJdRޘ1y`QIp~xU?zZ~ϞyHx-Q%P(ʲ ۣ F@뺄n jz,0 nXxPx^W/FF0?R4Xa$ɔsN `_앇_4c5<eøsN3x\ػZ խ*u0=\mX57L95MfpƢm7DV&n5wK6 YN&T*L%յm.GQ|REeh12Y9|>ёs^L񩩩~!wõY s?nUUEV,$[{MIkz} K(GS!%ĂȼPqb%7Hq~%L ~XZw~HeE^|2"_&0Tί!IENDB`gMTP/images/empty.png000064401651440000012000000004671157712272000154540ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8-0DH! [Ta ߏ]T^W;4sYjfR/BH$oeY4M8!Rp'1FHsp&^ f Tt߹/@UA639ARJ }Ƕm?- ۇa?K<IENDB`gMTP/images/audio-x-mpeg.png000064401651440000012000000010521157712272100166020ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8OkQόW& R J7Bgh@ ?B]Y7uiƝݸ+mwt*Pg`;3\%:st:Ƙ4Mp+nE^oTU{4M1 >RyRW ~f a8:S!mqa}1` 0AƐaz1 u.@ ꓌۵"KwGy km. cy:]AIn=…蚀xO : ǐR:gqP#31oWJm̌0J܉%>2s6ݛ٢˳`f.UT5+M+M/1+/R\qO)!ȧR `_8.GB!(˭OIENDB`gMTP/images/view-refresh.png000064401651440000012000000012111160433535000167040ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8OHa?9c Ѓ$!O Ayc7Ib;y a`Sf({`A+h n| }^58<' w{~uֿz|t0m}i)Zkl6tUO1+[cDI~x3>>~ggg窤r11YINAg,KFqQR$[dr$H HTD` -..ncjZ'a}<xG$jL8XkW$ڻm`9S^^^~ |k355T*h3 hZ]]-XkL&ldddw@9:88ѺuIˇRԄ8w]]]7b_SHVqI&~& s\RRE-b$ ]MJKb)npXj7gqS%IENDB`gMTP/images/text-plain.png000064401651440000012000000007171157712272000164010ustar00darranstaff00003030200010PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<LIDAT8=@Q$Sh,tkJˬ0 B UX)'dMlkk*Mw^"lv( Qir8_O#q{ ZKeknRJq\Jok-b)E) ;c\lJ)9O3N `0(9Z[%GȫT&Iɩ.ſ Ctt:A8xabKGDtIME   / IDATxy%W}o|g֒/ $v `c>bǞcO7#g趙h|l`f7YLI@T*Ts̪|"qeTU9{/7"]t.EH"]t.EH"]΁zRQﯸvκ;x tOD6KN?3r{nۺ7/J%|G)=J) *)+BqWRzx~TN((PR_/JIT(RJHYFAZ9V?6]3Bp?2|)%(3y@as>0(}^3W`٤T0P !N'NUZotj|Svoz>J*X(v_#1䕅a>*RaK!4/Su^-(i `+  ڍ_Q-C<ˮ֑DR3=|{:fĹ@)E2H~OdOj!Bx[ ;yM \sUN)ČwU+ìW3)F╈H}5*a$:+αXC {EOO;^;#Z(J=oEi !-`]_RiIaԾ/_2שX(Tt`*WV9T /^;#ʜ1D:NݷϘl,4:<7FȺSf 61Y w>i " $Hf~mZ7bz暩khp(7#a#FTy'K=P46!@I)cD 1<_fX+q?d+̶ҏs. ai5=9JT4"w#3&܏f(|t9i.'5'*|F$_3 (hjpV𘩷9xI;>vAlDU @y>|| g-SIaEa36@D @j-%N5ovL'̠L˜ ^\.P4W/t0>H{ [ YA $4ǭ8bˤy=-qH(5 vSnu2o[XO_޽ HD UwwZCAM2Jv&sF5Mk:[h[f%WX2$Z@伖lЍ ˝*m K<" (D26@EivhM@Kt9@|6ou Jzڅ.-`9kQ< 6PߓW@Zo3D0eNg:ϋ}j 1㕗&g| %!RppWt < UP?@K=+qȤ%8yG g4V3t7q$ N#-#tQ8i8l9@IIݾh\3cٌ@O֮IHhKzVHPqp13:h?az9.BTq߀สn_4+Q\~oEZA-K< H%bPMi[GOYw0a&yp3 "6gZl0; p:%0%ڧ5pqŎoG|/B_4Y+&&.dA7m ߍזԢd)acr(i[{I>f~]&g4MbĴ`@~$^ZɎﭠ浶hK|a%۱h~.a4-ϸ&K@08lo]ArKxnQIfp O/I$hMZ2Ξi(žs4p`miBb3A\'.)`si *uֶYկ0Ԓ.TJ#$9dI.Q=z| Z`m@I9 RTzps-l^|/ǔ.G& YƮp#ĮXXsh6Q,u>iMu60mL^0.@lsBVLTHE=0X{A T /U ݵUAsD ػM:vڑIpWҟZ4盁YNJ?Vv?J%Ƭ=?H}Mk& cfJR%9|n+JD(1Zxgu0e~uJ`fcag4W5 L>BX0"1v0z%p-AKN0jw]#9aLrwނxҚ1IL@=Htb5"N馎cJD.>V!}lZ K(%gn&㦃mܬ-ݬ)cP玱J!+7)c \tS7o}!C@%ủ8V "LHwnf˜ %Q-\Rbt J+b-Bb0USaƭM9N= iZd֥B)NTl#^iWseW*wG)05HgT/:RJbY_I4!KZV'޵^;˝Xb VC0>KRF`5]5NH򃒊Pc GJp)(Wc`yŹ ZMf$7 BzZJfHFqp|_Yj I_f0)|`4 W!of̻+_]M7BV7 t2_uAkq; Db?~?x A%鄡 V':ӷglrƫR*(` Y 5 (J7ڦc˨5.ƛ%3IӅ3kʍ&S'%xBkꁢ$]r?=f?Ltίb ̹:~.t=pmeO$OԀal'i AiGV\@tE+ H3.$ 49 6Z׿tտ\qL[ۢsb&p5r~vt IIgٷnإ?fVw%<-_<2S0yLJh.XH(ɆMzvrcf'i#Xi]i8Y%`@UmVF/i($M?L4 X@ 2 .o'pJBH q$Z-.l:CˇX+$VK"a}DWLq ]l ЎsɠRͩ.poi`9ӏ $:|m@'T$i淣0$ $`o+fNB%0uL6| `#lePL>c7-2JFP.KE}sZ5 p}.;cUnװRΕd>)y~@0y:h AkSAbRߴw)|}M"Q{RК,g2󍪏T;]EZ' AtBJj%B7Ꝿ? ZX]+zOb;@ ~ť2j)M?њ@@%M$VTcIjjh5[M :,N0ҙ|@XP(; |`InC)eAFJԺ!] AkU }}ח8\ E癞VS맷unu{UKy%"{)YTh5ꌏoKvQ,r ^nJZرcL?FR@m,ULe~0B86_ƞl,:wn'"R@&)O||+_ZO,!۷og>|'O2ac~au~/.h<ȼ8`]9m 367i}K># C CNOG?O\0 ^6vmo{[j-5Jr\.?>55(?珝muTY#=;BRn):u:VQq`l7a%ĀW\ 4Mn("JARJfffɍ4TJRi.~>"2r^.4R/U\t;oAk JT*e;_" ht˞3JFN'&saEGLjfrWcm |gdd^Fr333{OkF*0u{èBH;B|O)WpMeKs4?Ǎ:z|/CUQVQ׿ }cЮ'TO%W݀0X'xbs||[ߟŕ\.E??s^я:66v熍Loo2f|ߧ\P(|Ζ!sɞ'la=/цRSsvm)=M~ze[288ȩS8qbborfgKYWK/<(o]z_~PJ1==g?YOVC=;yGy׿l/g9ȦM(!/BrBT+.:JAtDO٤M~HIGĩ1fr92ϐfd3f}} "O"EWrerQCxl>/;yH)rO 8; o|WJQT;Ї><kчĉIٱc;W^uOZ޽{d2|Az{{o?aKV.j0De;"?/[L&͒1f 3FcO6@g»P#Soqr\|>OEt#Č7N".:Ύ}zs9oկ~?Jq P \)AW)E~2Z|JEwfnլ|G61fd}L|'*8mݺ08QH!0 Cc+]|NOO|+ I)y'_}{﹏uFٱs 4s=d-߰Y9Ft~̾'i8##z0GO?MoooZ]<2bƍJZ%J3L׉gԾ$߷o~&K6f!O nhM=û^LX qر8g\'Z$UfKsCCCqHКM`YR cGPٱc;l!>37O%.O ]-=|<#h4Bp嗑꯼+RgeDaD$%$I{N0Bʈ[6Cj4Lu3zqCL8Y($tZD?d2xվI??!plR[B=/yrk@\fKvGFF_X`箝ܵ6/URFaTP޼Wzj뮻gffo禧p*GV-[PJya/|O C=/{A|k==c{B'֒9~ 7蜀B$K[Rt:}#zp˳{Ʊc~sB@ /|G}KMuR:'/@-NW勄A@WRl߱_j"@\=& B;;RG?ގR׾}Ǚѩ)ѵ^Gyhhϳ9~o;΃{333z\~~ocjj; R={.7q$_o {Fo&&!yfW&(}G4z8)%o}7n8IDATh4j5xeԮ%uvr9fme Y?Bk03)@ *yȐޑ<\1B)j/,TYI:Jy8pQQ~T^Xx>:==}LMMn'**Bn_TeM<>:@oźuxLMNi87z .[Tf_!<ǹ桇iP2B6k&yZ,3IH U_Y+D"h'~=_N@EQ$919 zͤ#d?vd}_!(dtd7rľ}.rGA۔R}&<=+{}Ew£ZR,2h ZF!'vBI|d$鴃\ԉ{I@϶!Ge~~Y7=&!=o Ir f@h{"̳=x2=,!#G␮9H jߴ/*"Nfhp__f8ك}MA/(əpmeƣ(صLk^k1::0 `jj߸\.){/ȑc/,0:6J6srvj۹FPaƂfb`Ϗ::L_~@Wh5h=_ l[C+ͺ8)ēORVMHIdӾ 샪6[^u+y7h48pVPvѳz}2GQDQ!C2릍=jk8O ~4M -gn 5)0'a@<3T*>X.RJvؽs' 277'&&1?-[ι2BD*RxQn0ku{5Ogd|0bKy_ƩS߻W|WsvfN_VyWѬ7xXRGtiGfpp0u?zĶkh>,`|I',ӌI<<$ O;B h03= 7"cq4E!CC+jTX+ ZmRy~qL@QRS,d8qdK3݁vASE]IbPwc&ZE|lS(q$V7jVlߺj,,,O"uSF̝:4!Y0雐6MlY?$rx_ bHO1-xr͝ZiFs7qzF9ai4޽kuFLOR^l<%w248H$ 4M)ozkaZrL٤f?q] #)+躀il!$!'ǎgbbB;`~{>Nw17WѣGw/R.Y\\V:oP̝N$;ML)E?w:e?Gg /?p!==):OHۛ>W-PP, $\.'GN" 肠 Z4 |s}ي֏⽟R9L__R(ff%"FP|~-(E~P A{&ZQ(ó+Ҏ*$RRT0"^ṁ0ԦW)EEW<~Y6lߺuE)yϏ|7 qҤR1<4OTQ3nsf"a299'f| 0 $NJtdsxn$Cm_@6&R.3??333vF  NlAvw Ț [gO;JgEpR*pj$^s%r{S@d^uM<;4B:>Αckw(J P.ffYFGRWCb@Vyy !eyOU5f 2"}2z"roF\F OZx /"; y; Zn{cB г/ l8$J`5 =s:wnxŮ\.w@XZZ$GOj%}h ÐY(y@J*0`~'u0}sBfE"ɛ]q%N8 zGQ&LIؤ˫&w ?ř}}L|.4(J?}V ~rȖF?zNkJ}JA~|=,ˌ嘛gedhFI;0MqW7?Kz K|s;q cT(r+nF-Q1 "Fqǒ2Ձ!7mBI)['iBwtM5z,-.A~p4B?ή]ݻu088uӏ0c-TDJhZDX K]mΔ"/ɠܾ}U1n-\fTAoBRj-iGBRB0\ѱul߱a(":LMMNU+Iɥ^FV#NM{'|(LOH.chhjݲe ׯP(,[8~8GgnSW%~:!`sc< @|>w 6"HN,-TPT "BR/===(EA0}b>бJٻuHzvEFjB&cK"XQ2<<CCCz"H33G֎]ӧ9}Z)t2vjjv%|\>x>z%\kR$y"~n]%I _S9f_[*}xXP3m(4}~^vKRFy(=t[F,*\ Qq; Ty`<|B ̷].۲S*_9 9̏n5'9psssx\B>qtR$[6'&VrR 010mN^ h {1~WXz-\s>-_- cŞ顷l6h!b l p4} (i Bt1+Ş&NzP7kwnЏ/uhVL_/dA @I0^eE$Gs4f~7> ؐt58<3AyC1?g:J4SC48%$1>F:EL&Ng2Z7nDc@~N amVs/ ? O2VV5Dp͆D'vd'2\1q# д *fH=*FzyIkh ;9#Z+y,{7vOY# 󻁐iOhߋ]iTJa gH"eREw<;,@(zy9rbC?k \U:{˵@1b>vSxW{_Y?_ F7~3 ^|;ygq)ȣWYeX =vxA} (YK+3ߚ𳜯h+wP9 ZI4KγnϜ4\fBLΒfpSHf|xєH 5Xr…5.Y0pn͖] i$* 9J! Misսx]KDE$l KHoeXlEtn[ $W]\h D?mvRwXw.KK#<IENDB`gMTP/images/stock-about-16.png000064401651440000012000000005001151405040600167510ustar00darranstaff00003030200010PNG  IHDRabKGDIDATxݓNAݸ0[ AYBԉT$[z ֊@ea A2ս6ݛ;73=9lT! oK7_,tmڏ[9|_O.m~{-bq^mlF+`:q:Z%kH gƨrxl_J<HSqzHD ?Xh0[h6+׵0A2} F_W~J,U6sIENDB`