diff options
author | Iain Billett <iainbillett@gmail.com> | 2012-06-22 17:32:08 +0100 |
---|---|---|
committer | Iain Billett <iainbillett@gmail.com> | 2012-06-22 17:33:51 +0100 |
commit | d3949d453d06bbdd88d33dd0d6e660f67fad546a (patch) | |
tree | f415a328321577ba4eb029050c0d8ed09d7fc969 | |
parent | ceb8b18f5b7437ba7438c428c3c78e4d8d67fee3 (diff) |
An new project to combine DocumentLoader with the Android UI. (Not building - see manifest)
59 files changed, 4535 insertions, 0 deletions
diff --git a/android/experimental/LibreOffice4Android/AndroidManifest.xml b/android/experimental/LibreOffice4Android/AndroidManifest.xml new file mode 100644 index 000000000000..f521536fae0e --- /dev/null +++ b/android/experimental/LibreOffice4Android/AndroidManifest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.libreoffice" + android:versionCode="1" + android:versionName="1.0"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + + <application + android:debuggable="true" + android:icon="@drawable/lo_icon" + android:label="@string/app_name" + android:theme="@android:style/Theme.Holo.Light"> + + <!-- Original Document Loader activity - file Viewer --> + <activity android:name=".android.examples.DocumentLoader" + android:label="LO DocumentLoader" + android:configChanges="orientation|keyboardHidden"> + <!-- <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter>--> + </activity> + <!-- File Explorer Activities taken from eclipse workspace --> + <activity + android:name=".ui.LibreOfficeUIActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:theme="@android:style/Theme.Holo.Light" android:name=".ui.WriterViewerActivity"> + <intent-filter android:label="writer_viewer"></intent-filter> + </activity> + <activity android:name=".ui.PreferenceEditor"></activity> + + </application> +</manifest> diff --git a/android/experimental/LibreOffice4Android/Makefile b/android/experimental/LibreOffice4Android/Makefile new file mode 100644 index 000000000000..94233369b0e2 --- /dev/null +++ b/android/experimental/LibreOffice4Android/Makefile @@ -0,0 +1,272 @@ +include ../../../config_host.mk + +# The package of this app +APP_PACKAGE=org.libreoffice.android.examples + +# We can't keep assuming APP_DATA_PATH like this, surely this can vary with +# Android versions and whatnot, this is temporary and works at least with the +# SDK 16 emulator... + +# Probably would be best to just stop fooling around with the possibilities to +# set various stuff with the -env command line parameters (and environment +# variables?) and in a plethora of rc files, and hardcode construction of +# *all* required pathnames based on the app installation location for Android +# (and iOS), etc. We don't really win anything by having so many layers of +# configurability on platforms like Android and iOS where apps based on LO +# code are very much self-contained pre-packaged thingies. +APP_DATA_PATH=/data/data/$(APP_PACKAGE) + +SODEST=libs/armeabi-v7a +OBJLOCAL=obj/local/armeabi-v7a + +define COPYSO +cp $(1) $(SODEST)$(if $(2),/$(2)) && $(STRIP) --strip-debug $(SODEST)$(if $(2),/$(2),/$(notdir $(1))) && \ +cp $(1) $(OBJLOCAL)$(if $(2),/$(2)) +endef + +define COPYJAR +cp $(1) libs +endef + +# The default target just builds. + +all: build-ant + +properties: + echo sdk.dir=$(ANDROID_SDK_HOME) >local.properties + echo sdk.dir=$(ANDROID_SDK_HOME) >../../Bootstrap/local.properties + +copy-stuff: +# First always clean + rm -rf libs $(OBJLOCAL) + mkdir -p $(SODEST) $(OBJLOCAL) +# +# Copy shared libraries (including UNO components) we need to +# libs/armeabi-v7a so that ant will include them in the .apk. +# +# Copy them to obj/local/armeabi-v7a, too, where gdb will look for +# them. +# + for F in $(strip \ + analysislo \ + basebmplo \ + basegfxlo \ + bootstrap.uno \ + comphelpgcc3 \ + ctllo \ + datelo \ + dbaxmllo \ + dbtoolslo \ + evtattlo \ + expwrap.uno \ + fastsax.uno \ + fileacc \ + forlo \ + foruilo \ + frmlo \ + fsstorage.uno \ + gcc3_uno \ + hwplo \ + i18nisolang1gcc3 \ + i18npool.uno \ + i18nutilgcc3 \ + icudatalo \ + icui18nlo \ + iculelo \ + icuuclo \ + introspection.uno \ + java_uno \ + juh \ + juhx \ + jvmaccessgcc3 \ + jvmfwk \ + libotouchlo \ + lo-bootstrap \ + localebe1.uno \ + localedata_en \ + localedata_others \ + lwpftlo \ + mergedlo \ + msfilterlo \ + mswordlo \ + ooxlo \ + reflection.uno \ + reg \ + saxlo \ + sclo \ + scdlo \ + scfiltlo \ + sddlo \ + smdlo \ + sotlo \ + stocservices.uno \ + store \ + svgfilterlo \ + svllo \ + swdlo \ + swlo \ + t602filterlo \ + textinstream.uno \ + tllo \ + ucbhelper4gcc3 \ + ucppkg1 \ + uno_cppu \ + uno_cppuhelpergcc3 \ + uno_sal \ + uno_salhelpergcc3 \ + uno_cppuhelpergcc3 \ + unordflo \ + unoxmllo \ + utllo \ + vbahelperlo \ + vbaswobj.uno \ + wpftdrawlo \ + wpftwriterlo \ + vcllo \ + xml2 \ + xmlfdlo \ + xmlreader \ + xmlsecurity \ + xslt \ + xstor \ + ); do \ + $(call COPYSO,$(OUTDIR)/lib/lib$${F}.so); \ + done +# +# Then the shared GNU C++ library + $(call COPYSO,$(ANDROID_NDK_HOME)/sources/cxx-stl/gnu-libstdc++/libs/armeabi-v7a/libgnustl_shared.so) +# +# Then other "assets". Let the directory structure under assets mimic +# that under solver for now. +# +# Please note that I have no idea what all of this is really necessary and for +# much of this stuff being copied, no idea whether it makes any sense at all. +# Much of this is copy-pasted from android/qa/sc/Makefile (where a couple of +# unit tests for sc are built, and those do seem to mostly work) and +# android/qa/desktop/Makefile (mmeeks's desktop demo, also works to some +# extent). +# + mkdir -p assets/bin/ure assets/lib assets/program assets/xml/ure assets/ComponentTarget/i18npool/util + cp $(OUTDIR)/bin/udkapi.rdb assets/bin + cp $(OUTDIR)/bin/types.rdb assets/bin + cp $(OUTDIR)/bin/ure/types.rdb assets/bin/ure +# For some reason the vnd.sun.star.expand:$LO_LIB_DIR doesn't seem to work, it expands to empty!? +# So just hardcode the known APP_DATA_PATH for now... + for F in xml/services xml/ure/services; do \ + sed -e 's!uri="vnd.sun.star.expand:$$LO_LIB_DIR/!uri="file://$(APP_DATA_PATH)/lib/!g' <$(OUTDIR)/$$F.rdb >assets/$$F.rdb; \ + done + cp $(SRC_ROOT)/odk/examples/java/DocumentHandling/test/test1.odt \ + $(SRC_ROOT)/sc/qa/unit/data/xls/border.xls \ + $(SRC_ROOT)/sw/qa/core/data/odt/test.odt \ + $(SRC_ROOT)/sw/qa/core/data/doc/testVba.doc \ + assets + cp $(WORKDIR)/ComponentTarget/i18npool/util/i18npool.component assets/ComponentTarget/i18npool/util +# + mkdir -p assets/ure/share/misc assets/share/registry/res assets/share/config/soffice.cfg + cp -R $(OUTDIR)/xml/*.xcd assets/share/registry + mv assets/share/registry/fcfg_langpack_en-US.xcd assets/share/registry/res + cp -R $(OUTDIR)/xml/uiconfig/* assets/share/config/soffice.cfg + cp -R $(OUTDIR)/xml/registry/* assets/share/registry +# +# Set up rc, the "inifile". See BootstrapMap::getBaseIni(). As this app +# doesn't use soffice_main() (at least I think it shouldn't), the +# rtl::Bootstrap::setIniFilename() call there that hardcodes +# /assets/program/lofficerc isn't executed. Instead the hardcoding of +# /assets/rc in BootstrapMap::getBaseIni() gets used. + echo '[Bootstrap]' > assets/rc + echo 'Logo=1' >> assets/rc + echo 'NativeProgress=1' >> assets/rc + echo 'URE_BOOTSTRAP=file:///assets/program/fundamentalrc' >> assets/rc +# echo 'RTL_LOGFILE=file:///dev/log/main' >> assets/rc + echo "HOME=$(APP_DATA_PATH)/cache" >> assets/rc + echo "OSL_SOCKET_PATH=$(APP_DATA_PATH)/cache" >> assets/rc +# +# Set up fundamentalrc + echo '[Bootstrap]' > assets/program/fundamentalrc + echo "LO_LIB_DIR=file:$(APP_DATA_PATH)/lib/" >> assets/program/fundamentalrc + echo "URE_LIB_DIR=file://$(APP_DATA_PATH)/lib/" >> assets/program/fundamentalrc # checkme - is this used to find configs ? + echo 'BRAND_BASE_DIR=file:///assets' >> assets/program/fundamentalrc + echo 'CONFIGURATION_LAYERS=xcsxcu:$${BRAND_BASE_DIR}/share/registry module:$${BRAND_BASE_DIR}/share/registry/modules res:$${BRAND_BASE_DIR}/share/registry' >> assets/program/fundamentalrc + echo 'URE_BIN_DIR=file:///assets/ure/bin/dir/not-here/can-we/exec-anyway' >> assets/program/fundamentalrc + echo 'URE_MORE_TYPES=file:///assets/bin/ure/types.rdb file:///assets/bin/types.rdb' >> assets/program/fundamentalrc + echo 'URE_MORE_SERVICES=file:///assets/xml/services.rdb <$$BRAND_BASE_DIR/program/services>*' >> assets/program/fundamentalrc +# +# Set up unorc + echo '[Bootstrap]' > assets/program/unorc + echo "URE_INTERNAL_LIB_DIR=file://$(APP_DATA_PATH)/lib/" >> assets/program/unorc + echo 'UNO_TYPES=file:///assets/bin/ure/types.rdb file:///assets/bin/types.rdb $${URE_MORE_TYPES}' >> assets/program/unorc + echo 'UNO_SERVICES=file:///assets/xml/ure/services.rdb $${URE_MORE_SERVICES}' >> assets/program/unorc +# +# Set up bootstraprc + echo '[Bootstrap]' > assets/program/bootstraprc + echo 'InstallMode=<installmode>' >> assets/program/bootstraprc + echo 'ProductKey=LibreOffice 3.6' >> assets/program/bootstraprc + echo "UserInstallation=file://$(APP_DATA_PATH)" >> assets/program/bootstraprc +# +# Set up versionrc + echo '[Version]' > assets/program/versionrc + echo 'AllLanguages=en-US' >> assets/program/versionrc + echo 'BuildVersion=' >> assets/program/versionrc + echo 'buildid=dead-beef' >> assets/program/versionrc + echo 'ProductBuildid=3' >> assets/program/versionrc + echo 'ProductMajor=360' >> assets/program/versionrc + echo 'ProductMinor=1' >> assets/program/versionrc + echo 'ProductSource=OOO350' >> assets/program/versionrc + echo 'ReferenceOOoMajorMinor=3.6' >> assets/program/versionrc +# +# .res files + mkdir -p assets/program/resource + cp $(OUTDIR)/bin/*en-US.res assets/program/resource +# +# Assets that are unpacked at run-time into the app's data directory. These +# are files read by non-LO code, fontconfig and freetype for now, that doesn't +# understand "/assets" paths. + mkdir -p assets/unpack/etc/fonts + cp fonts.conf assets/unpack/etc/fonts + mkdir -p assets/unpack/user/fonts +# $UserInstallation/user/fonts is added to the fontconfig path in +# vcl/generic/fontmanager/helper.cxx: psp::getFontPath(). UserInstallation is +# set to the app's data dir above. + cp $(OUTDIR)/pck/Liberation*.ttf assets/unpack/user/fonts + cp $(OUTDIR)/pck/Gen*.ttf assets/unpack/user/fonts + cp $(OUTDIR)/pck/opens___.ttf assets/unpack/user/fonts +# +# Then gdbserver and gdb.setup so that we can debug with ndk-gdb. +# + cp $(ANDROID_NDK_HOME)/toolchains/arm-linux-androideabi-4.4.3/prebuilt/gdbserver $(SODEST) + echo set solib-search-path ./obj/local/armeabi-v7a >$(SODEST)/gdb.setup + +build-ant: copy-stuff properties +# +# Copy jar files we need, and even construct one. +# + for F in $(strip \ + java_uno \ + juh \ + jurt \ + ridl \ + unoil \ + unoloader \ + ); do \ + $(call COPYJAR,$(OUTDIR)/bin/$${F}.jar); \ + done +# + unset JAVA_HOME && $(ANT) debug + +install: build-ant + unset JAVA_HOME && $(ANT) debug install + @echo + @echo 'Run it with something like what "make run" does (see Makefile)' + @echo + +uninstall: + $(ANDROID_SDK_HOME)/platform-tools/adb uninstall $(APP_PACKAGE) + +run: +# /data/local/tmp/sample-document.odt + adb shell am start -n org.libreoffice.android.examples/.DocumentLoader -e input /assets/test1.odt + + +clean: properties + $(ANT) clean + rm -rf assets libs $(SODEST) $(OBJLOCAL) diff --git a/android/experimental/LibreOffice4Android/build.xml b/android/experimental/LibreOffice4Android/build.xml new file mode 100644 index 000000000000..5d960196c6b0 --- /dev/null +++ b/android/experimental/LibreOffice4Android/build.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="LibreOfficeDocumentLoader" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <loadproperties srcFile="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'" + unless="sdk.dir" + /> + + +<!-- extension targets. Uncomment the ones where you want to do custom work + in between standard targets --> +<!-- + <target name="-pre-build"> + </target> + <target name="-pre-compile"> + </target> + + /* This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir} */ + <target name="-post-compile"> + </target> +--> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + + <!-- Re-define the "-package-resources" target to not compress resources --> + + <target name="-package-resources" depends="-crunch"> + <!-- only package resources if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not package resources..." > + <aapt executable="${aapt}" + command="package" + versioncode="${version.code}" + versionname="${version.name}" + debug="${build.is.packaging.debug}" + manifest="AndroidManifest.xml" + assets="${asset.absolute.dir}" + androidjar="${android.jar}" + apkfolder="${out.absolute.dir}" + nocrunch="${build.packaging.nocrunch}" + resourcefilename="${resource.package.file.name}" + resourcefilter="${aapt.resource.filter}" + projectLibrariesResName="project.libraries.res" + projectLibrariesPackageName="project.libraries.package" + previousBuildType="${build.last.target}" + buildType="${build.target}"> + <res path="${out.res.absolute.dir}" /> + <res path="${resource.absolute.dir}" /> + <nocompress /> <!-- forces no compression on any files in assets or res/raw --> + <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw --> + </aapt> + </do-only-if-not-library> + </target> + +</project> diff --git a/android/experimental/LibreOffice4Android/fonts.conf b/android/experimental/LibreOffice4Android/fonts.conf new file mode 100644 index 000000000000..699e9d101048 --- /dev/null +++ b/android/experimental/LibreOffice4Android/fonts.conf @@ -0,0 +1,154 @@ +<?xml version="1.0"?> +<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> +<!-- /etc/fonts/fonts.conf file to configure system font access --> +<fontconfig> + +<!-- Font directory list --> + + <dir>/system/fonts</dir> + + <alias> + <family>serif</family> + <prefer> + <family>Droid Serif</family> + </prefer> + </alias> + <alias> + <family>sans-serif</family> + <prefer> + <family>Roboto</family> + <family>Droid Sans Fallback</family> + </prefer> + </alias> + <alias> + <family>monospace</family> + <prefer> + <family>Droid Sans Mono</family> + </prefer> + </alias> + +<!-- + Accept deprecated 'mono' alias, replacing it with 'monospace' +--> + <match target="pattern"> + <test qual="any" name="family"> + <string>mono</string> + </test> + <edit name="family" mode="assign"> + <string>monospace</string> + </edit> + </match> + +<!-- + Accept alternate 'sans serif' spelling, replacing it with 'sans-serif' +--> + <match target="pattern"> + <test qual="any" name="family"> + <string>sans serif</string> + </test> + <edit name="family" mode="assign"> + <string>sans-serif</string> + </edit> + </match> + +<!-- + Accept deprecated 'sans' alias, replacing it with 'sans-serif' +--> + <match target="pattern"> + <test qual="any" name="family"> + <string>sans</string> + </test> + <edit name="family" mode="assign"> + <string>sans-serif</string> + </edit> + </match> + +<!-- + Load local system customization file +--> + <include ignore_missing="yes">conf.d</include> + +<!-- Font cache directory list --> + + <!-- Yeah this hardcoding is wrong of course, will have to fix + later to patch in proper code in fontonfig on Android to + find out a good place. + --> + <cachedir>/data/data/org.libreoffice.android.examples/fontconfig</cachedir> + + <config> +<!-- + These are the default Unicode chars that are expected to be blank + in fonts. All other blank chars are assumed to be broken and + won't appear in the resulting charsets + --> + <blank> + <int>0x0020</int> <!-- SPACE --> + <int>0x00A0</int> <!-- NO-BREAK SPACE --> + <int>0x00AD</int> <!-- SOFT HYPHEN --> + <int>0x034F</int> <!-- COMBINING GRAPHEME JOINER --> + <int>0x0600</int> <!-- ARABIC NUMBER SIGN --> + <int>0x0601</int> <!-- ARABIC SIGN SANAH --> + <int>0x0602</int> <!-- ARABIC FOOTNOTE MARKER --> + <int>0x0603</int> <!-- ARABIC SIGN SAFHA --> + <int>0x06DD</int> <!-- ARABIC END OF AYAH --> + <int>0x070F</int> <!-- SYRIAC ABBREVIATION MARK --> + <int>0x115F</int> <!-- HANGUL CHOSEONG FILLER --> + <int>0x1160</int> <!-- HANGUL JUNGSEONG FILLER --> + <int>0x1680</int> <!-- OGHAM SPACE MARK --> + <int>0x17B4</int> <!-- KHMER VOWEL INHERENT AQ --> + <int>0x17B5</int> <!-- KHMER VOWEL INHERENT AA --> + <int>0x180E</int> <!-- MONGOLIAN VOWEL SEPARATOR --> + <int>0x2000</int> <!-- EN QUAD --> + <int>0x2001</int> <!-- EM QUAD --> + <int>0x2002</int> <!-- EN SPACE --> + <int>0x2003</int> <!-- EM SPACE --> + <int>0x2004</int> <!-- THREE-PER-EM SPACE --> + <int>0x2005</int> <!-- FOUR-PER-EM SPACE --> + <int>0x2006</int> <!-- SIX-PER-EM SPACE --> + <int>0x2007</int> <!-- FIGURE SPACE --> + <int>0x2008</int> <!-- PUNCTUATION SPACE --> + <int>0x2009</int> <!-- THIN SPACE --> + <int>0x200A</int> <!-- HAIR SPACE --> + <int>0x200B</int> <!-- ZERO WIDTH SPACE --> + <int>0x200C</int> <!-- ZERO WIDTH NON-JOINER --> + <int>0x200D</int> <!-- ZERO WIDTH JOINER --> + <int>0x200E</int> <!-- LEFT-TO-RIGHT MARK --> + <int>0x200F</int> <!-- RIGHT-TO-LEFT MARK --> + <int>0x2028</int> <!-- LINE SEPARATOR --> + <int>0x2029</int> <!-- PARAGRAPH SEPARATOR --> + <int>0x202A</int> <!-- LEFT-TO-RIGHT EMBEDDING --> + <int>0x202B</int> <!-- RIGHT-TO-LEFT EMBEDDING --> + <int>0x202C</int> <!-- POP DIRECTIONAL FORMATTING --> + <int>0x202D</int> <!-- LEFT-TO-RIGHT OVERRIDE --> + <int>0x202E</int> <!-- RIGHT-TO-LEFT OVERRIDE --> + <int>0x202F</int> <!-- NARROW NO-BREAK SPACE --> + <int>0x205F</int> <!-- MEDIUM MATHEMATICAL SPACE --> + <int>0x2060</int> <!-- WORD JOINER --> + <int>0x2061</int> <!-- FUNCTION APPLICATION --> + <int>0x2062</int> <!-- INVISIBLE TIMES --> + <int>0x2063</int> <!-- INVISIBLE SEPARATOR --> + <int>0x206A</int> <!-- INHIBIT SYMMETRIC SWAPPING --> + <int>0x206B</int> <!-- ACTIVATE SYMMETRIC SWAPPING --> + <int>0x206C</int> <!-- INHIBIT ARABIC FORM SHAPING --> + <int>0x206D</int> <!-- ACTIVATE ARABIC FORM SHAPING --> + <int>0x206E</int> <!-- NATIONAL DIGIT SHAPES --> + <int>0x206F</int> <!-- NOMINAL DIGIT SHAPES --> + <int>0x2800</int> <!-- BRAILLE PATTERN BLANK --> + <int>0x3000</int> <!-- IDEOGRAPHIC SPACE --> + <int>0x3164</int> <!-- HANGUL FILLER --> + <int>0xFEFF</int> <!-- ZERO WIDTH NO-BREAK SPACE --> + <int>0xFFA0</int> <!-- HALFWIDTH HANGUL FILLER --> + <int>0xFFF9</int> <!-- INTERLINEAR ANNOTATION ANCHOR --> + <int>0xFFFA</int> <!-- INTERLINEAR ANNOTATION SEPARATOR --> + <int>0xFFFB</int> <!-- INTERLINEAR ANNOTATION TERMINATOR --> + </blank> +<!-- + Rescan configuration every 3600 seconds when FcFontSetList is called + --> + <rescan> + <int>3600</int> + </rescan> + </config> + +</fontconfig> diff --git a/android/experimental/LibreOffice4Android/jni/Android.mk b/android/experimental/LibreOffice4Android/jni/Android.mk new file mode 100644 index 000000000000..939a1ea503bb --- /dev/null +++ b/android/experimental/LibreOffice4Android/jni/Android.mk @@ -0,0 +1,8 @@ +# Needed just to satisfy ndk-gdb for now, but maybe later we will actually add +# some JNI code here + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +include $(BUILD_SHARED_LIBRARY) diff --git a/android/experimental/LibreOffice4Android/jni/Application.mk b/android/experimental/LibreOffice4Android/jni/Application.mk new file mode 100644 index 000000000000..f326d1a59879 --- /dev/null +++ b/android/experimental/LibreOffice4Android/jni/Application.mk @@ -0,0 +1,3 @@ +# File needed by ndk-gdb +APP_ABI := armeabi-v7a +APP_PLATFORM := android-14 diff --git a/android/experimental/LibreOffice4Android/project.properties b/android/experimental/LibreOffice4Android/project.properties new file mode 100644 index 000000000000..06b2d880c3d4 --- /dev/null +++ b/android/experimental/LibreOffice4Android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-14 + +# Use the Bootstrap class +android.library.reference.1=../../Bootstrap diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png Binary files differnew file mode 100644 index 000000000000..e6b70451863a --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/action_search.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png Binary files differnew file mode 100644 index 000000000000..729dbcd82ebf --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/base.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png Binary files differnew file mode 100644 index 000000000000..a3f5fd4d80c0 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/calc.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png Binary files differnew file mode 100644 index 000000000000..b3ee11426a04 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/draw.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png Binary files differnew file mode 100644 index 000000000000..c58d276e7085 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/dummy_page.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png Binary files differnew file mode 100644 index 000000000000..9c9b42c83956 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/folder.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..8074c4c571b8 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/ic_launcher.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png Binary files differnew file mode 100644 index 000000000000..5909f05bf089 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/impress.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png Binary files differnew file mode 100644 index 000000000000..3b34aaf8ab57 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_sort_by_size.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png Binary files differnew file mode 100644 index 000000000000..ae138edbf006 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_grid.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png Binary files differnew file mode 100644 index 000000000000..c5f6c97b2687 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/light_view_as_list.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png Binary files differnew file mode 100644 index 000000000000..2ef86417e69e --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/lo_icon.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png Binary files differnew file mode 100644 index 000000000000..7e8e2a05e2da --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/main.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png Binary files differnew file mode 100644 index 000000000000..50b8dc863bff --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/math.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png Binary files differnew file mode 100644 index 000000000000..7e8e2a05e2da --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/startcenter.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png b/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png Binary files differnew file mode 100644 index 000000000000..2f4abcb280cd --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-hdpi/writer.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png Binary files differnew file mode 100644 index 000000000000..c58d276e7085 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/dummy_page.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..1095584ec21f --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/ic_launcher.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png Binary files differnew file mode 100644 index 000000000000..95b3113b6f95 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-ldpi/lo_icon.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png b/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..a07c69fa5a0f --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-mdpi/ic_launcher.png diff --git a/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png b/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png Binary files differnew file mode 100644 index 000000000000..4f3f89beadc2 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/drawable-mdpi/lo_icon.png diff --git a/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml b/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml new file mode 100644 index 000000000000..ce42e577fa95 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/layout/file_explorer_grid_item.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ImageView + android:id="@+id/grid_item_image" + android:layout_width="50dp" + android:layout_height="75dp" + android:paddingTop="15dp" + android:paddingBottom="10dp" + android:layout_gravity="center" > + </ImageView> + + <TextView + android:id="@+id/grid_item_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@+id/label" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:layout_gravity="center" + android:textSize="15dp" + android:textStyle="bold" + android:maxLines="2"> + </TextView> + +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/layout/file_grid.xml b/android/experimental/LibreOffice4Android/res/layout/file_grid.xml new file mode 100644 index 000000000000..1e241c00a13a --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/layout/file_grid.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <GridView + android:id="@+id/file_explorer_grid_view" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:columnWidth="120dp" + android:numColumns="auto_fit" + android:verticalSpacing="10dp" + android:horizontalSpacing="10dp" + android:stretchMode="columnWidth" + android:gravity="center"> + </GridView> + + +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/layout/file_list.xml b/android/experimental/LibreOffice4Android/res/layout/file_list.xml new file mode 100644 index 000000000000..6ef02555a8a3 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/layout/file_list.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ListView + android:id="@+id/file_explorer_list_view" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + ></ListView> + +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml b/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml new file mode 100644 index 000000000000..0bff445659a0 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/layout/file_list_item.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="48dp" + android:orientation="horizontal" > + <ImageView + android:id="@+id/file_list_item_icon" + android:layout_height="match_parent" + android:layout_width="32dp" + android:layout_margin="8dp" + android:layout_gravity="center"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="48dp" + android:orientation="horizontal"> + <TextView + android:id="@+id/file_list_item_name" + android:layout_height="48dp" + android:layout_width="0dp" + android:textSize="15dp" + android:textStyle="bold" + android:layout_weight="2" + android:gravity="center"/> + <TextView + android:id="@+id/file_list_item_size" + android:layout_height="48dp" + android:layout_width="0dp" + android:textSize="15dp" + android:textStyle="bold" + android:layout_weight="1" + android:gravity="center"/> + <TextView + android:id="@+id/file_list_item_date" + android:layout_height="48dp" + android:layout_width="0dp" + android:textSize="15dp" + android:textStyle="bold" + android:layout_weight="2" + android:gravity="center"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/layout/main.xml b/android/experimental/LibreOffice4Android/res/layout/main.xml new file mode 100644 index 000000000000..6b97fe101177 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/layout/main.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="#aaa" + android:orientation="vertical" + > + + <org.libreoffice.ui.PageView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + /> + + +</LinearLayout>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/menu/view_menu.xml b/android/experimental/LibreOffice4Android/res/menu/view_menu.xml new file mode 100644 index 000000000000..87270d3a5985 --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/menu/view_menu.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_search" + android:icon="@drawable/action_search" + android:showAsAction="always" /> + <item android:id="@+id/menu_view_toggle" + android:title="@string/grid_view"/> + <item android:id="@+id/menu_sort_size" + android:title="@string/menu_sort_size" + android:onClick="sortFiles"/> + <item android:id="@+id/menu_sort_az" + android:title="@string/menu_sort_az" + android:onClick="sortFiles"/> + <item android:id="@+id/menu_sort_modified" + android:title="@string/menu_sort_modified" + android:onClick="sortFiles"/> + <item android:id="@+id/menu_preferences" + android:title="@string/menu_preferences" + android:onClick="editPreferences"/> +</menu>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/values/arrays.xml b/android/experimental/LibreOffice4Android/res/values/arrays.xml new file mode 100644 index 000000000000..67a157d201bf --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/values/arrays.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <integer-array name="FilterTypeValues"> + <item >-1</item> + <item >0</item> + <item >1</item> + <item >2</item> + </integer-array> + <string-array name="FilterTypeStringValues"> + <item >-1</item> + <item >0</item> + <item >1</item> + <item >2</item> + </string-array> + <string-array name="SortModeStringValues"> + <item >0</item> + <item >1</item> + <item >2</item> + <item >3</item> + <item >4</item> + <item >5</item> + </string-array> + <!-- View Mode names,values --> + <string-array name="ViewModeNames"> + <item >Grid</item> + <item >List</item> + </string-array> + <string-array name="ViewModeStringValues"> + <item >0</item> + <item >1</item> + </string-array> + + <!-- Preference Name Arrays --> + <string-array name="file_view_modes"> + <item >EVERYTHING</item> + <item >DOCUMENTS</item> + <item >SPREADSHEETS</item> + <item >PRESENTATIONS</item> + </string-array> + <string-array name="FilterTypeNames"> + <item >Everything</item> + <item >Documents</item> + <item >Spreadsheets</item> + <item >Presentations</item> + </string-array> + <string-array name="SortModeNames"> + <item >A-Z</item> + <item >Z-A</item> + <item >Oldest First</item> + <item >Newest First</item> + <item >Largest First</item> + <item >Smallest First</item> + </string-array> + + +</resources>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/values/strings.xml b/android/experimental/LibreOffice4Android/res/values/strings.xml new file mode 100644 index 000000000000..8951dd495b8a --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/values/strings.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">LibreOfficeUI</string> + <string name="menu_search">Search</string> + <string name="list_view">List</string> + <string name="grid_view">Grid</string> + <string name="menu_sort_size">Sort By Size</string> + <string name="menu_sort_az">Sort A-Z</string> + <string name="menu_sort_modified">Sort by Date</string> + <string name="menu_preferences">Preferences</string> + <!-- Pref keys as resources ; Not currently used --> + <string name="EXPLORER_VIEW_TYPE_KEY">EXPLORER_VIEW_TYPE</string> + <string name="CURRENT_DIRECTORY_KEY">CURRENT_DIRECTORY</string> + + +</resources>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml b/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml new file mode 100644 index 000000000000..d19d9e65298c --- /dev/null +++ b/android/experimental/LibreOffice4Android/res/xml/libreoffice_preferences.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > + <ListPreference + android:title="Default File Filter" + android:summary="Set which files to show by default" + android:entries="@array/FilterTypeNames" + android:entryValues="@array/FilterTypeStringValues" + android:key="FILTER_MODE"/> + <ListPreference + android:summary="Select how to order files; A-Z, by size, etc." + android:key="SORT_MODE" + android:title="File Order" android:entries="@array/SortModeNames" android:entryValues="@array/SortModeStringValues"/> + <ListPreference + android:entries="@array/ViewModeNames" + android:entryValues="@array/ViewModeStringValues" + android:title="Default File Explorer View" + android:key="EXPLORER_VIEW_TYPE" + android:summary="View files as a grid or in a list. #not functional, yet."/> + + +</PreferenceScreen>
\ No newline at end of file diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java new file mode 100644 index 000000000000..993620893f91 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/Animation.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +/** + * @author Jason Polites + * + */ +public interface Animation { + + /** + * Transforms the view. + * @param view + * @param diffTime + * @return true if this animation should remain active. False otherwise. + */ + public boolean update(GestureImageView view, long time); + +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java b/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java new file mode 100644 index 000000000000..fb0728b7bf13 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/Animator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + + +/** + * @author Jason Polites + * + */ +public class Animator extends Thread { + + private GestureImageView view; + private Animation animation; + private boolean running = false; + private boolean active = false; + private long lastTime = -1L; + + public Animator(GestureImageView view, String threadName) { + super(threadName); + this.view = view; + } + + @Override + public void run() { + + running = true; + + while(running) { + + while(active && animation != null) { + long time = System.currentTimeMillis(); + active = animation.update(view, time - lastTime); + view.redraw(); + lastTime = time; + + while(active) { + try { + if(view.waitForDraw(32)) { // 30Htz + break; + } + } + catch (InterruptedException ignore) { + active = false; + } + } + } + + synchronized(this) { + if(running) { + try { + wait(); + } + catch (InterruptedException ignore) {} + } + } + } + } + + public synchronized void finish() { + running = false; + active = false; + notifyAll(); + } + + public void play(Animation transformer) { + if(active) { + cancel(); + } + this.animation = transformer; + + activate(); + } + + public synchronized void activate() { + lastTime = System.currentTimeMillis(); + active = true; + notifyAll(); + } + + public void cancel() { + active = false; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java new file mode 100644 index 000000000000..3124b6201464 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimation.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +/** + * @author Jason Polites + * + */ +public class FlingAnimation implements Animation { + + private float velocityX; + private float velocityY; + + private float factor = 0.85f; + + private float threshold = 10; + + private FlingAnimationListener listener; + + /* (non-Javadoc) + * @see com.polites.android.Transformer#update(com.polites.android.GestureImageView, long) + */ + @Override + public boolean update(GestureImageView view, long time) { + float seconds = (float) time / 1000.0f; + + float dx = velocityX * seconds; + float dy = velocityY * seconds; + + velocityX *= factor; + velocityY *= factor; + + boolean active = (Math.abs(velocityX) > threshold && Math.abs(velocityY) > threshold); + + if(listener != null) { + listener.onMove(dx, dy); + + if(!active) { + listener.onComplete(); + } + } + + return active; + } + + public void setVelocityX(float velocityX) { + this.velocityX = velocityX; + } + + public void setVelocityY(float velocityY) { + this.velocityY = velocityY; + } + + public void setFactor(float factor) { + this.factor = factor; + } + + public void setListener(FlingAnimationListener listener) { + this.listener = listener; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java new file mode 100644 index 000000000000..b9611d51c040 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingAnimationListener.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + + +/** + * @author Jason Polites + * + */ +public interface FlingAnimationListener { + + public void onMove(float x, float y); + + public void onComplete(); + +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java new file mode 100644 index 000000000000..ab3007a14b00 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/FlingListener.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + + +/** + * @author Jason Polites + * + */ +public class FlingListener extends SimpleOnGestureListener { + + private float velocityX; + private float velocityY; + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + this.velocityX = velocityX; + this.velocityY = velocityY; + return true; + } + + public float getVelocityX() { + return velocityX; + } + + public float getVelocityY() { + return velocityY; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java new file mode 100644 index 000000000000..1cde6e4b4889 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageView.java @@ -0,0 +1,712 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import java.io.InputStream; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import android.content.Context; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; + +public class GestureImageView extends ImageView { + + public static final String GLOBAL_NS = "http://schemas.android.com/apk/res/android"; + public static final String LOCAL_NS = "http://schemas.polites.com/android"; + + private final Semaphore drawLock = new Semaphore(0); + private Animator animator; + + private Drawable drawable; + + private float x = 0, y = 0; + + private boolean layout = false; + + private float scaleAdjust = 1.0f; + private float startingScale = -1.0f; + + private float scale = 1.0f; + private float maxScale = 5.0f; + private float minScale = 0.75f; + private float fitScaleHorizontal = 1.0f; + private float fitScaleVertical = 1.0f; + private float rotation = 0.0f; + + private float centerX; + private float centerY; + + private Float startX, startY; + + private int hWidth; + private int hHeight; + + private int resId = -1; + private boolean recycle = false; + private boolean strict = false; + + private int displayHeight; + private int displayWidth; + + private int alpha = 255; + private ColorFilter colorFilter; + + private int deviceOrientation = -1; + private int imageOrientation; + + private GestureImageViewListener gestureImageViewListener; + private GestureImageViewTouchListener gestureImageViewTouchListener; + + private OnTouchListener customOnTouchListener; + private OnClickListener onClickListener; + + public GestureImageView(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs); + } + + public GestureImageView(Context context, AttributeSet attrs) { + super(context, attrs); + + String scaleType = attrs.getAttributeValue(GLOBAL_NS, "scaleType"); + + if(scaleType == null || scaleType.trim().length() == 0) { + setScaleType(ScaleType.CENTER_INSIDE); + } + + String strStartX = attrs.getAttributeValue(LOCAL_NS, "start-x"); + String strStartY = attrs.getAttributeValue(LOCAL_NS, "start-y"); + + if(strStartX != null && strStartX.trim().length() > 0) { + startX = Float.parseFloat(strStartX); + } + + if(strStartY != null && strStartY.trim().length() > 0) { + startY = Float.parseFloat(strStartY); + } + + setStartingScale(attrs.getAttributeFloatValue(LOCAL_NS, "start-scale", startingScale)); + setMinScale(attrs.getAttributeFloatValue(LOCAL_NS, "min-scale", minScale)); + setMaxScale(attrs.getAttributeFloatValue(LOCAL_NS, "max-scale", maxScale)); + setStrict(attrs.getAttributeBooleanValue(LOCAL_NS, "strict", strict)); + setRecycle(attrs.getAttributeBooleanValue(LOCAL_NS, "recycle", recycle)); + + initImage(); + } + + public GestureImageView(Context context) { + super(context); + setScaleType(ScaleType.CENTER_INSIDE); + initImage(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + if(drawable != null) { + int orientation = getResources().getConfiguration().orientation; + if(orientation == Configuration.ORIENTATION_LANDSCAPE) { + displayHeight = MeasureSpec.getSize(heightMeasureSpec); + + if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) { + float ratio = (float) getImageWidth() / (float) getImageHeight(); + displayWidth = Math.round( (float) displayHeight * ratio) ; + } + else { + displayWidth = MeasureSpec.getSize(widthMeasureSpec); + } + } + else { + displayWidth = MeasureSpec.getSize(widthMeasureSpec); + if(getLayoutParams().height == LayoutParams.WRAP_CONTENT) { + float ratio = (float) getImageHeight() / (float) getImageWidth(); + displayHeight = Math.round( (float) displayWidth * ratio) ; + } + else { + displayHeight = MeasureSpec.getSize(heightMeasureSpec); + } + } + } + else { + displayHeight = MeasureSpec.getSize(heightMeasureSpec); + displayWidth = MeasureSpec.getSize(widthMeasureSpec); + } + + setMeasuredDimension(displayWidth, displayHeight); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if(changed || !layout) { + setupCanvas(displayWidth, displayHeight, getResources().getConfiguration().orientation); + } + } + + protected void setupCanvas(int measuredWidth, int measuredHeight, int orientation) { + + if(deviceOrientation != orientation) { + layout = false; + deviceOrientation = orientation; + } + + if(drawable != null && !layout) { + int imageWidth = getImageWidth(); + int imageHeight = getImageHeight(); + + hWidth = Math.round(((float)imageWidth / 2.0f)); + hHeight = Math.round(((float)imageHeight / 2.0f)); + + measuredWidth -= (getPaddingLeft() + getPaddingRight()); + measuredHeight -= (getPaddingTop() + getPaddingBottom()); + + computeCropScale(imageWidth, imageHeight, measuredWidth, measuredHeight); + + if(startingScale <= 0.0f) { + computeStartingScale(imageWidth, imageHeight, measuredWidth, measuredHeight); + } + + scaleAdjust = startingScale; + + this.centerX = (float) measuredWidth / 2.0f; + this.centerY = (float) measuredHeight / 2.0f; + + if(startX == null) { + x = centerX; + } + else { + x = startX; + } + + if(startY == null) { + y = centerY; + } + else { + y = startY; + } + + gestureImageViewTouchListener = new GestureImageViewTouchListener(this, measuredWidth, measuredHeight); + + if(isLandscape()) { + gestureImageViewTouchListener.setMinScale(minScale * fitScaleHorizontal); + } + else { + gestureImageViewTouchListener.setMinScale(minScale * fitScaleVertical); + } + + + gestureImageViewTouchListener.setMaxScale(maxScale * startingScale); + + gestureImageViewTouchListener.setFitScaleHorizontal(fitScaleHorizontal); + gestureImageViewTouchListener.setFitScaleVertical(fitScaleVertical); + gestureImageViewTouchListener.setCanvasWidth(measuredWidth); + gestureImageViewTouchListener.setCanvasHeight(measuredHeight); + gestureImageViewTouchListener.setOnClickListener(onClickListener); + + drawable.setBounds(-hWidth,-hHeight,hWidth,hHeight); + + super.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if(customOnTouchListener != null) { + customOnTouchListener.onTouch(v, event); + } + return gestureImageViewTouchListener.onTouch(v, event); + } + }); + + layout = true; + } + } + + protected void computeCropScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) { + fitScaleHorizontal = (float) measuredWidth / (float) imageWidth; + fitScaleVertical = (float) measuredHeight / (float) imageHeight; + } + + protected void computeStartingScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) { + switch(getScaleType()) { + case CENTER: + // Center the image in the view, but perform no scaling. + startingScale = 1.0f; + break; + + case CENTER_CROP: + startingScale = Math.max((float) measuredHeight / (float) imageHeight, (float) measuredWidth/ (float) imageWidth); + break; + + case CENTER_INSIDE: + if(isLandscape()) { + startingScale = fitScaleHorizontal; + } + else { + startingScale = fitScaleVertical; + } + break; + } + } + + protected boolean isRecycled() { + if(drawable != null && drawable instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap(); + if(bitmap != null) { + return bitmap.isRecycled(); + } + } + return false; + } + + protected void recycle() { + if(recycle && drawable != null && drawable instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap(); + if(bitmap != null) { + bitmap.recycle(); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + if(layout) { + if(drawable != null && !isRecycled()) { + canvas.save(); + + float adjustedScale = scale * scaleAdjust; + + canvas.translate(x, y); + + if(rotation != 0.0f) { + canvas.rotate(rotation); + } + + if(adjustedScale != 1.0f) { + canvas.scale(adjustedScale, adjustedScale); + } + + drawable.draw(canvas); + + canvas.restore(); + } + + if(drawLock.availablePermits() <= 0) { + drawLock.release(); + } + } + } + + /** + * Waits for a draw + * @param max time to wait for draw (ms) + * @throws InterruptedException + */ + public boolean waitForDraw(long timeout) throws InterruptedException { + return drawLock.tryAcquire(timeout, TimeUnit.MILLISECONDS); + } + + @Override + protected void onAttachedToWindow() { + animator = new Animator(this, "GestureImageViewAnimator"); + animator.start(); + + if(resId >= 0 && drawable == null) { + setImageResource(resId); + } + + super.onAttachedToWindow(); + } + + public void animationStart(Animation animation) { + if(animator != null) { + animator.play(animation); + } + } + + public void animationStop() { + if(animator != null) { + animator.cancel(); + } + } + + @Override + protected void onDetachedFromWindow() { + if(animator != null) { + animator.finish(); + } + if(recycle && drawable != null && !isRecycled()) { + recycle(); + drawable = null; + } + super.onDetachedFromWindow(); + } + + protected void initImage() { + if(this.drawable != null) { + this.drawable.setAlpha(alpha); + this.drawable.setFilterBitmap(true); + if(colorFilter != null) { + this.drawable.setColorFilter(colorFilter); + } + } + + if(!layout) { + requestLayout(); + redraw(); + } + } + + public void setImageBitmap(Bitmap image) { + this.drawable = new BitmapDrawable(getResources(), image); + initImage(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + this.drawable = drawable; + initImage(); + } + + public void setImageResource(int id) { + if(this.drawable != null) { + this.recycle(); + } + if(id >= 0) { + this.resId = id; + setImageDrawable(getContext().getResources().getDrawable(id)); + } + } + + public int getScaledWidth() { + return Math.round(getImageWidth() * getScale()); + } + + public int getScaledHeight() { + return Math.round(getImageHeight() * getScale()); + } + + public int getImageWidth() { + if(drawable != null) { + return drawable.getIntrinsicWidth(); + } + return 0; + } + + public int getImageHeight() { + if(drawable != null) { + return drawable.getIntrinsicHeight(); + } + return 0; + } + + public void moveBy(float x, float y) { + this.x += x; + this.y += y; + } + + public void setPosition(float x, float y) { + this.x = x; + this.y = y; + } + + public void redraw() { + postInvalidate(); + } + + public void setMinScale(float min) { + this.minScale = min; + if(gestureImageViewTouchListener != null) { + gestureImageViewTouchListener.setMinScale(min * fitScaleHorizontal); + } + } + + public void setMaxScale(float max) { + this.maxScale = max; + if(gestureImageViewTouchListener != null) { + gestureImageViewTouchListener.setMaxScale(max * startingScale); + } + } + + public void setScale(float scale) { + scaleAdjust = scale; + } + + public float getScale() { + return scaleAdjust; + } + + public float getImageX() { + return x; + } + + public float getImageY() { + return y; + } + + public boolean isStrict() { + return strict; + } + + public void setStrict(boolean strict) { + this.strict = strict; + } + + public boolean isRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public void reset() { + x = centerX; + y = centerY; + scaleAdjust = startingScale; + redraw(); + } + + public void setRotation(float rotation) { + this.rotation = rotation; + } + + public void setGestureImageViewListener(GestureImageViewListener pinchImageViewListener) { + this.gestureImageViewListener = pinchImageViewListener; + } + + public GestureImageViewListener getGestureImageViewListener() { + return gestureImageViewListener; + } + + @Override + public Drawable getDrawable() { + return drawable; + } + + @Override + public void setAlpha(int alpha) { + this.alpha = alpha; + if(drawable != null) { + drawable.setAlpha(alpha); + } + } + + @Override + public void setColorFilter(ColorFilter cf) { + this.colorFilter = cf; + if(drawable != null) { + drawable.setColorFilter(cf); + } + } + + @Override + public void setImageURI(Uri mUri) { + if ("content".equals(mUri.getScheme())) { + try { + String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION}; + + Cursor cur = getContext().getContentResolver().query(mUri, orientationColumn, null, null, null); + + if (cur != null && cur.moveToFirst()) { + imageOrientation = cur.getInt(cur.getColumnIndex(orientationColumn[0])); + } + + InputStream in = null; + + try { + in = getContext().getContentResolver().openInputStream(mUri); + Bitmap bmp = BitmapFactory.decodeStream(in); + + if(imageOrientation != 0) { + Matrix m = new Matrix(); + m.postRotate(imageOrientation); + Bitmap rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true); + bmp.recycle(); + setImageDrawable(new BitmapDrawable(getResources(), rotated)); + } + else { + setImageDrawable(new BitmapDrawable(getResources(), bmp)); + } + } + finally { + if(in != null) { + in.close(); + } + + if(cur != null) { + cur.close(); + } + } + } + catch (Exception e) { + Log.w("GestureImageView", "Unable to open content: " + mUri, e); + } + } + else { + setImageDrawable(Drawable.createFromPath(mUri.toString())); + } + + if (drawable == null) { + Log.e("GestureImageView", "resolveUri failed on bad bitmap uri: " + mUri); + // Don't try again. + mUri = null; + } + } + + @Override + public Matrix getImageMatrix() { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + return super.getImageMatrix(); + } + + @Override + public void setScaleType(ScaleType scaleType) { + if(scaleType == ScaleType.CENTER || + scaleType == ScaleType.CENTER_CROP || + scaleType == ScaleType.CENTER_INSIDE) { + + super.setScaleType(scaleType); + } + else if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + } + + @Override + public void invalidateDrawable(Drawable dr) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + super.invalidateDrawable(dr); + } + + @Override + public int[] onCreateDrawableState(int extraSpace) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + return super.onCreateDrawableState(extraSpace); + } + + @Override + public void setAdjustViewBounds(boolean adjustViewBounds) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + super.setAdjustViewBounds(adjustViewBounds); + } + + @Override + public void setImageLevel(int level) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + super.setImageLevel(level); + } + + @Override + public void setImageMatrix(Matrix matrix) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + } + + @Override + public void setImageState(int[] state, boolean merge) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + } + + @Override + public void setSelected(boolean selected) { + if(strict) { + throw new UnsupportedOperationException("Not supported"); + } + super.setSelected(selected); + } + + @Override + public void setOnTouchListener(OnTouchListener l) { + this.customOnTouchListener = l; + } + + public float getCenterX() { + return centerX; + } + + public float getCenterY() { + return centerY; + } + + public boolean isLandscape() { + return getImageWidth() >= getImageHeight(); + } + + public boolean isPortrait() { + return getImageWidth() <= getImageHeight(); + } + + public void setStartingScale(float startingScale) { + this.startingScale = startingScale; + } + + public void setStartingPosition(float x, float y) { + this.startX = x; + this.startY = y; + } + + @Override + public void setOnClickListener(OnClickListener l) { + this.onClickListener = l; + + if(gestureImageViewTouchListener != null) { + gestureImageViewTouchListener.setOnClickListener(l); + } + } + + /** + * Returns true if the image dimensions are aligned with the orientation of the device. + * @return + */ + public boolean isOrientationAligned() { + if(deviceOrientation == Configuration.ORIENTATION_LANDSCAPE) { + return isLandscape(); + } + else if(deviceOrientation == Configuration.ORIENTATION_PORTRAIT) { + return isPortrait(); + } + return true; + } + + public int getDeviceOrientation() { + return deviceOrientation; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java new file mode 100644 index 000000000000..4a52358216d5 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +/** + * @author jasonpolites + * + */ +public interface GestureImageViewListener { + + public void onTouch(float x, float y); + + public void onScale(float scale); + + public void onPosition(float x, float y); + +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java new file mode 100644 index 000000000000..76751d145b58 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/GestureImageViewTouchListener.java @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import android.content.res.Configuration; +import android.graphics.PointF; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; + +public class GestureImageViewTouchListener implements OnTouchListener { + + private GestureImageView image; + private OnClickListener onClickListener; + + private final PointF current = new PointF(); + private final PointF last = new PointF(); + private final PointF next = new PointF(); + private final PointF midpoint = new PointF(); + + private final VectorF scaleVector = new VectorF(); + private final VectorF pinchVector = new VectorF(); + + private boolean touched = false; + private boolean inZoom = false; + + private float initialDistance; + private float lastScale = 1.0f; + private float currentScale = 1.0f; + + private float boundaryLeft = 0; + private float boundaryTop = 0; + private float boundaryRight = 0; + private float boundaryBottom = 0; + + private float maxScale = 5.0f; + private float minScale = 0.25f; + private float fitScaleHorizontal = 1.0f; + private float fitScaleVertical = 1.0f; + + private int canvasWidth = 0; + private int canvasHeight = 0; + + private float centerX = 0; + private float centerY = 0; + + private float startingScale = 0; + + private boolean canDragX = false; + private boolean canDragY = false; + + private boolean multiTouch = false; + + private int displayWidth; + private int displayHeight; + + private int imageWidth; + private int imageHeight; + + private FlingListener flingListener; + private FlingAnimation flingAnimation; + private ZoomAnimation zoomAnimation; + private MoveAnimation moveAnimation; + private GestureDetector tapDetector; + private GestureDetector flingDetector; + private GestureImageViewListener imageListener; + + public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) { + super(); + + this.image = image; + + this.displayWidth = displayWidth; + this.displayHeight = displayHeight; + + this.centerX = (float) displayWidth / 2.0f; + this.centerY = (float) displayHeight / 2.0f; + + this.imageWidth = image.getImageWidth(); + this.imageHeight = image.getImageHeight(); + + startingScale = image.getScale(); + + currentScale = startingScale; + lastScale = startingScale; + + boundaryRight = displayWidth; + boundaryBottom = displayHeight; + boundaryLeft = 0; + boundaryTop = 0; + + next.x = image.getImageX(); + next.y = image.getImageY(); + + flingListener = new FlingListener(); + flingAnimation = new FlingAnimation(); + zoomAnimation = new ZoomAnimation(); + moveAnimation = new MoveAnimation(); + + flingAnimation.setListener(new FlingAnimationListener() { + @Override + public void onMove(float x, float y) { + handleDrag(current.x + x, current.y + y); + } + + @Override + public void onComplete() {} + }); + + zoomAnimation.setZoom(2.0f); + zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() { + @Override + public void onZoom(float scale, float x, float y) { + if(scale <= maxScale && scale >= minScale) { + handleScale(scale, x, y); + } + } + + @Override + public void onComplete() { + inZoom = false; + handleUp(); + } + }); + + moveAnimation.setMoveAnimationListener(new MoveAnimationListener() { + + @Override + public void onMove(float x, float y) { + image.setPosition(x, y); + image.redraw(); + } + }); + + tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + startZoom(e); + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if(!inZoom) { + if(onClickListener != null) { + onClickListener.onClick(image); + return true; + } + } + + return false; + } + }); + + flingDetector = new GestureDetector(image.getContext(), flingListener); + imageListener = image.getGestureImageViewListener(); + + calculateBoundaries(); + } + + private void startFling() { + flingAnimation.setVelocityX(flingListener.getVelocityX()); + flingAnimation.setVelocityY(flingListener.getVelocityY()); + image.animationStart(flingAnimation); + } + + private void startZoom(MotionEvent e) { + inZoom = true; + zoomAnimation.reset(); + + float zoomTo = 1.0f; + + if(image.isLandscape()) { + if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) { + int scaledHeight = image.getScaledHeight(); + + if(scaledHeight < canvasHeight) { + zoomTo = fitScaleVertical / currentScale; + zoomAnimation.setTouchX(e.getX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + else { + zoomTo = fitScaleHorizontal / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + } + else { + int scaledWidth = image.getScaledWidth(); + + if(scaledWidth == canvasWidth) { + zoomTo = currentScale*4.0f; + zoomAnimation.setTouchX(e.getX()); + zoomAnimation.setTouchY(e.getY()); + } + else if(scaledWidth < canvasWidth) { + zoomTo = fitScaleHorizontal / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(e.getY()); + } + else { + zoomTo = fitScaleHorizontal / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + } + } + else { + if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) { + + int scaledHeight = image.getScaledHeight(); + + if(scaledHeight == canvasHeight) { + zoomTo = currentScale*4.0f; + zoomAnimation.setTouchX(e.getX()); + zoomAnimation.setTouchY(e.getY()); + } + else if(scaledHeight < canvasHeight) { + zoomTo = fitScaleVertical / currentScale; + zoomAnimation.setTouchX(e.getX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + else { + zoomTo = fitScaleVertical / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + } + else { + int scaledWidth = image.getScaledWidth(); + + if(scaledWidth < canvasWidth) { + zoomTo = fitScaleHorizontal / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(e.getY()); + } + else { + zoomTo = fitScaleVertical / currentScale; + zoomAnimation.setTouchX(image.getCenterX()); + zoomAnimation.setTouchY(image.getCenterY()); + } + } + } + + zoomAnimation.setZoom(zoomTo); + image.animationStart(zoomAnimation); + } + + + private void stopAnimations() { + image.animationStop(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + + if(!inZoom) { + + if(!tapDetector.onTouchEvent(event)) { + if(event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) { + startFling(); + } + + if(event.getAction() == MotionEvent.ACTION_UP) { + handleUp(); + } + else if(event.getAction() == MotionEvent.ACTION_DOWN) { + stopAnimations(); + + last.x = event.getX(); + last.y = event.getY(); + + if(imageListener != null) { + imageListener.onTouch(last.x, last.y); + } + + touched = true; + } + else if(event.getAction() == MotionEvent.ACTION_MOVE) { + if(event.getPointerCount() > 1) { + multiTouch = true; + if(initialDistance > 0) { + + pinchVector.set(event); + pinchVector.calculateLength(); + + float distance = pinchVector.length; + + if(initialDistance != distance) { + + float newScale = (distance / initialDistance) * lastScale; + + if(newScale <= maxScale) { + scaleVector.length *= newScale; + + scaleVector.calculateEndPoint(); + + scaleVector.length /= newScale; + + float newX = scaleVector.end.x; + float newY = scaleVector.end.y; + + handleScale(newScale, newX, newY); + } + } + } + else { + initialDistance = MathUtils.distance(event); + + MathUtils.midpoint(event, midpoint); + + scaleVector.setStart(midpoint); + scaleVector.setEnd(next); + + scaleVector.calculateLength(); + scaleVector.calculateAngle(); + + scaleVector.length /= lastScale; + } + } + else { + if(!touched) { + touched = true; + last.x = event.getX(); + last.y = event.getY(); + next.x = image.getImageX(); + next.y = image.getImageY(); + } + else if(!multiTouch) { + if(handleDrag(event.getX(), event.getY())) { + image.redraw(); + } + } + } + } + } + } + + return true; + } + + protected void handleUp() { + + multiTouch = false; + + initialDistance = 0; + lastScale = currentScale; + + if(!canDragX) { + next.x = centerX; + } + + if(!canDragY) { + next.y = centerY; + } + + boundCoordinates(); + + if(!canDragX && !canDragY) { + + if(image.isLandscape()) { + currentScale = fitScaleHorizontal; + lastScale = fitScaleHorizontal; + } + else { + currentScale = fitScaleVertical; + lastScale = fitScaleVertical; + } + } + + image.setScale(currentScale); + image.setPosition(next.x, next.y); + + if(imageListener != null) { + imageListener.onScale(currentScale); + imageListener.onPosition(next.x, next.y); + } + + image.redraw(); + } + + protected void handleScale(float scale, float x, float y) { + + currentScale = scale; + + if(currentScale > maxScale) { + currentScale = maxScale; + } + else if (currentScale < minScale) { + currentScale = minScale; + } + else { + next.x = x; + next.y = y; + } + + calculateBoundaries(); + + image.setScale(currentScale); + image.setPosition(next.x, next.y); + + if(imageListener != null) { + imageListener.onScale(currentScale); + imageListener.onPosition(next.x, next.y); + } + + image.redraw(); + } + + protected boolean handleDrag(float x, float y) { + current.x = x; + current.y = y; + + float diffX = (current.x - last.x); + float diffY = (current.y - last.y); + + if(diffX != 0 || diffY != 0) { + + if(canDragX) next.x += diffX; + if(canDragY) next.y += diffY; + + boundCoordinates(); + + last.x = current.x; + last.y = current.y; + + if(canDragX || canDragY) { + image.setPosition(next.x, next.y); + + if(imageListener != null) { + imageListener.onPosition(next.x, next.y); + } + + return true; + } + } + + return false; + } + + public void reset() { + currentScale = startingScale; + next.x = centerX; + next.y = centerY; + calculateBoundaries(); + image.setScale(currentScale); + image.setPosition(next.x, next.y); + image.redraw(); + } + + + public float getMaxScale() { + return maxScale; + } + + public void setMaxScale(float maxScale) { + this.maxScale = maxScale; + } + + public float getMinScale() { + return minScale; + } + + public void setMinScale(float minScale) { + this.minScale = minScale; + } + + public void setOnClickListener(OnClickListener onClickListener) { + this.onClickListener = onClickListener; + } + + protected void setCanvasWidth(int canvasWidth) { + this.canvasWidth = canvasWidth; + } + + protected void setCanvasHeight(int canvasHeight) { + this.canvasHeight = canvasHeight; + } + + protected void setFitScaleHorizontal(float fitScale) { + this.fitScaleHorizontal = fitScale; + } + + protected void setFitScaleVertical(float fitScaleVertical) { + this.fitScaleVertical = fitScaleVertical; + } + + protected void boundCoordinates() { + if(next.x < boundaryLeft) { + next.x = boundaryLeft; + } + else if(next.x > boundaryRight) { + next.x = boundaryRight; + } + + if(next.y < boundaryTop) { + next.y = boundaryTop; + } + else if(next.y > boundaryBottom) { + next.y = boundaryBottom; + } + } + + protected void calculateBoundaries() { + + int effectiveWidth = Math.round( (float) imageWidth * currentScale ); + int effectiveHeight = Math.round( (float) imageHeight * currentScale ); + + canDragX = effectiveWidth > displayWidth; + canDragY = effectiveHeight > displayHeight; + + if(canDragX) { + float diff = (float)(effectiveWidth - displayWidth) / 2.0f; + boundaryLeft = centerX - diff; + boundaryRight = centerX + diff; + } + + if(canDragY) { + float diff = (float)(effectiveHeight - displayHeight) / 2.0f; + boundaryTop = centerY - diff; + boundaryBottom = centerY + diff; + } + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java new file mode 100644 index 000000000000..df7f30db54a7 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MathUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import android.graphics.PointF; +import android.util.FloatMath; +import android.view.MotionEvent; + +public class MathUtils { + + public static float distance(MotionEvent event) { + float x = event.getX(0) - event.getX(1); + float y = event.getY(0) - event.getY(1); + return FloatMath.sqrt(x * x + y * y); + } + + public static float distance(PointF p1, PointF p2) { + float x = p1.x - p2.x; + float y = p1.y - p2.y; + return FloatMath.sqrt(x * x + y * y); + } + + public static float distance(float x1, float y1, float x2, float y2) { + float x = x1 - x2; + float y = y1 - y2; + return FloatMath.sqrt(x * x + y * y); + } + + public static void midpoint(MotionEvent event, PointF point) { + float x1 = event.getX(0); + float y1 = event.getY(0); + float x2 = event.getX(1); + float y2 = event.getY(1); + midpoint(x1, y1, x2, y2, point); + } + + public static void midpoint(float x1, float y1, float x2, float y2, PointF point) { + point.x = (x1 + x2) / 2.0f; + point.y = (y1 + y2) / 2.0f; + } + /** + * Rotates p1 around p2 by angle degrees. + * @param p1 + * @param p2 + * @param angle + */ + public void rotate(PointF p1, PointF p2, float angle) { + float px = p1.x; + float py = p1.y; + float ox = p2.x; + float oy = p2.y; + p1.x = (FloatMath.cos(angle) * (px-ox) - FloatMath.sin(angle) * (py-oy) + ox); + p1.y = (FloatMath.sin(angle) * (px-ox) + FloatMath.cos(angle) * (py-oy) + oy); + } + + public static float angle(PointF p1, PointF p2) { + return angle(p1.x, p1.y, p2.x, p2.y); + } + + public static float angle(float x1, float y1, float x2, float y2) { + return (float) Math.atan2(y2 - y1, x2 - x1); + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java new file mode 100644 index 000000000000..5303d646672b --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimation.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + + +/** + * @author Jason Polites + * + */ +public class MoveAnimation implements Animation { + + private boolean firstFrame = true; + + private float startX; + private float startY; + + private float targetX; + private float targetY; + private long animationTimeMS = 100; + private long totalTime = 0; + + private MoveAnimationListener moveAnimationListener; + + /* (non-Javadoc) + * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long) + */ + @Override + public boolean update(GestureImageView view, long time) { + totalTime += time; + + if(firstFrame) { + firstFrame = false; + startX = view.getImageX(); + startY = view.getImageY(); + } + + if(totalTime < animationTimeMS) { + + float ratio = (float) totalTime / animationTimeMS; + + float newX = ((targetX - startX) * ratio) + startX; + float newY = ((targetY - startY) * ratio) + startY; + + if(moveAnimationListener != null) { + moveAnimationListener.onMove(newX, newY); + } + + return true; + } + else { + if(moveAnimationListener != null) { + moveAnimationListener.onMove(targetX, targetY); + } + } + + return false; + } + + public void reset() { + firstFrame = true; + totalTime = 0; + } + + + public float getTargetX() { + return targetX; + } + + + public void setTargetX(float targetX) { + this.targetX = targetX; + } + + + public float getTargetY() { + return targetY; + } + + public void setTargetY(float targetY) { + this.targetY = targetY; + } + + public long getAnimationTimeMS() { + return animationTimeMS; + } + + public void setAnimationTimeMS(long animationTimeMS) { + this.animationTimeMS = animationTimeMS; + } + + public void setMoveAnimationListener(MoveAnimationListener moveAnimationListener) { + this.moveAnimationListener = moveAnimationListener; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java new file mode 100644 index 000000000000..a19a265e5844 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/MoveAnimationListener.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + + +/** + * @author Jason Polites + * + */ +public interface MoveAnimationListener { + + public void onMove(float x, float y); + +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java b/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java new file mode 100644 index 000000000000..1ff4b19d7e4f --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/VectorF.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import android.graphics.PointF; +import android.util.FloatMath; +import android.view.MotionEvent; + +public class VectorF { + + public float angle; + public float length; + + public final PointF start = new PointF(); + public final PointF end = new PointF(); + + public void calculateEndPoint() { + end.x = FloatMath.cos(angle) * length + start.x; + end.y = FloatMath.sin(angle) * length + start.y; + } + + public void setStart(PointF p) { + this.start.x = p.x; + this.start.y = p.y; + } + + public void setEnd(PointF p) { + this.end.x = p.x; + this.end.y = p.y; + } + + public void set(MotionEvent event) { + this.start.x = event.getX(0); + this.start.y = event.getY(0); + this.end.x = event.getX(1); + this.end.y = event.getY(1); + } + + public float calculateLength() { + length = MathUtils.distance(start, end); + return length; + } + + public float calculateAngle() { + angle = MathUtils.angle(start, end); + return angle; + } + + +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java new file mode 100644 index 000000000000..673b7f9cb148 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimation.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + +import android.graphics.PointF; + + +/** + * @author Jason Polites + * + */ +public class ZoomAnimation implements Animation { + + private boolean firstFrame = true; + + private float touchX; + private float touchY; + + private float zoom; + + private float startX; + private float startY; + private float startScale; + + private float xDiff; + private float yDiff; + private float scaleDiff; + + private long animationLengthMS = 200; + private long totalTime = 0; + + private ZoomAnimationListener zoomAnimationListener; + + /* (non-Javadoc) + * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long) + */ + @Override + public boolean update(GestureImageView view, long time) { + if(firstFrame) { + firstFrame = false; + + startX = view.getImageX(); + startY = view.getImageY(); + startScale = view.getScale(); + scaleDiff = (zoom * startScale) - startScale; + + if(scaleDiff > 0) { + // Calculate destination for midpoint + VectorF vector = new VectorF(); + + // Set the touch point as start because we want to move the end + vector.setStart(new PointF(touchX, touchY)); + vector.setEnd(new PointF(startX, startY)); + + vector.calculateAngle(); + + // Get the current length + float length = vector.calculateLength(); + + // Multiply length by zoom to get the new length + vector.length = length*zoom; + + // Now deduce the new endpoint + vector.calculateEndPoint(); + + xDiff = vector.end.x - startX; + yDiff = vector.end.y - startY; + } + else { + // Zoom out to center + xDiff = view.getCenterX() - startX; + yDiff = view.getCenterY() - startY; + } + } + + totalTime += time; + + float ratio = (float) totalTime / (float) animationLengthMS; + + if(ratio < 1) { + + if(ratio > 0) { + // we still have time left + float newScale = (ratio * scaleDiff) + startScale; + float newX = (ratio * xDiff) + startX; + float newY = (ratio * yDiff) + startY; + + if(zoomAnimationListener != null) { + zoomAnimationListener.onZoom(newScale, newX, newY); + } + } + + return true; + } + else { + + float newScale = scaleDiff + startScale; + float newX = xDiff + startX; + float newY = yDiff + startY; + + if(zoomAnimationListener != null) { + zoomAnimationListener.onZoom(newScale, newX, newY); + zoomAnimationListener.onComplete(); + } + + return false; + } + } + + public void reset() { + firstFrame = true; + totalTime = 0; + } + + public float getZoom() { + return zoom; + } + + public void setZoom(float zoom) { + this.zoom = zoom; + } + + public float getTouchX() { + return touchX; + } + + public void setTouchX(float touchX) { + this.touchX = touchX; + } + + public float getTouchY() { + return touchY; + } + + public void setTouchY(float touchY) { + this.touchY = touchY; + } + + public long getAnimationLengthMS() { + return animationLengthMS; + } + + public void setAnimationLengthMS(long animationLengthMS) { + this.animationLengthMS = animationLengthMS; + } + + public ZoomAnimationListener getZoomAnimationListener() { + return zoomAnimationListener; + } + + public void setZoomAnimationListener(ZoomAnimationListener zoomAnimationListener) { + this.zoomAnimationListener = zoomAnimationListener; + } +} diff --git a/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java new file mode 100644 index 000000000000..8df4bf641952 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/com/polites/android/ZoomAnimationListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012 Jason Polites + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.polites.android; + + +/** + * @author Jason Polites + * + */ +public interface ZoomAnimationListener { + public void onZoom(float scale, float x, float y); + public void onComplete(); +} diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java new file mode 100644 index 000000000000..cf042a426421 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/android/examples/DocumentLoader.java @@ -0,0 +1,596 @@ +// -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// +// This file is part of the LibreOffice project. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This is just a testbed for ideas and implementations. (Still, it might turn +// out to be somewhat useful as such while waiting for "real" apps.) + +// Important points: + +// Everything that might take a long time should be done asynchronously: +// - loading the document (loadComponentFromURL()) +// - counting number of pages (getRendererCount()) +// - rendering a page (render()) + +// Unclear whether pages can be rendered in parallel. Probably best to +// serialize all the above in the same worker thread, for instance using +// AsyncTask.SERIAL_EXECUTOR. + +// While a page is loading ideally should display some animated spinner (but +// for now just a static "please wait" text). + +// Just three views are used for the pages: For the current page being viewed, +// the previous, and the next. This could be bumped higher, need to make the +// "3" into a parameter below. + +package org.libreoffice.android.examples; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.TranslateAnimation; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.ViewFlipper; +import android.widget.ViewSwitcher; + +import com.polites.android.GestureImageView; + +import com.sun.star.awt.Size; +import com.sun.star.awt.XBitmap; +import com.sun.star.awt.XControl; +import com.sun.star.awt.XDevice; +import com.sun.star.awt.XToolkit2; +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XController; +import com.sun.star.frame.XFrame; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XEventListener; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.view.XRenderable; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.libreoffice.android.Bootstrap; + +public class DocumentLoader + extends Activity +{ + private static final String TAG = "DocumentLoader"; + + // Size of a small virtual (bitmap) device used to find out page count and + // page sizes + private static final int SMALLSIZE = 128; + + // We pre-render this many pages preceding and succeeding the currently + // viewed one, i.e. the total number of rendered pages kept is + // PAGECACHE_PLUSMINUS*2+1. + private static final int PAGECACHE_PLUSMINUS = 2; + private static final int PAGECACHE_SIZE = PAGECACHE_PLUSMINUS*2 + 1; + + long timingOverhead; + XComponentContext context; + XMultiComponentFactory mcf; + XComponentLoader componentLoader; + XToolkit2 toolkit; + XDevice dummySmallDevice; + Object doc; + int pageCount; + XRenderable renderable; + + GestureDetector gestureDetector; + + ViewGroup.LayoutParams matchParent; + + ViewFlipper flipper; + + class GestureListener + extends GestureDetector.SimpleOnGestureListener + { + @Override + public boolean onFling(MotionEvent event1, + MotionEvent event2, + float velocityX, + float velocityY) + { + Log.i(TAG, "onFling: " + event1 + " " + event2); + if (event1.getX() - event2.getX() > 120) { + if (((PageViewer)flipper.getCurrentView()).currentPageNumber == pageCount-1) + return false; + + Animation inFromRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0); + inFromRight.setDuration(500); + flipper.setInAnimation(inFromRight); + + Animation outToLeft = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, -1, + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0); + outToLeft.setDuration(500); + flipper.setOutAnimation(outToLeft); + + flipper.showNext(); + + ((PageViewer)flipper.getChildAt((flipper.getDisplayedChild() + PAGECACHE_PLUSMINUS) % PAGECACHE_SIZE)).display(((PageViewer)flipper.getCurrentView()).currentPageNumber + PAGECACHE_PLUSMINUS); + return true; + } else if (event2.getX() - event1.getX() > 120) { + if (((PageViewer)flipper.getCurrentView()).currentPageNumber == 0) + return false; + + Animation inFromLeft = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -1, Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0); + inFromLeft.setDuration(500); + flipper.setInAnimation(inFromLeft); + + Animation outToRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, + Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0); + outToRight.setDuration(500); + flipper.setOutAnimation(outToRight); + + flipper.showPrevious(); + + ((PageViewer)flipper.getChildAt((flipper.getDisplayedChild() + PAGECACHE_SIZE - PAGECACHE_PLUSMINUS) % PAGECACHE_SIZE)).display(((PageViewer)flipper.getCurrentView()).currentPageNumber - PAGECACHE_PLUSMINUS); + + return true; + } + return false; + } + } + + class MyXController + implements XController + { + + XFrame frame; + XModel model; + + public void attachFrame(XFrame frame) + { + Log.i(TAG, "attachFrame"); + this.frame = frame; + } + + public boolean attachModel(XModel model) + { + Log.i(TAG, "attachModel"); + this.model = model; + return true; + } + + public boolean suspend(boolean doSuspend) + { + Log.i(TAG, "suspend"); + return false; + } + + public Object getViewData() + { + Log.i(TAG, "getViewData"); + return null; + } + + public void restoreViewData(Object data) + { + Log.i(TAG, "restoreViewData"); + } + + public XModel getModel() + { + Log.i(TAG, "getModel"); + return model; + } + + public XFrame getFrame() + { + Log.i(TAG, "getFrame"); + return frame; + } + + public void dispose() + { + Log.i(TAG, "dispose"); + } + + public void addEventListener(XEventListener listener) + { + Log.i(TAG, "addEventListener"); + } + + public void removeEventListener(XEventListener listener) + { + Log.i(TAG, "removeEventListener"); + } + } + + ByteBuffer renderPage(int number) + { + try { + // Use dummySmallDevice with no scale of offset just to find out + // the paper size of this page. + + PropertyValue renderProps[] = new PropertyValue[3]; + renderProps[0] = new PropertyValue(); + renderProps[0].Name = "IsPrinter"; + renderProps[0].Value = new Boolean(true); + renderProps[1] = new PropertyValue(); + renderProps[1].Name = "RenderDevice"; + renderProps[1].Value = dummySmallDevice; + renderProps[2] = new PropertyValue(); + renderProps[2].Name = "View"; + renderProps[2].Value = new MyXController(); + + // getRenderer returns a set of properties that include the PageSize + long t0 = System.currentTimeMillis(); + PropertyValue rendererProps[] = renderable.getRenderer(number, doc, renderProps); + long t1 = System.currentTimeMillis(); + Log.i(TAG, "getRenderer took " + ((t1-t0)-timingOverhead) + " ms"); + + int pageWidth = 0, pageHeight = 0; + for (int i = 0; i < rendererProps.length; i++) { + if (rendererProps[i].Name.equals("PageSize")) { + pageWidth = ((Size) rendererProps[i].Value).Width; + pageHeight = ((Size) rendererProps[i].Value).Height; + Log.i(TAG, "PageSize: " + pageWidth + "x" + pageHeight); + } + } + + // Create a new device with the correct scale and offset + ByteBuffer bb = ByteBuffer.allocateDirect(flipper.getWidth()*flipper.getHeight()*4); + long wrapped_bb = Bootstrap.new_byte_buffer_wrapper(bb); + + XDevice device; + if (pageWidth == 0) { + // Huh? + device = toolkit.createScreenCompatibleDeviceUsingBuffer(flipper.getWidth(), flipper.getHeight(), 1, 1, 0, 0, wrapped_bb); + } else { + + // Scale so that it fits our device which has a resolution of 96/in (see + // SvpSalGraphics::GetResolution()). The page size returned from getRenderer() is in 1/mm * 100. + + int scaleNumerator, scaleDenominator; + + // If the view has a wider aspect ratio than the page, fit + // height; otherwise, fit width + if ((double) flipper.getWidth() / flipper.getHeight() > (double) pageWidth / pageHeight) { + scaleNumerator = flipper.getHeight(); + scaleDenominator = pageHeight / 2540 * 96; + } else { + scaleNumerator = flipper.getWidth(); + scaleDenominator = pageWidth / 2540 * 96; + } + Log.i(TAG, "Scaling with " + scaleNumerator + "/" + scaleDenominator); + + device = toolkit.createScreenCompatibleDeviceUsingBuffer(flipper.getWidth(), flipper.getHeight(), + scaleNumerator, scaleDenominator, + 0, 0, + wrapped_bb); + } + + // Update the property that points to the device + renderProps[1].Value = device; + + t0 = System.currentTimeMillis(); + renderable.render(number, doc, renderProps); + t1 = System.currentTimeMillis(); + Log.i(TAG, "Rendering page " + number + " took " + ((t1-t0)-timingOverhead) + " ms"); + + Bootstrap.force_full_alpha_bb(bb, 0, flipper.getWidth() * flipper.getHeight() * 4); + + return bb; + } + catch (Exception e) { + e.printStackTrace(System.err); + finish(); + } + + return null; + } + + enum PageState { NONEXISTENT, LOADING, READY }; + + class PageViewer + extends ViewSwitcher + { + int currentPageNumber = -1; + TextView waitView; + PageState state = PageState.NONEXISTENT; + Bitmap bm; + + class PageLoadTask + extends AsyncTask<Integer, Void, Integer> + { + protected Integer doInBackground(Integer... params) + { + int number = params[0]; + + if (number >= pageCount) + return -1; + + state = PageState.LOADING; + currentPageNumber = number; + ByteBuffer bb = renderPage(currentPageNumber); + bm = Bitmap.createBitmap(flipper.getWidth(), flipper.getHeight(), Bitmap.Config.ARGB_8888); + bm.copyPixelsFromBuffer(bb); + + return currentPageNumber; + } + + protected void onPostExecute(Integer result) + { + Log.i(TAG, "onPostExecute: " + result); + if (result == -1) + return; + + ImageView imageView = new ImageView(DocumentLoader.this); + imageView.setImageBitmap(bm); + + imageView.setScaleY(-1); + + if (getChildCount() == 2) + removeViewAt(1); + addView(imageView, 1, matchParent); + showNext(); + state = PageState.READY; + } + } + + void display(int number) + { + Log.i(TAG, "PageViewer display(" + number + ")"); + if (number >= 0) + waitView.setText("Page " + (number+1) + ", wait..."); + state = PageState.NONEXISTENT; + + if (getDisplayedChild() == 1) { + showPrevious(); + removeViewAt(1); + } + + if (number >= 0) { + new PageLoadTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, number); + } + } + + PageViewer(int number) + { + super(DocumentLoader.this); + + waitView = new TextView(DocumentLoader.this); + waitView.setTextSize(24); + waitView.setGravity(Gravity.CENTER); + waitView.setBackgroundColor(Color.WHITE); + waitView.setTextColor(Color.BLACK); + addView(waitView, 0, matchParent); + + display(number); + } + } + + class DocumentLoadTask + extends AsyncTask<String, Void, Void> + { + protected Void doInBackground(String... params) + { + try { + String url = params[0]; + Log.i(TAG, "Attempting to load " + url); + + PropertyValue loadProps[] = new PropertyValue[3]; + loadProps[0] = new PropertyValue(); + loadProps[0].Name = "Hidden"; + loadProps[0].Value = new Boolean(true); + loadProps[1] = new PropertyValue(); + loadProps[1].Name = "ReadOnly"; + loadProps[1].Value = new Boolean(true); + loadProps[2] = new PropertyValue(); + loadProps[2].Name = "Preview"; + loadProps[2].Value = new Boolean(true); + + long t0 = System.currentTimeMillis(); + doc = componentLoader.loadComponentFromURL(url, "_blank", 0, loadProps); + long t1 = System.currentTimeMillis(); + Log.i(TAG, "Loading took " + ((t1-t0)-timingOverhead) + " ms"); + + Object toolkitService = mcf.createInstanceWithContext + ("com.sun.star.awt.Toolkit", context); + toolkit = (XToolkit2) UnoRuntime.queryInterface(XToolkit2.class, toolkitService); + + renderable = (XRenderable) UnoRuntime.queryInterface(XRenderable.class, doc); + + // Set up dummySmallDevice and use it to find out the number + // of pages ("renderers"). + ByteBuffer smallbb = ByteBuffer.allocateDirect(SMALLSIZE*SMALLSIZE*4); + long wrapped_smallbb = Bootstrap.new_byte_buffer_wrapper(smallbb); + dummySmallDevice = toolkit.createScreenCompatibleDeviceUsingBuffer(SMALLSIZE, SMALLSIZE, 1, 1, 0, 0, wrapped_smallbb); + + PropertyValue renderProps[] = new PropertyValue[3]; + renderProps[0] = new PropertyValue(); + renderProps[0].Name = "IsPrinter"; + renderProps[0].Value = new Boolean(true); + renderProps[1] = new PropertyValue(); + renderProps[1].Name = "RenderDevice"; + renderProps[1].Value = dummySmallDevice; + renderProps[2] = new PropertyValue(); + renderProps[2].Name = "View"; + renderProps[2].Value = new MyXController(); + + t0 = System.currentTimeMillis(); + pageCount = renderable.getRendererCount(doc, renderProps); + t1 = System.currentTimeMillis(); + Log.i(TAG, "getRendererCount: " + pageCount + ", took " + ((t1-t0)-timingOverhead) + " ms"); + } + catch (Exception e) { + e.printStackTrace(System.err); + finish(); + } + return null; + } + } + + static void dumpUNOObject(String objectName, Object object) + { + Log.i(TAG, objectName + " is " + (object != null ? object.toString() : "null")); + + if (object == null) + return; + + XTypeProvider typeProvider = (XTypeProvider) + UnoRuntime.queryInterface(XTypeProvider.class, object); + if (typeProvider == null) + return; + + Type[] types = typeProvider.getTypes(); + if (types == null) + return; + + for (Type t : types) + Log.i(TAG, " " + t.getTypeName()); + } + + static void dumpBytes(String name, byte[] bytes, int offset) + { + if (bytes == null) { + Log.i(TAG, name + " is null"); + return; + } + Log.i(TAG, name + ":"); + + if (offset != 0) + Log.i(TAG, " (offset " + offset + ")"); + + for (int i = offset; i < Math.min(bytes.length, offset+160); i += 16) { + String s = ""; + for (int j = i; j < Math.min(bytes.length, i+16); j++) + s = s + String.format(" %02x", bytes[j]); + + Log.i(TAG, s); + } + } + + static void dumpBytes(String name, ByteBuffer bytes, int offset) + { + if (bytes == null) { + Log.i(TAG, name + " is null"); + return; + } + Log.i(TAG, name + ":"); + + if (offset != 0) + Log.i(TAG, " (offset " + offset + ")"); + + for (int i = offset; i < Math.min(bytes.limit(), offset+160); i += 16) { + String s = ""; + for (int j = i; j < Math.min(bytes.limit(), i+16); j++) + s = s + String.format(" %02x", bytes.get(j)); + + Log.i(TAG, s); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + gestureDetector = new GestureDetector(this, new GestureListener()); + + try { + long t0 = System.currentTimeMillis(); + long t1 = System.currentTimeMillis(); + timingOverhead = t1 - t0; + + Bootstrap.setup(this); + + Bootstrap.putenv("SAL_LOG=yes"); + + // Load a lot of shlibs here explicitly in advance because that + // makes debugging work better, sigh + Bootstrap.dlopen("libvcllo.so"); + Bootstrap.dlopen("libmergedlo.so"); + Bootstrap.dlopen("libswdlo.so"); + Bootstrap.dlopen("libswlo.so"); + + // Log.i(TAG, "Sleeping NOW"); + // Thread.sleep(20000); + + context = com.sun.star.comp.helper.Bootstrap.defaultBootstrap_InitialComponentContext(); + + Log.i(TAG, "context is" + (context!=null ? " not" : "") + " null"); + + mcf = context.getServiceManager(); + + Log.i(TAG, "mcf is" + (mcf!=null ? " not" : "") + " null"); + + String input = getIntent().getStringExtra("input"); + if (input == null) + input = "/assets/test1.odt"; + + // We need to fake up an argv, and the argv[0] even needs to + // point to some file name that we can pretend is the "program". + // setCommandArgs() will prefix argv[0] with the app's data + // directory. + + String[] argv = { "lo-document-loader", input }; + + Bootstrap.setCommandArgs(argv); + + Bootstrap.initVCL(); + + Object desktop = mcf.createInstanceWithContext + ("com.sun.star.frame.Desktop", context); + + Log.i(TAG, "desktop is" + (desktop!=null ? " not" : "") + " null"); + + Bootstrap.initUCBHelper(); + + componentLoader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, desktop); + + Log.i(TAG, "componentLoader is" + (componentLoader!=null ? " not" : "") + " null"); + + // Load the wanted document + new DocumentLoadTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "file://" + input); + + flipper = new ViewFlipper(this); + + matchParent = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + flipper.addView(new PageViewer(0), 0, matchParent); + for (int i = 0; i < PAGECACHE_PLUSMINUS; i++) + flipper.addView(new PageViewer(i+1), i+1, matchParent); + for (int i = 0; i < PAGECACHE_PLUSMINUS; i++) + flipper.addView(new PageViewer(-1), PAGECACHE_PLUSMINUS + i+1, matchParent); + + setContentView(flipper); + } + catch (Exception e) { + e.printStackTrace(System.err); + finish(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) + { + return gestureDetector.onTouchEvent(event); + } +} + +// vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java new file mode 100644 index 000000000000..2e21dbe5da61 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/FileUtilities.java @@ -0,0 +1,159 @@ +package org.libreoffice.ui; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.Comparator; + +public class FileUtilities { + + static final int ALL = -1; + static final int DOC = 0; + static final int CALC = 1; + static final int IMPRESS = 2; + + static final int SORT_AZ = 0; + static final int SORT_ZA = 1; + /** Oldest Files First*/ + static final int SORT_OLDEST = 2; + /** Newest Files First*/ + static final int SORT_NEWEST = 3; + /** Largest Files First */ + static final int SORT_LARGEST = 4; + /** Smallest Files First */ + static final int SORT_SMALLEST = 5; + + private static String[] fileExtensions = {".odt",".ods",".odp"}; + + static boolean isDoc(String filename){ + if( filename.endsWith( fileExtensions[ DOC ] ) ){ + return true; + } + return false; + } + + static boolean isCalc(String filename){ + if( filename.endsWith( fileExtensions[ CALC ] ) ){ + return true; + } + return false; + } + + static boolean isImpress(String filename){ + if( filename.endsWith( fileExtensions[ IMPRESS ] ) ){ + return true; + } + return false; + } + + static FileFilter getFileFilter(int mode ){ + if( mode != ALL){ + final String ext = fileExtensions[ mode ]; + return new FileFilter() { + + public boolean accept(File pathname) { + if( pathname.getName().endsWith( ext ) ){ + return true; + } + if( pathname.isDirectory() ){ + return true; + } + return false; + } + }; + }else{//return all + return new FileFilter() { + + public boolean accept(File pathname) { + // TODO Auto-generated method stub + return true; + } + }; + } + } + + static FilenameFilter getFilenameFilter(int mode){ + if( mode != ALL){ + final String ext = fileExtensions[ mode ]; + return new FilenameFilter() { + + public boolean accept(File dir, String filename) { + if( filename.endsWith( ext ) ){ + return true; + } + if( new File( dir , filename ).isDirectory() ){ + return true; + } + return false; + } + }; + }else{ + return new FilenameFilter() { + + public boolean accept(File dir, String filename) { + return true; + } + }; + } + } + + static void sortFiles(File[] files , int sortMode){ + //Should really change all this to a switch statement... + if( sortMode == SORT_AZ ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return lhs.getName().compareTo( rhs.getName() ); + } + }); + return; + } + if( sortMode == SORT_ZA ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return rhs.getName().compareTo( lhs.getName() ); + } + }); + return; + } + if( sortMode == SORT_OLDEST ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return Long.valueOf( lhs.lastModified() ).compareTo( rhs.lastModified() ); + } + }); + return; + } + if( sortMode == SORT_NEWEST ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return Long.valueOf( rhs.lastModified() ).compareTo( lhs.lastModified() ); + } + }); + return; + } + if( sortMode == SORT_LARGEST ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return Long.valueOf( rhs.length() ).compareTo( lhs.length() ); + } + }); + return; + } + if( sortMode == SORT_SMALLEST ){ + Arrays.sort( files , new Comparator<File>() { + + public int compare(File lhs, File rhs) { + return Long.valueOf( lhs.length() ).compareTo( rhs.length() ); + } + }); + return; + } + return; + } +} diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java new file mode 100644 index 000000000000..9585705d5e06 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/GridItemAdapter.java @@ -0,0 +1,95 @@ +package org.libreoffice.ui; + +import java.io.File; + +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class GridItemAdapter extends BaseAdapter{ + Context mContext; + File[] filePaths; + File currentDirectory; + String tag = "GridItemAdapter"; + + public GridItemAdapter(Context mContext, File[] filePaths) { + this.mContext = mContext; + this.filePaths = filePaths; + for(File fn : filePaths){ + Log.d(tag, fn.getName()); + } + } + + public GridItemAdapter(Context mContext, File currentDirectory) { + this.mContext = mContext; + this.currentDirectory = currentDirectory; + filePaths = currentDirectory.listFiles(); + } + + public GridItemAdapter(Context mContext, File currentDirectory, File[] filteredFiles) { + this.mContext = mContext; + this.currentDirectory = currentDirectory; + filePaths = filteredFiles; + } + + public int getCount() { + return filePaths.length; + } + + public Object getItem(int position) { + return null;//filePaths[ position ]; + } + + public long getItemId(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + View gridView; + + if (convertView == null) { + + + + } else { + gridView = (View) convertView; + } + gridView = new View(mContext); + + // get layout from mobile.xml + gridView = inflater.inflate(R.layout.file_explorer_grid_item, null); + + // set value into textview + TextView textView = (TextView) gridView + .findViewById(R.id.grid_item_label); + textView.setText(filePaths[position].getName()); + // set image based on selected text + ImageView imageView = (ImageView) gridView + .findViewById(R.id.grid_item_image); + if( filePaths[position].getName().endsWith(".odt") ){ + imageView.setImageResource(R.drawable.writer); + } + if( filePaths[position].getName().endsWith(".ods") ){ + imageView.setImageResource(R.drawable.calc); + } + if( filePaths[position].getName().endsWith(".odp") ){ + imageView.setImageResource(R.drawable.impress); + } + if( filePaths[position].isDirectory() ){//Is a folder + //Eventually have thumbnails of each sub file on a black circle + //For now just a folder icon + imageView.setImageResource(R.drawable.folder); + } + return gridView; + } + +} diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java new file mode 100644 index 000000000000..d1e46a9b543d --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/LibreOfficeUIActivity.java @@ -0,0 +1,548 @@ +package org.libreoffice.ui; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.prefs.Preferences; + +import android.app.ActionBar; +import android.app.ActionBar.OnNavigationListener; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +public class LibreOfficeUIActivity extends Activity implements OnNavigationListener { + private String tag = "file_manager"; + private SharedPreferences prefs; + private File homeDirectory; + private File currentDirectory; + private int filterMode = FileUtilities.ALL; + private int viewMode; + private int sortMode; + + FileFilter fileFilter; + FilenameFilter filenameFilter; + private String[] fileNames; + private File[] filePaths; + + + private static final String CURRENT_DIRECTORY_KEY = "CURRENT_DIRECTORY"; + private static final String FILTER_MODE_KEY = "FILTER_MODE"; + public static final String EXPLORER_VIEW_TYPE_KEY = "EXPLORER_VIEW_TYPE"; + public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS"; + public static final String SORT_MODE_KEY = "SORT_MODE"; + + public static final int GRID_VIEW = 0; + public static final int LIST_VIEW = 1; + + GridView gv; + ListView lv; + + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + Log.d(tag, "onCreate"); + //Set the "home" - top level - directory. + homeDirectory = new File(Environment.getExternalStorageDirectory(),"LibreOffice"); + homeDirectory.mkdirs(); + currentDirectory = homeDirectory; + //Load default settings + + + } + + public void createUI(){ + ActionBar actionBar = getActionBar(); + actionBar.setDisplayShowTitleEnabled(false);//This should show current directory if anything + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.file_view_modes, + android.R.layout.simple_spinner_dropdown_item); + actionBar.setListNavigationCallbacks(mSpinnerAdapter, this); + if( !currentDirectory.equals( homeDirectory )){ + actionBar.setDisplayHomeAsUpEnabled(true); + } + + if( viewMode == GRID_VIEW){ + // code to make a grid view + setContentView(R.layout.file_grid); + gv = (GridView)findViewById(R.id.file_explorer_grid_view); + fileNames = currentDirectory.list( FileUtilities.getFilenameFilter( filterMode ) ); + filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) ); + gv.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> parent, View view, + int position, long id) { + File file = filePaths[position]; + if(!file.isDirectory()){ + open(fileNames[position]); + }else{ + file = new File( currentDirectory, file.getName() ); + openDirectory( file ); + } + + } + }); + gv.setAdapter( new GridItemAdapter(getApplicationContext(), currentDirectory, filePaths ) ); + actionBar.setSelectedNavigationItem( filterMode + 1 );//This triggers the listener which modifies the view. + }else{ + setContentView(R.layout.file_list); + lv = (ListView)findViewById( R.id.file_explorer_list_view); + lv.setClickable(true); + fileNames = currentDirectory.list( FileUtilities.getFilenameFilter( filterMode ) ); + filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) ); + /*lv.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> parent, View view, + int position, long id) { + Log.d(tag, "click!"); + File file = filePaths[position]; + if(!file.isDirectory()){ + open(fileNames[position]); + }else{ + file = new File( currentDirectory, file.getName() ); + openDirectory( file ); + } + } + });*/ + lv.setAdapter( new ListItemAdapter(getApplicationContext(), filePaths) ); + actionBar.setSelectedNavigationItem( filterMode + 1 ); + } + + } + + public void openDirectory(File dir ){ + currentDirectory = dir; + if( !currentDirectory.equals( homeDirectory )){ + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + }else{ + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled( false ); + } + filePaths = currentDirectory.listFiles( FileUtilities.getFileFilter( filterMode ) ); + fileNames = new String[ filePaths.length ]; + FileUtilities.sortFiles( filePaths, sortMode ); + for( int i = 0; i < fileNames.length; i++){ + fileNames[ i ] = filePaths[ i ].getName(); + } + if( viewMode == GRID_VIEW){ + gv.setAdapter( new GridItemAdapter(getApplicationContext(), currentDirectory, filePaths ) ); + }else{ + lv.setAdapter( new ListItemAdapter(getApplicationContext(), filePaths) ); + } + } + + public void open(String file){ + Intent i = new Intent( this , WriterViewerActivity.class ); + i.putExtra( CURRENT_DIRECTORY_KEY , currentDirectory.getAbsolutePath() ); + i.putExtra( FILTER_MODE_KEY , filterMode ); + i.putExtra( EXPLORER_VIEW_TYPE_KEY , viewMode ); + startActivity( i ); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.view_menu, menu); + + MenuItem item = (MenuItem)menu.findItem(R.id.menu_view_toggle); + if( viewMode == GRID_VIEW){ + item.setTitle(R.string.list_view); + item.setIcon( R.drawable.light_view_as_list ); + }else{ + item.setTitle(R.string.grid_view); + item.setIcon( R.drawable.light_view_as_grid ); + } + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if( !currentDirectory.equals( homeDirectory ) ){ + openDirectory( currentDirectory.getParentFile() ); + } + break; + case R.id.menu_view_toggle: + if( viewMode == GRID_VIEW){ + viewMode = LIST_VIEW; + item.setTitle(R.string.grid_view);//Button points to next view. + item.setIcon( R.drawable.light_view_as_grid ); + + }else{ + viewMode = GRID_VIEW; + item.setTitle(R.string.list_view);//Button points to next view. + item.setIcon( R.drawable.light_view_as_list ); + } + createUI(); + break; + default: + return super.onOptionsItemSelected(item); + } + return true; + } + + public void createDummyFileSystem(){ + boolean mExternalStorageAvailable = false; + boolean mExternalStorageWriteable = false; + String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + mExternalStorageAvailable = mExternalStorageWriteable = true; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + mExternalStorageAvailable = true; + mExternalStorageWriteable = false; + } else { + mExternalStorageAvailable = mExternalStorageWriteable = false; + } + if( mExternalStorageAvailable && mExternalStorageWriteable ){//can also check if its writeable + Log.d(tag, Boolean.toString( currentDirectory.mkdir() ) ); + try { + File[] removeList = currentDirectory.listFiles(); + for(File item : removeList){ + if(item.isDirectory()) + continue;//Log.d(tag, item.getPath()); + item.delete(); + } + new File( currentDirectory , "d0.odp" ).createNewFile(); + new File( currentDirectory , "d1.odt" ).createNewFile(); + new File( currentDirectory , "d2.odt" ).createNewFile(); + new File( currentDirectory , "d3.odp" ).createNewFile(); + new File( currentDirectory , "d4.ods" ).createNewFile(); + new File( currentDirectory , "d5.odt" ).createNewFile(); + new File( currentDirectory , "d6.odp" ).createNewFile(); + new File( currentDirectory , "d7.odt" ).createNewFile(); + new File( currentDirectory , "d8.odt" ).createNewFile(); + new File( currentDirectory , "d9.odp" ).createNewFile(); + new File( currentDirectory , "d10.odp" ).createNewFile(); + new File( currentDirectory , "d11.odt" ).createNewFile(); + new File( currentDirectory , "d12.odt" ).createNewFile(); + new File( currentDirectory , "d13.odp" ).createNewFile(); + new File( currentDirectory , "d14.ods" ).createNewFile(); + new File( currentDirectory , "d15.odt" ).createNewFile(); + File templatesDirectory = new File( currentDirectory , "Templates" ); + templatesDirectory.mkdir(); + new File( templatesDirectory , "template1.odt" ).createNewFile(); + new File( templatesDirectory , "template2.odt" ).createNewFile(); + new File( templatesDirectory , "template3.ods" ).createNewFile(); + new File( templatesDirectory , "template4.odp" ).createNewFile(); + File regularDirectory = new File( currentDirectory , "Folder" ); + regularDirectory.mkdir(); + new File( regularDirectory , "yetAnotherDoc.odt" ).createNewFile(); + new File( regularDirectory , "some really long file name.ods" ).createNewFile(); + File anotherRegularDirectory = new File( regularDirectory , "AnotherFolder" ); + anotherRegularDirectory.mkdir(); + new File( anotherRegularDirectory , "yetAnotherDoc2.odt" ).createNewFile(); + //Should put a folder in at some stage. + + } catch (IOException e) { + Log.d(tag, "file io failure"); + e.printStackTrace(); + } + //Log.d(tag, fileStore.toString()); + } + else{ + Log.d(tag, "No External Storage"); + } + } + + @SuppressWarnings("unused")//see android:onClick properties in view_menu.xml + public void sortFiles(MenuItem item){ + switch ( item.getItemId() ) { + case R.id.menu_sort_az: + if( sortMode == FileUtilities.SORT_AZ ){ + sortMode = FileUtilities.SORT_ZA; + }else{ + sortMode = FileUtilities.SORT_AZ; + } + break; + case R.id.menu_sort_modified: + if( sortMode == FileUtilities.SORT_NEWEST ){ + sortMode = FileUtilities.SORT_OLDEST; + }else{ + sortMode = FileUtilities.SORT_NEWEST; + } + break; + case R.id.menu_sort_size: + if( sortMode == FileUtilities.SORT_LARGEST ){ + sortMode = FileUtilities.SORT_SMALLEST; + }else{ + sortMode = FileUtilities.SORT_LARGEST; + } + break; + default: + break; + } + this.onResume(); + return; + } + + public void readPreferences(){ + prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); + viewMode = prefs.getInt( EXPLORER_VIEW_TYPE_KEY, GRID_VIEW); + sortMode = prefs.getInt( SORT_MODE_KEY, FileUtilities.SORT_AZ ); + SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences( getBaseContext() ); + filterMode = Integer.valueOf( defaultPrefs.getString( FILTER_MODE_KEY , "-1") ); + sortMode = Integer.valueOf( defaultPrefs.getString( SORT_MODE_KEY , "-1") ); + } + + public void editPreferences(MenuItem item){ + startActivity( new Intent( this , PreferenceEditor.class) ); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + // TODO Auto-generated method stub + super.onSaveInstanceState(outState); + outState.putString( CURRENT_DIRECTORY_KEY , currentDirectory.getAbsolutePath() ); + outState.putInt( FILTER_MODE_KEY , filterMode ); + outState.putInt( EXPLORER_VIEW_TYPE_KEY , viewMode ); + + Log.d(tag, currentDirectory.toString() + Integer.toString(filterMode ) + Integer.toString(viewMode) ); + //prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit(); + Log.d(tag, "savedInstanceSate"); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + // TODO Auto-generated method stub + super.onRestoreInstanceState(savedInstanceState); + if( savedInstanceState.isEmpty() ){ + return; + } + currentDirectory = new File( savedInstanceState.getString( CURRENT_DIRECTORY_KEY ) ); + filterMode = savedInstanceState.getInt( FILTER_MODE_KEY , FileUtilities.ALL ) ; + viewMode = savedInstanceState.getInt( EXPLORER_VIEW_TYPE_KEY , GRID_VIEW ); + //openDirectory( currentDirectory ); + Log.d(tag, "onRestoreInstanceState"); + Log.d(tag, currentDirectory.toString() + Integer.toString(filterMode ) + Integer.toString(viewMode) ); + } + + @Override + protected void onPause() { + //prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit(); + super.onPause(); + Log.d(tag, "onPause"); + } + + @Override + protected void onResume() { + // TODO Auto-generated method stub + super.onResume(); + Log.d(tag, "onResume"); + readPreferences();// intent values take precedence over prefs? + Intent i = this.getIntent(); + if( i.hasExtra( CURRENT_DIRECTORY_KEY ) ){ + currentDirectory = new File( i.getStringExtra( CURRENT_DIRECTORY_KEY ) ); + Log.d(tag, CURRENT_DIRECTORY_KEY); + } + if( i.hasExtra( FILTER_MODE_KEY ) ){ + filterMode = i.getIntExtra( FILTER_MODE_KEY, FileUtilities.ALL); + Log.d(tag, FILTER_MODE_KEY); + } + if( i.hasExtra( EXPLORER_VIEW_TYPE_KEY ) ){ + viewMode = i.getIntExtra( EXPLORER_VIEW_TYPE_KEY, GRID_VIEW); + Log.d(tag, EXPLORER_VIEW_TYPE_KEY); + } + createUI(); + openDirectory( currentDirectory ); + } + + @Override + protected void onStart() { + // TODO Auto-generated method stub + super.onStart(); + Log.d(tag, "onStart"); + } + + @Override + protected void onStop() { + // TODO Auto-generated method stub + super.onStop(); + Log.d(tag, "onStop"); + } + + @Override + protected void onDestroy() { + // TODO Auto-generated method stub + super.onDestroy(); + + Log.d(tag, "onDestroy"); + } + + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + filterMode = itemPosition -1; //bit of a hack, I know. -1 is ALL 0 Docs etc + openDirectory( currentDirectory );// Uses filter mode + return true; + } + +class ListItemAdapter implements ListAdapter{ + private Context mContext; + private File[] filePaths; + private final long KB = 1024; + private final long MB = 1048576; + + public ListItemAdapter(Context mContext, File[] filePaths) { + this.mContext = mContext; + this.filePaths = filePaths; + } + + public int getCount() { + // TODO Auto-generated method stub + return filePaths.length; + } + + public Object getItem(int arg0) { + // TODO Auto-generated method stub + return null; + } + + public long getItemId(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + public int getItemViewType(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + View listItem; + + if (convertView == null) { + listItem = new View(mContext); + listItem = inflater.inflate(R.layout.file_list_item, null); + } else { + listItem = (View) convertView; + } + final int pos = position; + listItem.setClickable(true); + listItem.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + Log.d("LIST", "click!"); + if(filePaths[ pos ].isDirectory() ){ + openDirectory( filePaths[ pos ] ); + }else{ + open( filePaths[ pos ].getName() ); + } + } + }); + + + + // set value into textview + TextView filename = (TextView) listItem.findViewById(R.id.file_list_item_name); + filename.setText( filePaths[ position ].getName() ); + //filename.setClickable(true); + + TextView fileSize = (TextView) listItem.findViewById(R.id.file_list_item_size); + //TODO Give size in KB , MB as appropriate. + String size = "0B"; + long length = filePaths[ position ].length(); + if( length < KB ){ + size = Long.toString( length ) + "B"; + } + if( length >= KB && length < MB){ + size = Long.toString( length/KB ) + "KB"; + } + if( length >= MB){ + size = Long.toString( length/MB ) + "MB"; + } + fileSize.setText( size ); + //fileSize.setClickable(true); + + TextView fileDate = (TextView) listItem.findViewById(R.id.file_list_item_date); + SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy hh:ss"); + Date date = new Date( filePaths[ position ].lastModified() ); + //TODO format date + fileDate.setText( df.format( date ) ); + + // set image based on selected text + ImageView imageView = (ImageView) listItem.findViewById(R.id.file_list_item_icon); + if( filePaths[position].getName().endsWith(".odt") ){ + imageView.setImageResource(R.drawable.writer); + } + if( filePaths[position].getName().endsWith(".ods") ){ + imageView.setImageResource(R.drawable.calc); + } + if( filePaths[position].getName().endsWith(".odp") ){ + imageView.setImageResource(R.drawable.impress); + } + if( filePaths[position].isDirectory() ){ + //Eventually have thumbnails of each sub file on a black circle + //For now just a folder icon + imageView.setImageResource(R.drawable.folder); + } + //imageView.setClickable(true); + return listItem; + } + + public int getViewTypeCount() { + // TODO Auto-generated method stub + return 1; + } + + public boolean hasStableIds() { + // TODO Auto-generated method stub + return false; + } + + public boolean isEmpty() { + // TODO Auto-generated method stub + return false; + } + + public void registerDataSetObserver(DataSetObserver arg0) { + // TODO Auto-generated method stub + + } + + public void unregisterDataSetObserver(DataSetObserver arg0) { + // TODO Auto-generated method stub + + } + + public boolean areAllItemsEnabled() { + // TODO Auto-generated method stub + return false; + } + + public boolean isEnabled(int position) { + // TODO Auto-generated method stub + return false; + } + + } + +} + + diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java new file mode 100644 index 000000000000..1e66e3a0501c --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/ListItemAdapter.java @@ -0,0 +1,159 @@ +package org.libreoffice.ui; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +import android.content.Context; +import android.database.DataSetObserver; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +/*Currently this is class is not used but instead is implemented as an inner class in LibreOfficeUI. + * This is because I can't get the onItemClickListener to fire on the listview so I need to set an + * onClick listener in the adapter. ( I've tried turning off the focusability etc of the listitem + * contents but no dice...) */ +public class ListItemAdapter implements ListAdapter{ + private Context mContext; + private File[] filePaths; + private final long KB = 1024; + private final long MB = 1048576; + + public ListItemAdapter(Context mContext, File[] filePaths) { + this.mContext = mContext; + this.filePaths = filePaths; + } + + public int getCount() { + // TODO Auto-generated method stub + return filePaths.length; + } + + public Object getItem(int arg0) { + // TODO Auto-generated method stub + return null; + } + + public long getItemId(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + public int getItemViewType(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + View listItem; + + if (convertView == null) { + listItem = new View(mContext); + listItem = inflater.inflate(R.layout.file_list_item, null); + } else { + listItem = (View) convertView; + } + + listItem.setClickable(true); + listItem.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + Log.d("LIST", "click!"); + } + }); + + + + // set value into textview + TextView filename = (TextView) listItem.findViewById(R.id.file_list_item_name); + filename.setText( filePaths[ position ].getName() ); + //filename.setClickable(true); + + TextView fileSize = (TextView) listItem.findViewById(R.id.file_list_item_size); + //TODO Give size in KB , MB as appropriate. + String size = "0B"; + long length = filePaths[ position ].length(); + if( length < KB ){ + size = Long.toString( length ) + "B"; + } + if( length >= KB && length < MB){ + size = Long.toString( length/KB ) + "KB"; + } + if( length >= MB){ + size = Long.toString( length/MB ) + "MB"; + } + fileSize.setText( size ); + //fileSize.setClickable(true); + + TextView fileDate = (TextView) listItem.findViewById(R.id.file_list_item_date); + SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy hh:ss"); + Date date = new Date( filePaths[ position ].lastModified() ); + //TODO format date + fileDate.setText( df.format( date ) ); + + // set image based on selected text + ImageView imageView = (ImageView) listItem.findViewById(R.id.file_list_item_icon); + if( filePaths[position].getName().endsWith(".odt") ){ + imageView.setImageResource(R.drawable.writer); + } + if( filePaths[position].getName().endsWith(".ods") ){ + imageView.setImageResource(R.drawable.calc); + } + if( filePaths[position].getName().endsWith(".odp") ){ + imageView.setImageResource(R.drawable.impress); + } + if( filePaths[position].isDirectory() ){ + //Eventually have thumbnails of each sub file on a black circle + //For now just a folder icon + imageView.setImageResource(R.drawable.folder); + } + //imageView.setClickable(true); + return listItem; + } + + public int getViewTypeCount() { + // TODO Auto-generated method stub + return 1; + } + + public boolean hasStableIds() { + // TODO Auto-generated method stub + return false; + } + + public boolean isEmpty() { + // TODO Auto-generated method stub + return false; + } + + public void registerDataSetObserver(DataSetObserver arg0) { + // TODO Auto-generated method stub + + } + + public void unregisterDataSetObserver(DataSetObserver arg0) { + // TODO Auto-generated method stub + + } + + public boolean areAllItemsEnabled() { + // TODO Auto-generated method stub + return false; + } + + public boolean isEnabled(int position) { + // TODO Auto-generated method stub + return false; + } + +} + diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java new file mode 100644 index 000000000000..5edb6ea20926 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PageView.java @@ -0,0 +1,63 @@ +package org.libreoffice.ui; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +public class PageView extends View{ + private Bitmap bmp; + private Paint mPaintBlack; + private String tag = "PageView"; + + public PageView(Context context ) { + super(context); + bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page); + intialise(); + } + public PageView(Context context, AttributeSet attrs) { + super(context, attrs); + bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page); + Log.d( tag , bmp.toString()); + intialise(); + } + public PageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page);//load a "page" + intialise(); + } + + private void intialise(){ + mPaintBlack = new Paint(); + mPaintBlack.setARGB(255, 0, 0, 0); + Log.d(tag, " Doing some set-up"); + } + + public void setBitmap(Bitmap bmp){ + this.bmp = bmp; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Log.d(tag, "Draw"); + Log.d(tag, Integer.toString(bmp.getHeight())); + if( bmp != null ){ + int horizontalMargin = (int) (canvas.getWidth()*0.1); + //int verticalMargin = (int) (canvas.getHeight()*0.1); + int verticalMargin = horizontalMargin; + canvas.drawBitmap(bmp, new Rect(0, 0, bmp.getWidth(), bmp.getHeight()), + new Rect(horizontalMargin,verticalMargin,canvas.getWidth()-horizontalMargin, + canvas.getHeight()-verticalMargin), + mPaintBlack);// + } + if( bmp == null) + canvas.drawText("Bmp is null!", 100, 100, new Paint()); + } + +} diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java new file mode 100644 index 000000000000..dec509de1167 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/PreferenceEditor.java @@ -0,0 +1,18 @@ +package org.libreoffice.ui; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceActivity; + +public class PreferenceEditor extends PreferenceActivity { + public final static String FilterTypePrefKey = "FilterTypePreference"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource( R.xml.libreoffice_preferences ); + //mPrefs = getSharedPreferences( LibreOfficeUIActivity.EXPLORER_PREFS_KEY , MODE_PRIVATE ); + } +} diff --git a/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java new file mode 100644 index 000000000000..fe2f1135b3a0 --- /dev/null +++ b/android/experimental/LibreOffice4Android/src/org/libreoffice/ui/WriterViewerActivity.java @@ -0,0 +1,37 @@ +package org.libreoffice.ui; + +import android.app.ActionBar; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; + +public class WriterViewerActivity extends Activity{ + private Bundle extras; + + @Override + protected void onCreate(Bundle savedInstanceState) { + // TODO Auto-generated method stub + super.onCreate(savedInstanceState); + extras = getIntent().getExtras(); + setContentView(R.layout.main); + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // app icon in action bar clicked; go home + Intent intent = new Intent(this, LibreOfficeUIActivity.class); + intent.putExtras( extras ); + //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + +} |