[Arakhnę-Dev] [422] * Add the general Android library. |
[ Thread Index |
Date Index
| More arakhne.org/dev Archives
]
Revision: 422
Author: galland
Date: 2013-04-11 18:28:25 +0200 (Thu, 11 Apr 2013)
Log Message:
-----------
* Add the general Android library.
Modified Paths:
--------------
trunk/ui/pom.xml
Added Paths:
-----------
trunk/ui/ui-android/
trunk/ui/ui-android/AndroidManifest.xml
trunk/ui/ui-android/assets/
trunk/ui/ui-android/gen/
trunk/ui/ui-android/libs/
trunk/ui/ui-android/pom.xml
trunk/ui/ui-android/proguard-project.txt
trunk/ui/ui-android/project.properties
trunk/ui/ui-android/res/
trunk/ui/ui-android/res/drawable/
trunk/ui/ui-android/res/drawable/colorpicker_arrow_down.png
trunk/ui/ui-android/res/drawable/colorpicker_arrow_right.png
trunk/ui/ui-android/res/drawable/colorpicker_cursor.png
trunk/ui/ui-android/res/drawable/colorpicker_hue.png
trunk/ui/ui-android/res/drawable/colorpicker_target.png
trunk/ui/ui-android/res/drawable/ic_menu_save_selector.xml
trunk/ui/ui-android/res/drawable/ic_menu_save_upload_selector.xml
trunk/ui/ui-android/res/drawable/ic_menu_up_directory_selector.xml
trunk/ui/ui-android/res/drawable-hdpi/
trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_down.png
trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_right.png
trunk/ui/ui-android/res/drawable-hdpi/colorpicker_cursor.png
trunk/ui/ui-android/res/drawable-hdpi/colorpicker_target.png
trunk/ui/ui-android/res/drawable-hdpi/ic_file.png
trunk/ui/ui-android/res/drawable-hdpi/ic_folder.png
trunk/ui/ui-android/res/drawable-ldpi/
trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_down.png
trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_right.png
trunk/ui/ui-android/res/drawable-ldpi/colorpicker_cursor.png
trunk/ui/ui-android/res/drawable-ldpi/colorpicker_target.png
trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_gray.png
trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload.png
trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload_gray.png
trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory.png
trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory_gray.png
trunk/ui/ui-android/res/drawable-mdpi/
trunk/ui/ui-android/res/drawable-mdpi/ic_file.png
trunk/ui/ui-android/res/drawable-mdpi/ic_folder.png
trunk/ui/ui-android/res/drawable-xhdpi/
trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_down.png
trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_right.png
trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_cursor.png
trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_target.png
trunk/ui/ui-android/res/drawable-xhdpi/hatchs.png
trunk/ui/ui-android/res/drawable-xhdpi/ic_file.png
trunk/ui/ui-android/res/drawable-xhdpi/ic_folder.png
trunk/ui/ui-android/res/layout/
trunk/ui/ui-android/res/layout/about.xml
trunk/ui/ui-android/res/layout/colorpicker_dialog.xml
trunk/ui/ui-android/res/layout/colorpicker_pref_widget.xml
trunk/ui/ui-android/res/layout/filechooser_onefile.xml
trunk/ui/ui-android/res/layout/filechooser_open.xml
trunk/ui/ui-android/res/layout/filechooser_save.xml
trunk/ui/ui-android/res/layout/texteditor.xml
trunk/ui/ui-android/res/layout-land/
trunk/ui/ui-android/res/layout-land/colorpicker_dialog.xml
trunk/ui/ui-android/res/menu/
trunk/ui/ui-android/res/menu/filechooser_menu.xml
trunk/ui/ui-android/res/menu/filechooser_open_menu.xml
trunk/ui/ui-android/res/menu/filechooser_save_menu.xml
trunk/ui/ui-android/res/menu/texteditor_menu.xml
trunk/ui/ui-android/res/values/
trunk/ui/ui-android/res/values/about_strings.xml
trunk/ui/ui-android/res/values/colorpicker_dimens.xml
trunk/ui/ui-android/res/values/colorpicker_strings.xml
trunk/ui/ui-android/res/values/filechooser_dimens.xml
trunk/ui/ui-android/res/values/filechooser_strings.xml
trunk/ui/ui-android/res/values/filechooser_styles.xml
trunk/ui/ui-android/res/values/propertyeditor_strings.xml
trunk/ui/ui-android/res/values/texteditor_strings.xml
trunk/ui/ui-android/res/values/zoomableview_strings.xml
trunk/ui/ui-android/res/values-fr/
trunk/ui/ui-android/res/values-fr/about_strings.xml
trunk/ui/ui-android/res/values-fr/colorpicker_strings.xml
trunk/ui/ui-android/res/values-fr/filechooser_strings.xml
trunk/ui/ui-android/res/values-fr/propertyeditor_strings.xml
trunk/ui/ui-android/res/values-fr/texteditor_strings.xml
trunk/ui/ui-android/res/values-fr/zoomableview_strings.xml
trunk/ui/ui-android/res/values-land/
trunk/ui/ui-android/res/values-land/colorpicker_dimens.xml
trunk/ui/ui-android/res/values-xlarge-land/
trunk/ui/ui-android/res/values-xlarge-land/colorpicker_dimens.xml
trunk/ui/ui-android/src/
trunk/ui/ui-android/src/main/
trunk/ui/ui-android/src/main/java/
trunk/ui/ui-android/src/main/java/org/
trunk/ui/ui-android/src/main/java/org/arakhne/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/about/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/about/AboutDialog.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorButton.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorChangeListener.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerDialog.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreference.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreferenceView.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerView.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/event/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/event/PointerEventAndroid.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/AsyncFileLoader.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooser.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserActivity.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserIconSelector.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListAdapter.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListFragment.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/progress/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/progress/ProgressDialogProgressionListener.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditorView.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditors.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/TablePropertyEditorView.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/texteditor/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/texteditor/TextEditorActivity.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/DroidZoomableGraphics2D.java
trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/ZoomableView.java
trunk/ui/ui-android/src/test/
trunk/ui/ui-android/src/test/java/
Modified: trunk/ui/pom.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android
___________________________________________________________________
Added: svn:ignore
+ .classpath
..metadata
..settings
..project
target
bin
Added: trunk/ui/ui-android/AndroidManifest.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/AndroidManifest.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Property changes on: trunk/ui/ui-android/gen
___________________________________________________________________
Added: svn:ignore
+ *
Added: trunk/ui/ui-android/pom.xml
===================================================================
--- trunk/ui/ui-android/pom.xml (rev 0)
+++ trunk/ui/ui-android/pom.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,74 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.arakhne.afc</groupId>
+ <artifactId>arakhneUi</artifactId>
+ <version>1.1-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.arakhne.afc.ui</groupId>
+ <artifactId>ui-android</artifactId>
+ <name>Android Utilities</name>
+ <packaging>apklib</packaging>
+
+ <properties>
+ <android.sdk.path>/opt/android-sdk</android.sdk.path>
+ <android.platform>15</android.platform>
+ <android.emulator>acer500</android.emulator>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>support-v4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.arakhne.afc</groupId>
+ <artifactId>util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.arakhne.afc.ui</groupId>
+ <artifactId>ui-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.arakhne.afc.ui</groupId>
+ <artifactId>ui-vector-android</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.arakhne.afc</groupId>
+ <artifactId>arakhneRefs</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <configuration>
+ <androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile>
+ <assetsDirectory>${project.basedir}/assets</assetsDirectory>
+ <resourceDirectory>${project.basedir}/res</resourceDirectory>
+ <nativeLibrariesDirectory>${project.basedir}/native</nativeLibrariesDirectory>
+ <sdk>
+ <path>${android.sdk.path}</path>
+ <platform>${android.platform}</platform>
+ </sdk>
+ <emulator>
+ <avd>${android.emulator}</avd>
+ </emulator>
+ <deleteConflictingFiles>true</deleteConflictingFiles>
+ <undeployBeforeDeploy>true</undeployBeforeDeploy>
+ </configuration>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
Added: trunk/ui/ui-android/proguard-project.txt
===================================================================
--- trunk/ui/ui-android/proguard-project.txt (rev 0)
+++ trunk/ui/ui-android/proguard-project.txt 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
Added: trunk/ui/ui-android/project.properties
===================================================================
--- trunk/ui/ui-android/project.properties (rev 0)
+++ trunk/ui/ui-android/project.properties 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,15 @@
+# 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 edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-15
+android.library=true
Added: trunk/ui/ui-android/res/drawable/colorpicker_arrow_down.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/colorpicker_arrow_down.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable/colorpicker_arrow_right.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/colorpicker_arrow_right.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable/colorpicker_cursor.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/colorpicker_cursor.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable/colorpicker_hue.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/colorpicker_hue.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable/colorpicker_target.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/colorpicker_target.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable/ic_menu_save_selector.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/ic_menu_save_selector.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/drawable/ic_menu_save_upload_selector.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/ic_menu_save_upload_selector.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/drawable/ic_menu_up_directory_selector.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable/ic_menu_up_directory_selector.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_down.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_down.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_right.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_arrow_right.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_cursor.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_cursor.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_target.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/colorpicker_target.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-hdpi/ic_file.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/ic_file.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-hdpi/ic_folder.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-hdpi/ic_folder.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_down.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_down.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_right.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_arrow_right.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_cursor.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_cursor.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_target.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/colorpicker_target.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_gray.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_gray.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload_gray.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_save_upload_gray.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory_gray.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-ldpi/ic_menu_up_directory_gray.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-mdpi/ic_file.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-mdpi/ic_file.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-mdpi/ic_folder.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-mdpi/ic_folder.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_down.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_down.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_right.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_arrow_right.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_cursor.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_cursor.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_target.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/colorpicker_target.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/hatchs.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/hatchs.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/ic_file.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/ic_file.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/drawable-xhdpi/ic_folder.png
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/drawable-xhdpi/ic_folder.png
___________________________________________________________________
Added: svn:mime-type
+ image/png
Added: trunk/ui/ui-android/res/layout/about.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/layout/about.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/layout/colorpicker_dialog.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/layout/colorpicker_dialog.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/layout/colorpicker_pref_widget.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/layout/colorpicker_pref_widget.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/layout/filechooser_onefile.xml
===================================================================
--- trunk/ui/ui-android/res/layout/filechooser_onefile.xml (rev 0)
+++ trunk/ui/ui-android/res/layout/filechooser_onefile.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,19 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:id="@+id/fileChooserFileIcon"
+ style="@style/fileChooserIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/fileChooserFileName"
+ style="@style/fileChooserName"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
Added: trunk/ui/ui-android/res/layout/filechooser_open.xml
===================================================================
--- trunk/ui/ui-android/res/layout/filechooser_open.xml (rev 0)
+++ trunk/ui/ui-android/res/layout/filechooser_open.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,13 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <FrameLayout
+ android:id="@+id/fileChooserExplorerFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginRight="@dimen/fileChooserListPadding"
+ android:layout_marginLeft="@dimen/fileChooserListPadding" />
+
+</LinearLayout>
\ No newline at end of file
Added: trunk/ui/ui-android/res/layout/filechooser_save.xml
===================================================================
--- trunk/ui/ui-android/res/layout/filechooser_save.xml (rev 0)
+++ trunk/ui/ui-android/res/layout/filechooser_save.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,35 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/filechooserTextView1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enter_filename" />
+
+ <EditText
+ android:id="@+id/fileChooserFilenameField"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textUri"
+ android:ems="10" >
+ <requestFocus />
+ </EditText>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/fileChooserExplorerFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginRight="@dimen/fileChooserListPadding"
+ android:layout_marginLeft="@dimen/fileChooserListPadding" />
+
+</LinearLayout>
\ No newline at end of file
Added: trunk/ui/ui-android/res/layout/texteditor.xml
===================================================================
--- trunk/ui/ui-android/res/layout/texteditor.xml (rev 0)
+++ trunk/ui/ui-android/res/layout/texteditor.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,18 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <EditText
+ android:id="@+id/textEditor"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:ems="10"
+ android:inputType="textMultiLine"
+ android:scrollHorizontally="true"
+ android:gravity="top" >
+
+ <requestFocus />
+ </EditText>
+
+</LinearLayout>
\ No newline at end of file
Added: trunk/ui/ui-android/res/layout-land/colorpicker_dialog.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/layout-land/colorpicker_dialog.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/menu/filechooser_menu.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/menu/filechooser_menu.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/menu/filechooser_open_menu.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/menu/filechooser_open_menu.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/menu/filechooser_save_menu.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/menu/filechooser_save_menu.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/menu/texteditor_menu.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/menu/texteditor_menu.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values/about_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values/about_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values/colorpicker_dimens.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values/colorpicker_dimens.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values/colorpicker_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values/colorpicker_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values/filechooser_dimens.xml
===================================================================
--- trunk/ui/ui-android/res/values/filechooser_dimens.xml (rev 0)
+++ trunk/ui/ui-android/res/values/filechooser_dimens.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,4 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <dimen name="fileChooserListPadding">0dp</dimen>
+ <dimen name="fileChooserListItemPadding">6dp</dimen>
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values/filechooser_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values/filechooser_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values/filechooser_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,7 @@
+<resources>
+ <string name="empty_directory">Empty directory</string>
+ <string name="choose_file">Select a file</string>
+ <string name="enter_filename">File name:</string>
+ <string name="save">Save</string>
+ <string name="up_directory">Parent directory</string>
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values/filechooser_styles.xml
===================================================================
--- trunk/ui/ui-android/res/values/filechooser_styles.xml (rev 0)
+++ trunk/ui/ui-android/res/values/filechooser_styles.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,13 @@
+<resources>
+ <style name="fileChooserName">
+ <item name="android:layout_marginLeft">@dimen/fileChooserListItemPadding</item>
+ <item name="android:layout_marginRight">@dimen/fileChooserListItemPadding</item>
+ <item name="android:maxLines">2</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+ <style name="fileChooserIcon">
+ <item name="android:layout_marginLeft">@dimen/fileChooserListItemPadding</item>
+ </style>
+
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values/propertyeditor_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values/propertyeditor_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values/propertyeditor_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,8 @@
+<resources>
+
+ <string name="property_dialog_title">Properties</string>
+ <string name="undo_property_edition">the edition of properties</string>
+ <string name="property_name">Name</string>
+ <string name="property_text">Text</string>
+
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values/texteditor_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values/texteditor_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values/texteditor_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,4 @@
+<resources>
+ <string name="edit_text">Edit a text file</string>
+ <string name="save_upload">Save and upload</string>
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values/zoomableview_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values/zoomableview_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values/zoomableview_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,5 @@
+<resources>
+
+ <string name="reset_view_done">View is reset to its defaults</string>
+
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values-fr/about_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-fr/about_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values-fr/colorpicker_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-fr/colorpicker_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values-fr/filechooser_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-fr/filechooser_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values-fr/propertyeditor_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values-fr/propertyeditor_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values-fr/propertyeditor_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,8 @@
+<resources>
+
+ <string name="property_dialog_title">Propriétés</string>
+ <string name="undo_property_edition">l\'édition de propriétés</string>
+ <string name="property_name">Nom</string>
+ <string name="property_text">Texte</string>
+
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values-fr/texteditor_strings.xml
===================================================================
--- trunk/ui/ui-android/res/values-fr/texteditor_strings.xml (rev 0)
+++ trunk/ui/ui-android/res/values-fr/texteditor_strings.xml 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,4 @@
+<resources>
+ <string name="edit_text">Éditer un fichier texte</string>
+ <string name="save_upload">Enregistrer et valider</string>
+</resources>
\ No newline at end of file
Added: trunk/ui/ui-android/res/values-fr/zoomableview_strings.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-fr/zoomableview_strings.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values-land/colorpicker_dimens.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-land/colorpicker_dimens.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/res/values-xlarge-land/colorpicker_dimens.xml
===================================================================
(Binary files differ)
Property changes on: trunk/ui/ui-android/res/values-xlarge-land/colorpicker_dimens.xml
___________________________________________________________________
Added: svn:mime-type
+ application/xml
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/about/AboutDialog.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/about/AboutDialog.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/about/AboutDialog.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,125 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.about;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.arakhne.afc.ui.android.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.util.Linkify;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/** Dialog that is displaying a "about this app" message.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class AboutDialog extends Dialog {
+
+ private final int applicationIconId;
+ private final String legalAssetName;
+ private final String infoAssetName;
+
+ /** Create the "About" dialog with the default configuration.
+ * The description of the application is read for the asset file
+ * {@code "info.html"} (HTML format).
+ * The legal text is read from the asset file {@code "legal.html"}
+ * (HTML format).
+ *
+ * @param context
+ * @see #AboutDialog(Context, int, String, String)
+ */
+ public AboutDialog(Context context) {
+ this(context, 0, null, null);
+ }
+
+ /** Create the "About" dialog with the default configuration.
+ *
+ * @param context
+ * @param applicationIcon is the identifier of the application icon.
+ * @param infoAsset is the name of the asset file that is a HTML file with the description of the application.
+ * By default it is {@code "info.html"}.
+ * @param legalAsset is the name of the asset file that is a HTML file with the legal text to display in the dialog box.
+ * By default it is {@code "legal.html"}.
+ */
+ public AboutDialog(Context context, int applicationIcon, String infoAsset, String legalAsset) {
+ super(context);
+ this.applicationIconId = applicationIcon;
+ if (infoAsset==null || infoAsset.isEmpty())
+ this.infoAssetName = "info.html"; //$NON-NLS-1$
+ else
+ this.infoAssetName = infoAsset;
+ if (legalAsset==null || legalAsset.isEmpty())
+ this.legalAssetName = "legal.html"; //$NON-NLS-1$
+ else
+ this.legalAssetName = legalAsset;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTitle(R.string.about_this_app);
+ setContentView(R.layout.about);
+ if (this.applicationIconId>0) {
+ ImageView iv = (ImageView)findViewById(R.id.about_application_id);
+ iv.setImageResource(this.applicationIconId);
+ }
+ TextView tv = (TextView)findViewById(R.id.about_legal_text);
+ tv.setText(Html.fromHtml(readAsset(this.legalAssetName)));
+ tv = (TextView)findViewById(R.id.about_info_text);
+ tv.setText(Html.fromHtml(readAsset(this.infoAssetName)));
+ Linkify.addLinks(tv, Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS);
+ }
+
+ private String readAsset(String assetName) {
+ try {
+ InputStream inputStream = getContext().getAssets().open(assetName);
+ try {
+ InputStreamReader in = new InputStreamReader(inputStream);
+ BufferedReader buf = new BufferedReader(in);
+ String line;
+ StringBuilder text = new StringBuilder();
+ while (( line = buf.readLine()) != null) {
+ text.append(line);
+ }
+ return text.toString();
+ }
+ finally {
+ inputStream.close();
+ }
+ }
+ catch (IOException _) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorButton.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorButton.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorButton.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,198 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.button;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.afc.ui.android.colorpicker.ColorPickerDialog;
+import org.arakhne.afc.ui.android.colorpicker.ColorPickerDialog.OnColorPickerListener;
+import org.arakhne.afc.util.ListenerCollection;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.View;
+import android.widget.Button;
+
+
+/** A button for picking a color.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class ColorButton extends Button {
+
+ private final Listener eventListener = new Listener();
+ private final ListenerCollection<ColorChangeListener> listeners = new ListenerCollection<ColorChangeListener>();
+ private Integer color = null;
+ private boolean enableDefaultColor = true;
+
+ /**
+ * @param context
+ */
+ public ColorButton(Context context) {
+ super(context, null, android.R.attr.buttonStyleSmall);
+ setClickable(true);
+ setOnClickListener(this.eventListener);
+ setBackgroundResource(R.drawable.hatchs);
+ setText(R.string.default_color);
+ }
+
+ /** Replies if the default color is allowed in this color button.
+ *
+ * @return the default color is allowed in this color button.
+ */
+ public boolean isDefaultColorEnabled() {
+ return this.enableDefaultColor;
+ }
+
+ /** Set if the default color is allowed in this color button.
+ *
+ * @param enable is the default color is allowed in this color button.
+ */
+ public void setDefaultColorEnabled(boolean enable) {
+ if (this.enableDefaultColor!=enable) {
+ this.enableDefaultColor = enable;
+ if (!this.enableDefaultColor && this.color==null) {
+ setColor(Color.BLACK);
+ }
+ }
+ }
+
+ /** Change the selected color.
+ *
+ * @param rgb
+ */
+ public final void setColor(int rgb) {
+ setColor(Integer.valueOf(rgb));
+ }
+
+ /** Change the selected color.
+ *
+ * @param rgb
+ */
+ public final void setColor(Integer rgb) {
+ if ((rgb!=null || isDefaultColorEnabled())
+ &&((this.color==null && rgb!=null)
+ || (this.color!=null && !this.color.equals(rgb)))) {
+ this.color = rgb;
+ if (this.color!=null) {
+ setBackgroundColor(this.color.intValue());
+ setText(this.color.toString());
+ }
+ else {
+ setBackgroundResource(R.drawable.hatchs);
+ setText(R.string.default_color);
+ }
+ fireColorChange(this.color);
+ }
+ }
+
+ /** Replies the selected color.
+ *
+ * @return the selected color; or <code>null</code> if the
+ * default color is selected.
+ */
+ public Integer getColor() {
+ return this.color;
+ }
+
+ /** Add listener on color changes.
+ *
+ * @param listener
+ */
+ public void addColorChangeListener(ColorChangeListener listener) {
+ this.listeners.add(ColorChangeListener.class, listener);
+ }
+
+ /** Remove listener on color changes.
+ *
+ * @param listener
+ */
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ this.listeners.remove(ColorChangeListener.class, listener);
+ }
+
+ /** Notifies listeners about a color change.
+ *
+ * @param color
+ */
+ public void fireColorChange(int color) {
+ for(ColorChangeListener listener : this.listeners.getListeners(ColorChangeListener.class)) {
+ listener.onSelectedColorChanged(this, color);
+ }
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private class Listener implements OnClickListener, OnColorPickerListener {
+
+ /**
+ */
+ public Listener() {
+ //
+ }
+
+ @Override
+ public void onClick(View v) {
+ Integer color = getColor();
+ if (color==null) {
+ color = Color.BLACK;
+ }
+ ColorPickerDialog dialog;
+ if (color!=null) {
+ dialog = new ColorPickerDialog(
+ getContext(),
+ color,
+ isDefaultColorEnabled(),
+ this);
+ }
+ else {
+ dialog = new ColorPickerDialog(
+ getContext(),
+ isDefaultColorEnabled(),
+ this);
+ }
+ dialog.show();
+ }
+
+ @Override
+ public void onColorPickingCanceled(ColorPickerDialog dialog) {
+ //
+ }
+
+ @Override
+ public void onColorPicked(ColorPickerDialog dialog, int color) {
+ setColor(color);
+ }
+
+ @Override
+ public void onDefaultColorPicked(ColorPickerDialog dialog) {
+ setColor(null);
+ }
+
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorChangeListener.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorChangeListener.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/button/ColorChangeListener.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.button;
+
+import java.util.EventListener;
+
+/** Listener on change of color in a ColorButton.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public interface ColorChangeListener extends EventListener {
+
+ /** Invoked when the color selected in a ColorButton has changed.
+ *
+ * @param source
+ * @param color is the RGBA color
+ */
+ public void onSelectedColorChanged(ColorButton source, int color);
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerDialog.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerDialog.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerDialog.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,412 @@
+/*
+ * $Id$
+ *
+ * Copyright 2012, Yuku Sugianto
+ * Copyright 2013, Yuku Sugianto, Stephane Galland
+ *
+ * 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 org.arakhne.afc.ui.android.colorpicker;
+
+import org.arakhne.afc.ui.android.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/** A dialog that permits to pick a color.
+ * This view is a panel on which all the colors are painted
+ * and on which the user may click to pick a color.
+ * <p>
+ * The original source code was copied from
+ * {@link "http://code.google.com/p/android-color-picker/"}.
+ * Comments were added, and source code patched for
+ * AFC compliance.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class ColorPickerDialog extends AlertDialog {
+
+ private final OnColorPickerListener listener;
+ private final float[] currentColorHsv = new float[3];
+
+ private final View viewHue;
+ private final ColorPickerView viewSatVal;
+ private final ImageView viewCursor;
+ private final View viewOldColor;
+ private final View viewNewColor;
+ private final ImageView viewTarget;
+ private final ViewGroup viewContainer;
+
+ /**
+ * Create a ColorPickerDialog with black color.
+ * Call this only from {@code OnCreateDialog()} or from a background thread.
+ *
+ * @param context
+ * @param enableDefaultColorButton indicates if the button to select a default color
+ * is displayed or not.
+ * @param listener is a listener on the picking events.
+ */
+ public ColorPickerDialog(Context context, boolean enableDefaultColorButton, OnColorPickerListener listener) {
+ this(context, 0xff000000, enableDefaultColorButton, listener);
+ }
+
+ /**
+ * Create a ColorPickerDialog.
+ * Call this only from {@code OnCreateDialog()} or from a background thread.
+ *
+ * @param context
+ * @param color is the RGB color
+ * @param enableDefaultColorButton indicates if the button to select a default color
+ * is displayed or not.
+ * @param listener is a listener on the picking events.
+ */
+ public ColorPickerDialog(Context context, int color, boolean enableDefaultColorButton, OnColorPickerListener listener) {
+ super(context);
+ this.listener = listener;
+ Color.colorToHSV(color, this.currentColorHsv);
+
+ final View rootView = LayoutInflater.from(context).inflate(R.layout.colorpicker_dialog, null);
+ this.viewHue = rootView.findViewById(R.id.colorpicker_viewHue);
+ this.viewSatVal = (ColorPickerView) rootView.findViewById(R.id.colorpicker_viewSatBri);
+ this.viewCursor = (ImageView) rootView.findViewById(R.id.colorpicker_cursor);
+ this.viewOldColor = rootView.findViewById(R.id.colorpicker_warnaLama);
+ this.viewNewColor = rootView.findViewById(R.id.colorpicker_warnaBaru);
+ this.viewTarget = (ImageView) rootView.findViewById(R.id.colorpicker_target);
+ this.viewContainer = (ViewGroup) rootView.findViewById(R.id.colorpicker_viewContainer);
+
+ this.viewSatVal.setHue(getHue());
+ this.viewOldColor.setBackgroundColor(color);
+ this.viewNewColor.setBackgroundColor(color);
+
+ this.viewHue.setOnTouchListener(new View.OnTouchListener() {
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_MOVE
+ || event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_UP) {
+
+ float y = event.getY();
+ if (y < 0.f) y = 0.f;
+ if (y > ColorPickerDialog.this.viewHue.getMeasuredHeight())
+ y = ColorPickerDialog.this.viewHue.getMeasuredHeight() - 0.001f; // to avoid looping from end to start.
+ float hue = 360.f - 360.f / ColorPickerDialog.this.viewHue.getMeasuredHeight() * y;
+ if (hue == 360.f) hue = 0.f;
+
+ setHue(hue);
+ return true;
+ }
+ return false;
+ }
+ });
+
+ this.viewSatVal.setOnTouchListener(new View.OnTouchListener() {
+ @SuppressWarnings("synthetic-access")
+ @Override public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_MOVE
+ || event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_UP) {
+
+ float x = event.getX(); // touch event are in dp units.
+ float y = event.getY();
+
+ if (x < 0.f) x = 0.f;
+ if (x > ColorPickerDialog.this.viewSatVal.getMeasuredWidth())
+ x = ColorPickerDialog.this.viewSatVal.getMeasuredWidth();
+ if (y < 0.f) y = 0.f;
+ if (y > ColorPickerDialog.this.viewSatVal.getMeasuredHeight())
+ y = ColorPickerDialog.this.viewSatVal.getMeasuredHeight();
+
+ setSaturationAndValue(
+ 1.f / ColorPickerDialog.this.viewSatVal.getMeasuredWidth() * x,
+ 1.f - (1.f / ColorPickerDialog.this.viewSatVal.getMeasuredHeight() * y));
+ return true;
+ }
+ return false;
+ }
+ });
+
+ setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ fireColorPicked();
+ }
+ });
+ setButton(BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ fireColorPickingCanceled();
+ }
+ });
+ setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface paramDialogInterface) {
+ fireColorPickingCanceled();
+ }
+ });
+ if (enableDefaultColorButton) {
+ setButton(BUTTON_NEUTRAL,
+ context.getString(R.string.default_color),
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ fireDefaultColorPicked();
+ }
+ });
+ }
+ // kill all padding from the dialog window
+ setView(rootView, 0, 0, 0, 0);
+
+ // move cursor & target on first draw
+ ViewTreeObserver vto = rootView.getViewTreeObserver();
+ vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void onGlobalLayout() {
+ moveCursor();
+ moveTarget();
+ rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ }
+ });
+ }
+
+ /** Notifies the listeners about the cancelation of the color picking.
+ */
+ protected void fireColorPickingCanceled() {
+ if (this.listener != null) {
+ this.listener.onColorPickingCanceled(this);
+ }
+ }
+
+ /** Notifies the listeners about the selection of the color.
+ */
+ protected void fireColorPicked() {
+ if (this.listener != null) {
+ this.listener.onColorPicked(this, getRGB());
+ }
+ }
+
+ /** Notifies the listeners about the selection of the default color.
+ */
+ protected void fireDefaultColorPicked() {
+ if (this.listener != null) {
+ this.listener.onDefaultColorPicked(this);
+ }
+ }
+
+ private void moveCursor() {
+ float y = this.viewHue.getMeasuredHeight() - (getHue() * this.viewHue.getMeasuredHeight() / 360.f);
+ if (y == this.viewHue.getMeasuredHeight()) y = 0.f;
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) this.viewCursor.getLayoutParams();
+ layoutParams.leftMargin = (int) (this.viewHue.getLeft() - Math.floor(this.viewCursor.getMeasuredWidth() / 2) - this.viewContainer.getPaddingLeft());
+
+ layoutParams.topMargin = (int) (this.viewHue.getTop() + y - Math.floor(this.viewCursor.getMeasuredHeight() / 2) - this.viewContainer.getPaddingTop());
+
+ this.viewCursor.setLayoutParams(layoutParams);
+ }
+
+ private void moveTarget() {
+ float x = getSaturation() * this.viewSatVal.getMeasuredWidth();
+ float y = (1.f - getValue()) * this.viewSatVal.getMeasuredHeight();
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) this.viewTarget.getLayoutParams();
+ layoutParams.leftMargin = (int) (this.viewSatVal.getLeft() + x - Math.floor(this.viewTarget.getMeasuredWidth() / 2) - this.viewContainer.getPaddingLeft());
+ layoutParams.topMargin = (int) (this.viewSatVal.getTop() + y - Math.floor(this.viewTarget.getMeasuredHeight() / 2) - this.viewContainer.getPaddingTop());
+ this.viewTarget.setLayoutParams(layoutParams);
+ }
+
+ /** Replies the RGB representation of the current selected color.
+ *
+ * @return RGB
+ */
+ public int getRGB() {
+ return Color.HSVToColor(this.currentColorHsv);
+ }
+
+ /** Set the RGB representation of the current selected color.
+ *
+ * @param rgb
+ */
+ public void setRGB(int rgb) {
+ float[] hsv = new float[3];
+ Color.colorToHSV(rgb, hsv);
+ setHSV(hsv);
+ }
+
+ /** Replies the HSV representation of the current selected color.
+ *
+ * @return HSV
+ */
+ public float[] getHSV() {
+ return this.currentColorHsv.clone();
+ }
+
+ /** Set the HSV representation of the current selected color.
+ *
+ * @param hsv
+ */
+ public final void setHSV(float[] hsv) {
+ setHSV(hsv[0], hsv[1], hsv[2]);
+ }
+
+ /** Set the HSV representation of the current selected color.
+ *
+ * @param hue
+ * @param saturation
+ * @param value
+ */
+ public void setHSV(float hue, float saturation, float value) {
+ if (hue!=this.currentColorHsv[0]
+ ||saturation!=this.currentColorHsv[1]
+ ||value!=this.currentColorHsv[2]) {
+ this.currentColorHsv[0] = hue;
+ this.currentColorHsv[1] = saturation;
+ this.currentColorHsv[2] = value;
+
+ this.viewSatVal.setHue(getHue());
+ moveCursor();
+ moveTarget();
+ this.viewNewColor.setBackgroundColor(getRGB());
+ }
+ }
+
+ /** Replies the hue of the current color, according
+ * to the HSV representation.
+ *
+ * @return the hue of the color.
+ */
+ public float getHue() {
+ return this.currentColorHsv[0];
+ }
+
+ /** Set the hue of the current color, according
+ * to the HSV representation.
+ *
+ * @param hue the hue of the color.
+ */
+ public void setHue(float hue) {
+ if (hue!=this.currentColorHsv[0]) {
+ this.currentColorHsv[0] = hue;
+ this.viewSatVal.setHue(getHue());
+ moveCursor();
+ this.viewNewColor.setBackgroundColor(getRGB());
+ }
+ }
+
+ /** Replies the saturation of the current color, according
+ * to the HSV representation.
+ *
+ * @return the saturation of the color.
+ */
+ public float getSaturation() {
+ return this.currentColorHsv[1];
+ }
+
+ /** Set the saturation of the current color, according
+ * to the HSV representation.
+ *
+ * @param sat is the saturation of the color.
+ */
+ public void setSaturation(float sat) {
+ if (sat!=this.currentColorHsv[1]) {
+ this.currentColorHsv[1] = sat;
+ moveTarget();
+ this.viewNewColor.setBackgroundColor(getRGB());
+ }
+ }
+
+ /** Replies the value of the current color, according
+ * to the HSV representation.
+ *
+ * @return the value of the color.
+ */
+ public float getValue() {
+ return this.currentColorHsv[2];
+ }
+
+ /** Set the value of the current color, according
+ * to the HSV representation.
+ *
+ * @param val is the value of the color.
+ */
+ public void setValue(float val) {
+ if (val!=this.currentColorHsv[2]) {
+ this.currentColorHsv[2] = val;
+ moveTarget();
+ this.viewNewColor.setBackgroundColor(getRGB());
+ }
+ }
+
+ /** Set the saturation and the value of the current color, according
+ * to the HSV representation.
+ *
+ * @param sat is the saturation of the color.
+ * @param val is the value of the color.
+ */
+ protected void setSaturationAndValue(float sat, float val) {
+ if (sat!=this.currentColorHsv[1] || val!=this.currentColorHsv[2]) {
+ this.currentColorHsv[1] = sat;
+ this.currentColorHsv[2] = val;
+ moveTarget();
+ this.viewNewColor.setBackgroundColor(getRGB());
+ }
+ }
+
+ /** Listener on color picking in a color picked
+ * dialog.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ public interface OnColorPickerListener {
+
+ /** Invoked when the color picking was canceled.
+ *
+ * @param dialog is the canceled dialog.
+ */
+ public void onColorPickingCanceled(ColorPickerDialog dialog);
+
+ /** Invoked when the color picking was successfull
+ * with a selected color.
+ *
+ * @param dialog is the dialog from which the color was selected.
+ * @param color is the RGB color.
+ */
+ public void onColorPicked(ColorPickerDialog dialog, int color);
+
+ /** Invoked when the color picking was successfull
+ * with the default color.
+ *
+ * @param dialog is the dialog from which the color was selected.
+ */
+ public void onDefaultColorPicked(ColorPickerDialog dialog);
+
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreference.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreference.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreference.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,245 @@
+/*
+ * $Id$
+ *
+ * Copyright 2012, Yuku Sugianto
+ * Copyright 2013, Yuku Sugianto, Stephane Galland
+ *
+ * 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 org.arakhne.afc.ui.android.colorpicker;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.afc.ui.android.colorpicker.ColorPickerDialog.OnColorPickerListener;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+
+/** A preference that permits to pick a color in the preference UI.
+ * <p>
+ * The original source code was copied from
+ * {@link "http://code.google.com/p/android-color-picker/"}.
+ * Comments were added, and source code patched for
+ * AFC compliance.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class ColorPickerPreference extends Preference {
+
+ private int rgb;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ColorPickerPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.colorpicker_pref_widget);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public ColorPickerPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setWidgetLayoutResource(R.layout.colorpicker_pref_widget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ // Set our custom views inside the layout
+ View kotak = view.findViewById(R.id.colorpicker_pref_widget_kotak);
+ if (kotak != null) {
+ kotak.setBackgroundColor(this.rgb);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onClick() {
+ OnColorPickerListener listener = new OnColorPickerListener() {
+ @Override
+ public void onColorPickingCanceled(ColorPickerDialog dialog) {
+ //
+ }
+ @Override
+ public void onColorPicked(ColorPickerDialog dialog, int color) {
+ setRGB(color);
+ }
+ @Override
+ public void onDefaultColorPicked(ColorPickerDialog dialog) {
+ //
+ }
+ };
+
+ new ColorPickerDialog(getContext(), this.rgb, false, listener).show();
+ }
+
+ /** Set the rgb preference after asking to the
+ * client if the change is allowed.
+ *
+ * @param rgb
+ */
+ public void setRGB(int rgb) {
+ // They don't want the value to be set
+ if (!callChangeListener(rgb)) return;
+ ColorPickerPreference.this.rgb = rgb;
+ persistInt(ColorPickerPreference.this.rgb);
+ notifyChanged();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ // This preference type's value type is Integer,
+ // so we read the default value from the attributes
+ // as an Integer.
+ return a.getInteger(index, 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ if (restoreValue) {
+ // Restore state
+ this.rgb = getPersistedInt(this.rgb);
+ }
+ else {
+ // Set state
+ this.rgb = ((Number)defaultValue).intValue();
+ persistInt(this.rgb);
+ }
+ }
+
+ /** {@inheritDoc}
+ * <p>
+ * Suppose a client uses this preference type without persisting. We
+ * must save the instance state so it is able to, for example, survive
+ * orientation changes.
+ */
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ // No need to save instance state since it's persistent
+ if (isPersistent())
+ return superState;
+
+ SavedState myState = new SavedState(superState, this.rgb);
+ return myState;
+ }
+
+ @Override protected void onRestoreInstanceState(Parcelable state) {
+ if (!state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ // Restore the instance state
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ this.rgb = myState.getRGB();
+ notifyChanged();
+ }
+
+ /**
+ * SavedState, a subclass of {@link BaseSavedState}, will store the state
+ * of MyPreference, a subclass of Preference.
+ * <p>
+ * It is important to always call through to super methods.
+ * <p>
+ * The original source code was copied from
+ * {@link "http://code.google.com/p/android-color-picker/"}.
+ * Comments were added, and source code patched for
+ * AFC compliance.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private static class SavedState extends BaseSavedState {
+
+ private int rgb;
+
+ /**
+ * @param source
+ */
+ public SavedState(Parcel source) {
+ super(source);
+ this.rgb = source.readInt();
+ }
+
+ /**
+ * @param superState
+ * @param rgb
+ */
+ public SavedState(Parcelable superState, int rgb) {
+ super(superState);
+ this.rgb = rgb;
+ }
+
+ /**
+ * Replies the saved RGB.
+ *
+ * @return RGB
+ */
+ public int getRGB() {
+ return this.rgb;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.rgb);
+ }
+
+ @SuppressWarnings({ "unused", "hiding" })
+ public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreferenceView.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreferenceView.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerPreferenceView.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,86 @@
+/*
+ * $Id$
+ *
+ * Copyright 2012, Yuku Sugianto
+ * Copyright 2013, Yuku Sugianto, Stephane Galland
+ *
+ * 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 org.arakhne.afc.ui.android.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+import android.view.View;
+
+/** A view that permits to pick a color in the preference UI.
+ * <p>
+ * The original source code was copied from
+ * {@link "http://code.google.com/p/android-color-picker/"}.
+ * Comments were added, and source code patched for
+ * AFC compliance.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ * @see ColorPickerView for a view outside the preference UI.
+ */
+public class ColorPickerPreferenceView extends View {
+
+ private final Paint paint;
+ private final float rectSize;
+ private final float strokeWidth;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ColorPickerPreferenceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public ColorPickerPreferenceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ float density = context.getResources().getDisplayMetrics().density;
+ this.rectSize = (float)Math.floor(24.f * density + 0.5f);
+ this.strokeWidth = (float)Math.floor(1.f * density + 0.5f);
+
+ this.paint = new Paint();
+ this.paint.setColor(0xffffffff);
+ this.paint.setStyle(Style.STROKE);
+ this.paint.setStrokeWidth(this.strokeWidth);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(
+ this.strokeWidth, this.strokeWidth,
+ this.rectSize - this.strokeWidth,
+ this.rectSize - this.strokeWidth,
+ this.paint);
+ }
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerView.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerView.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/colorpicker/ColorPickerView.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,167 @@
+/*
+ * $Id$
+ *
+ * Copyright 2012, Yuku Sugianto
+ * Copyright 2013, Yuku Sugianto, Stephane Galland
+ *
+ * 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 org.arakhne.afc.ui.android.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.view.View;
+
+/** A view that permits to pick a color.
+ * This view is a panel on which all the colors are painted
+ * and on which the user may click to pick a color.
+ * <p>
+ * The original source code was copied from
+ * {@link "http://code.google.com/p/android-color-picker/"}.
+ * Comments were added, and source code patched for
+ * AFC compliance.
+ *
+ * @author $Author: yukuku$
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ * @see ColorPickerPreferenceView for a view inside the preference UI.
+ */
+public class ColorPickerView extends View {
+
+
+ /** Current selected color.
+ */
+ private final float[] hsv = { 1.f, 1.f, 1.f };
+
+ private final Paint paint = new Paint();
+ private Shader whiteBlackGradientShader = null;
+ private Shader whiteColorGradientShader = null;
+ private Shader composedGradientShader = null;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ private Shader getWhiteToBlackGradientShader() {
+ if (this.whiteBlackGradientShader==null) {
+ this.whiteBlackGradientShader = new LinearGradient(
+ 0.f, 0.f, 0.f, getMeasuredHeight(),
+ 0xffffffff, 0xff000000,
+ TileMode.CLAMP);
+ }
+ return this.whiteBlackGradientShader;
+ }
+
+ private Shader getWhiteToColorGradientShader() {
+ if (this.whiteColorGradientShader==null) {
+ this.whiteColorGradientShader = new LinearGradient(
+ 0.f, 0.f, this.getMeasuredWidth(),
+ 0.f,
+ 0xffffffff,
+ getRGB(),
+ TileMode.CLAMP);
+ }
+ return this.whiteColorGradientShader;
+ }
+
+ private Shader getComposedGradientShader() {
+ if (this.composedGradientShader==null) {
+ this.composedGradientShader = new ComposeShader(
+ getWhiteToBlackGradientShader(),
+ getWhiteToColorGradientShader(),
+ PorterDuff.Mode.MULTIPLY);
+ }
+ return this.composedGradientShader;
+ }
+
+ private void reinitPaintingObjects() {
+ this.whiteColorGradientShader = null;
+ this.composedGradientShader = null;
+ this.paint.setShader(getComposedGradientShader());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(
+ 0.f, 0.f,
+ this.getMeasuredWidth(), this.getMeasuredHeight(),
+ this.paint);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Ensure that the gradients will be correctly drawn according
+ // to the size of the view
+ reinitPaintingObjects();
+ }
+
+ /** Replies the RGB representation of the current color.
+ *
+ * @return RGB
+ */
+ public int getRGB() {
+ return Color.HSVToColor(this.hsv);
+ }
+
+ /** Replies the HSV representation of the current color.
+ *
+ * @return HSV
+ */
+ public float[] getHSV() {
+ return this.hsv.clone();
+ }
+
+ /** Set the hue of the color according to HSB color definition.
+ *
+ * @param hue
+ */
+ public void setHue(float hue) {
+ if (this.hsv[0]!=hue) {
+ this.hsv[0] = hue;
+ reinitPaintingObjects();
+ invalidate();
+ }
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/event/PointerEventAndroid.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/event/PointerEventAndroid.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/event/PointerEventAndroid.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,225 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.event ;
+
+import java.util.EventObject;
+
+import org.arakhne.afc.math.continous.object2d.Circle2f;
+import org.arakhne.afc.math.continous.object2d.Shape2f;
+import org.arakhne.afc.ui.event.PointerEvent;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/** Android implementation of a pointer event.
+ *
+ * @author $Author: galland$
+ * @version $FullVersion$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class PointerEventAndroid extends EventObject implements PointerEvent {
+
+ private static final long serialVersionUID = -1281311602355739859L;
+
+ private final MotionEvent motionEvent;
+ private long when;
+ private float x;
+ private float y;
+ private boolean consumed = false;
+
+ /**
+ * @param source
+ * @param x
+ * @param y
+ * @param me
+ */
+ public PointerEventAndroid(Object source, float x, float y, MotionEvent me) {
+ super(source);
+ this.motionEvent = me;
+ this.when = me.getEventTime();
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override
+ public int getDeviceId() {
+ return this.motionEvent.getDeviceId();
+ }
+
+ @Override
+ public long when() {
+ return this.when;
+ }
+
+ /** Change the timestamp of the event.
+ *
+ * @param when is the new timestamp.
+ */
+ public void setWhen(long when) {
+ this.when = when;
+ }
+
+ @Override
+ public boolean isConsumed() {
+ return this.consumed;
+ }
+
+ @Override
+ public void consume() {
+ this.consumed = true;
+ }
+
+ /** Mark this event as not consumed.
+ */
+ public void unconsume() {
+ this.consumed = false;
+ }
+
+ @Override
+ public boolean isShiftDown() {
+ return (this.motionEvent.getMetaState()&KeyEvent.META_SHIFT_ON)!=0;
+ }
+
+ @Override
+ public boolean isControlDown() {
+ return (this.motionEvent.getMetaState()&KeyEvent.META_CTRL_ON)!=0;
+ }
+
+ @Override
+ public boolean isMetaDown() {
+ return (this.motionEvent.getMetaState()&KeyEvent.META_META_ON)!=0;
+ }
+
+ @Override
+ public boolean isAltDown() {
+ return (this.motionEvent.getMetaState()&KeyEvent.META_ALT_ON)!=0;
+ }
+
+ @Override
+ public boolean isAltGraphDown() {
+ return false;
+ }
+
+ @Override
+ public boolean isContextualActionTriggered() {
+ return false;
+ }
+
+ @Override
+ public float getX() {
+ return this.x;
+ }
+
+ @Override
+ public float getY() {
+ return this.y;
+ }
+
+ @Override
+ public int getButton() {
+ return this.motionEvent.getButtonState();
+ }
+
+ @Override
+ public int getClickCount() {
+ return 1;
+ }
+
+ @Override
+ public float getOrientation() {
+ return this.motionEvent.getOrientation();
+ }
+
+ /** Set the position of the event.
+ *
+ * @param x
+ * @param y
+ */
+ public void setXY(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "device="+getDeviceId()+ //$NON-NLS-1$
+ "\nx="+getX()+ //$NON-NLS-1$
+ "\ny="+getY()+ //$NON-NLS-1$
+ "\nbutton="+getButton()+ //$NON-NLS-1$
+ "\nkeys=0x"+Integer.toHexString(this.motionEvent.getMetaState())+ //$NON-NLS-1$
+ "\norientation="+getOrientation()+ //$NON-NLS-1$
+ "\nconsumed="+isConsumed(); //$NON-NLS-1$
+ }
+
+ @Override
+ public float getXPrecision() {
+ return this.motionEvent.getXPrecision();
+ }
+
+ @Override
+ public float getYPrecision() {
+ return this.motionEvent.getYPrecision();
+ }
+
+ @Override
+ public int getPointerCount() {
+ return this.motionEvent.getPointerCount();
+ }
+
+ @Override
+ public Shape2f getToolArea(int pointerIndex) {
+ float x = this.motionEvent.getX(pointerIndex);
+ float y = this.motionEvent.getY(pointerIndex);
+ float major = this.motionEvent.getToolMajor(pointerIndex);
+ float minor = this.motionEvent.getToolMinor(pointerIndex);
+ // The circle has a minimal radius of 1 pixel.
+ return new Circle2f(x, y,
+ Math.max(
+ Math.max(major, minor),
+ MINIMAL_TOOL_SIZE));
+ }
+
+ @Override
+ public ToolType getToolType(int pointerIndex) {
+ switch(this.motionEvent.getToolType(pointerIndex)) {
+ case MotionEvent.TOOL_TYPE_FINGER:
+ return ToolType.FINGER;
+ case MotionEvent.TOOL_TYPE_STYLUS:
+ return ToolType.STYLUS;
+ case MotionEvent.TOOL_TYPE_MOUSE:
+ return ToolType.MOUSE;
+ case MotionEvent.TOOL_TYPE_ERASER:
+ return ToolType.ERASER;
+ default:
+ }
+ return ToolType.UNKNOW;
+ }
+
+ @Override
+ public boolean isToolAreaSupported() {
+ return true;
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/AsyncFileLoader.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/AsyncFileLoader.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/AsyncFileLoader.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,182 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.os.FileObserver;
+import android.support.v4.content.AsyncTaskLoader;
+
+/**
+ * Asynchronous loader of a list of files.
+ * This loader is watching the file system content
+ * to update the list of the files when this list
+ * was changed by the action of an other application.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+class AsyncFileLoader extends AsyncTaskLoader<List<File>> {
+
+ private final File path;
+ private final FileFilter filter;
+
+ private FileObserver fileObserver;
+
+ private List<File> bufferedList = null;
+
+ /**
+ * @param context is the execution context of the task.
+ * @param path is the path of the directory to explore.
+ * @param fileFilter is the filter to use.
+ */
+ public AsyncFileLoader(Context context, File path, FileFilter fileFilter) {
+ super(context);
+ this.path = path;
+ this.filter = fileFilter;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<File> loadInBackground() {
+ File[] array;
+ if (this.filter!=null) {
+ array = this.path.listFiles(this.filter);
+ }
+ else {
+ array = this.path.listFiles();
+ }
+ List<File> list = new ArrayList<File>(array.length);
+ // Dichotomic insertion
+ int f, l, c;
+ File cf;
+ for(File file : array) {
+ if (!file.isHidden() && !file.getName().equalsIgnoreCase("lost.dir")) { //$NON-NLS-1$
+ f = 0;
+ l = list.size()-1;
+ while (f<=l) {
+ c = (f+l)/2;
+ cf = array[c];
+ if (file.compareTo(cf)<=0) {
+ l = c-1;
+ }
+ else {
+ f = c+1;
+ }
+ }
+ list.add(f,file);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStartLoading() {
+ // A list is already available. Publish it.
+ if (this.bufferedList != null) deliverResult(this.bufferedList);
+
+ if (this.fileObserver == null) {
+ this.fileObserver = new FileObserver(this.path.getAbsolutePath(),
+ FileObserver.CREATE
+ | FileObserver.DELETE | FileObserver.DELETE_SELF
+ | FileObserver.MOVED_FROM | FileObserver.MOVED_TO
+ | FileObserver.MODIFY | FileObserver.MOVE_SELF) {
+ @Override
+ public void onEvent(int event, String path) {
+ onContentChanged();
+ }
+ };
+ }
+ this.fileObserver.startWatching();
+
+ if (takeContentChanged() || this.bufferedList==null)
+ forceLoad();
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public void deliverResult(List<File> data) {
+ if (isReset()) {
+ unwatch();
+ return;
+ }
+
+ List<File> old = this.bufferedList;
+ this.bufferedList = data;
+
+ if (isStarted())
+ super.deliverResult(this.bufferedList);
+
+ if (old!=null && old!=data)
+ unwatch();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onReset() {
+ onStopLoading();
+
+ if (this.bufferedList!=null) {
+ unwatch();
+ this.bufferedList = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCanceled(List<File> data) {
+ super.onCanceled(data);
+ unwatch();
+ }
+
+ /** Unwatch the state for the given files.
+ */
+ protected void unwatch() {
+ if (this.fileObserver != null) {
+ this.fileObserver.stopWatching();
+ this.fileObserver = null;
+ }
+ }
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooser.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooser.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooser.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,305 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+import java.io.FileFilter;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+
+
+/**
+ * Utilities about file choosers.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class FileChooser {
+
+ /** Show the file chooser for opening a file.
+ * <p>
+ * The keys supported by the file chooser are:<ul>
+ * <li><code>path</code>: the directory path to open at start-up.</li>
+ * <li><code>fileFilter</code>: the classname of the file filter to use.</li>
+ * </ul>
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param mimeType is the MIME type of the files to select.
+ * @param options are the options to pass to the activity.
+ */
+ public static void showOpenChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ String mimeType,
+ Bundle options) {
+ // Implicitly allow the user to select a particular kind of data
+ Intent target = new Intent(Intent.ACTION_GET_CONTENT);
+ // The MIME data type filter
+ target.setType(mimeType);
+ // Only return URIs that can be opened with ContentResolver
+ target.addCategory(Intent.CATEGORY_OPENABLE);
+ // Create the chooser Intent
+ Intent intent = Intent.createChooser(
+ target, context.getString(chooserTitle));
+ // Set the extra data to pass to the activity.
+ Bundle opts;
+ if (options!=null) {
+ opts = new Bundle(options);
+ }
+ else {
+ opts = new Bundle();
+ }
+ opts.putBoolean(FileChooserActivity.SAVED_IS_OPEN, true);
+ intent.putExtra(FileChooserActivity.ACTIVITY_OPTIONS, opts);
+ Parcelable innerIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (innerIntent instanceof Intent)
+ ((Intent)innerIntent).putExtra(FileChooserActivity.ACTIVITY_OPTIONS, opts);
+ try {
+ context.startActivityForResult(intent, activityResultRequestCode);
+ }
+ catch (ActivityNotFoundException e) {
+ Log.e("FILE_CHOOSER", e.getLocalizedMessage(), e); //$NON-NLS-1$
+ }
+ }
+
+ /** Show the file chooser for opening a file.
+ * <p>
+ * The keys supported by the file chooser are:<ul>
+ * <li><code>path</code>: the directory path to open at start-up.</li>
+ * <li><code>fileFilter</code>: the classname of the file filter to use.</li>
+ * </ul>
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param options are the options to pass to the activity.
+ */
+ public static void showOpenChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ Bundle options) {
+ showOpenChooser(context, activityResultRequestCode, chooserTitle, "file/*", options); //$NON-NLS-1$
+ }
+
+ /** Show the file chooser for opening a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param directory is the directory to explore.
+ * @param fileFilter is the file filter to use.
+ */
+ public static void showOpenChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ File directory,
+ Class<? extends FileFilter> fileFilter) {
+ showOpenChooser(context, activityResultRequestCode, chooserTitle,
+ createOptions(directory, fileFilter));
+ }
+
+ /** Create options that may be passed to a chooser.
+ *
+ * @param directory is the directory to explore.
+ * @param fileFilter is the file filter to use.
+ * @return the options.
+ */
+ public static Bundle createOptions(
+ File directory,
+ Class<? extends FileFilter> fileFilter) {
+ Bundle bundle = new Bundle();
+ if (directory!=null) {
+ bundle.putString(FileChooserActivity.SAVED_PATH_NAME, directory.getAbsolutePath());
+ }
+ if (fileFilter!=null) {
+ bundle.putString(FileChooserActivity.SAVED_FILE_FILTER, fileFilter.getName());
+ }
+ return bundle;
+ }
+
+ /** Show the file chooser for opening a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param directory is the directory to explore.
+ * @param fileFilter is the file filter to use.
+ */
+ public static void showOpenChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ File directory,
+ FileFilter fileFilter) {
+ showOpenChooser(context, activityResultRequestCode, chooserTitle,
+ createOptions(directory, fileFilter!=null ? fileFilter.getClass() : null));
+ }
+
+ /** Show the file chooser for opening a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ */
+ public static void showOpenChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle) {
+ showOpenChooser(context, activityResultRequestCode, chooserTitle, null);
+ }
+
+ /** Show the file chooser for saving a file.
+ * <p>
+ * The keys supported by the file chooser are:<ul>
+ * <li><code>path</code>: the file path to save at start-up.</li>
+ * <li><code>fileFilter</code>: the classname of the file filter to use.</li>
+ * </ul>
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param mimeType is the mime type of the files to select.
+ * @param options are the options to pass to the activity.
+ */
+ public static void showSaveChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ String mimeType,
+ Bundle options) {
+ // Implicitly allow the user to select a particular kind of data
+ Intent target = new Intent(Intent.ACTION_GET_CONTENT);
+ // The MIME data type filter
+ target.setType(mimeType);
+ // Only return URIs that can be opened with ContentResolver
+ target.addCategory(Intent.CATEGORY_OPENABLE);
+ // Create the chooser Intent
+ Intent intent = Intent.createChooser(
+ target, context.getString(chooserTitle));
+ // Set the extra data to pass to the activity.
+ Bundle opts;
+ if (options!=null) {
+ opts = new Bundle(options);
+ }
+ else {
+ opts = new Bundle();
+ }
+ opts.putBoolean(FileChooserActivity.SAVED_IS_OPEN, false);
+ intent.putExtra(FileChooserActivity.ACTIVITY_OPTIONS, opts);
+ Parcelable innerIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (innerIntent instanceof Intent)
+ ((Intent)innerIntent).putExtra(FileChooserActivity.ACTIVITY_OPTIONS, opts);
+ try {
+ context.startActivityForResult(intent, activityResultRequestCode);
+ }
+ catch (ActivityNotFoundException e) {
+ Log.e("FILE_CHOOSER", e.getLocalizedMessage(), e); //$NON-NLS-1$
+ }
+ }
+
+ /** Show the file chooser for saving a file.
+ * <p>
+ * The keys supported by the file chooser are:<ul>
+ * <li><code>path</code>: the file path to save at start-up.</li>
+ * <li><code>fileFilter</code>: the classname of the file filter to use.</li>
+ * </ul>
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param options are the options to pass to the activity.
+ */
+ public static void showSaveChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ Bundle options) {
+ showSaveChooser(context, activityResultRequestCode, chooserTitle, "file/*", options); //$NON-NLS-1$
+ }
+
+ /** Show the file chooser for saving a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param file is the filename to use for the saving file.
+ * @param fileFilter is the file filter to use.
+ */
+ public static void showSaveChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ File file,
+ Class<? extends FileFilter> fileFilter) {
+ showSaveChooser(context, activityResultRequestCode, chooserTitle,
+ createOptions(file, fileFilter));
+ }
+
+ /** Show the file chooser for saving a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ * @param file is the filename to use for the saving file.
+ * @param fileFilter is the file filter to use.
+ */
+ public static void showSaveChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle,
+ File file,
+ FileFilter fileFilter) {
+ showSaveChooser(context, activityResultRequestCode, chooserTitle,
+ createOptions(file, fileFilter!=null ? fileFilter.getClass() : null));
+ }
+
+ /** Show the file chooser for saving a file.
+ * <p>
+ * Mime type is "file/*".
+ *
+ * @param context is the execution context.
+ * @param activityResultRequestCode is the code that may be used to retreive the result of the activity.
+ * @param chooserTitle is the identifier for the chooser's title.
+ */
+ public static void showSaveChooser(Activity context,
+ int activityResultRequestCode,
+ int chooserTitle) {
+ showSaveChooser(context, activityResultRequestCode, chooserTitle, null);
+ }
+
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserActivity.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserActivity.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserActivity.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,517 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+import java.io.FileFilter;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.vmutil.FileSystem;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
+import android.support.v4.app.FragmentTransaction;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+/**
+ * File chooser embedded inside an activity fragment.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class FileChooserActivity extends FragmentActivity implements OnBackStackChangedListener {
+
+ /** Name of the extra data that is containing the options of the file chooser.
+ */
+ static final String ACTIVITY_OPTIONS = "fileChooserActivityOptions"; //$NON-NLS-1$
+
+ /** Name of the attribute that permits to save the path in the activity.
+ */
+ public static final String SAVED_PATH_NAME = "path"; //$NON-NLS-1$
+
+ /** Name of the attribute that permits to save the file filter in the activity.
+ */
+ public static final String SAVED_FILE_FILTER = "fileFilter"; //$NON-NLS-1$
+
+ /** Name of the attribute that permits to indicates if the chooser is for opening a file.
+ */
+ public static final String SAVED_IS_OPEN = "isOpen"; //$NON-NLS-1$
+
+ /** Name of the preference that permits to store the path.
+ */
+ private static final String PREFERENCE_FILE_PATH = "lastSelectedPath"; //$NON-NLS-1$
+
+ private static File getFileParameter(String name, File defaultValue, Bundle... bundles) {
+ for(Bundle b : bundles) {
+ if (b!=null) {
+ String absPath = b.getString(name);
+ if (absPath!=null) return new File(absPath);
+ }
+ }
+ return defaultValue;
+ }
+
+ private static FileFilter getFileFilterParameter(String name, FileFilter defaultValue, Bundle... bundles) {
+ for(Bundle b : bundles) {
+ if (b!=null) {
+ String classname = b.getString(name);
+ if (classname!=null) {
+ try {
+ Class<?> type = Class.forName(classname);
+ if (FileFilter.class.isAssignableFrom(type)) {
+ return (FileFilter)type.newInstance();
+ }
+ }
+ catch(Throwable e) {
+ Log.d("FILE_CHOOSER", e.getLocalizedMessage(), e); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+ return defaultValue;
+ }
+
+ /** Listener on the external storage state.
+ */
+ private BroadcastReceiver storageListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onValidatedFile(null);
+ }
+ };
+
+ /** Current selected file.
+ */
+ private File path = null;
+
+ /** File filter.
+ */
+ private FileFilter fileFilter = null;
+
+ /** Icon selector.
+ */
+ private FileChooserIconSelector iconSelector = null;
+
+ /** Manager of fragments.
+ */
+ private FragmentManager fragmentManager = null;
+
+ /** Indicates if the file chooser is for opening (<code>true</code>)
+ * or saving (<code>false</code>) a file.
+ */
+ private boolean isOpen = true;
+
+ /** Reference to the save menu item.
+ */
+ private MenuItem saveItem = null;
+
+ /** Reference to the up-directory menu item.
+ */
+ private MenuItem upDirectoryItem = null;
+
+ /** Options given to the activity.
+ */
+ private Bundle activityOptions = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ this.activityOptions = intent.getBundleExtra(ACTIVITY_OPTIONS);
+
+ this.path = getFileParameter(
+ SAVED_PATH_NAME,
+ null,
+ this.activityOptions, savedInstanceState);
+
+ if (this.path==null) {
+ SharedPreferences preferences = getPreferences(DEFAULT_KEYS_SEARCH_LOCAL);
+ String filePath = preferences.getString(PREFERENCE_FILE_PATH, null);
+ if (filePath!=null && !filePath.isEmpty()) {
+ this.path = new File(filePath);
+ if (!this.path.isDirectory()) {
+ this.path = this.path.getParentFile();
+ }
+ }
+ if (this.path==null) {
+ this.path = Environment.getExternalStorageDirectory();
+ }
+ }
+
+ this.fileFilter = getFileFilterParameter(
+ SAVED_FILE_FILTER,
+ null,
+ this.activityOptions, savedInstanceState);
+
+ if (this.activityOptions!=null) {
+ this.isOpen = this.activityOptions.getBoolean(SAVED_IS_OPEN,
+ savedInstanceState!=null ?
+ savedInstanceState.getBoolean(SAVED_IS_OPEN, true) :
+ true);
+ }
+
+ String basename = null;
+ if (!this.isOpen && !this.path.isDirectory()) {
+ basename = this.path.getName();
+ }
+
+ while (this.path!=null && !this.path.isDirectory()) {
+ this.path = this.path.getParentFile();
+ }
+
+ if (this.path==null) {
+ this.path = Environment.getExternalStorageDirectory();
+ }
+
+ if (this.isOpen) {
+ setContentView(R.layout.filechooser_open);
+ }
+ else {
+ setContentView(R.layout.filechooser_save);
+ EditText editor = (EditText)findViewById(R.id.fileChooserFilenameField);
+ if (basename!=null) editor.setText(basename);
+ editor.addTextChangedListener(new Listener());
+ }
+
+ this.fragmentManager = getSupportFragmentManager();
+ this.fragmentManager.addOnBackStackChangedListener(this);
+
+ setTitle(this.path.getAbsolutePath());
+
+ // Start to listen on directory changes.
+ FileListFragment explorerFragment = FileListFragment.newInstance(
+ this.path, this.fileFilter, this.iconSelector);
+ FragmentTransaction transaction = this.fragmentManager.beginTransaction();
+ transaction.add(
+ R.id.fileChooserExplorerFragment,
+ explorerFragment).commit();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (this.isOpen) {
+ getMenuInflater().inflate(R.menu.filechooser_open_menu, menu);
+ }
+ else {
+ getMenuInflater().inflate(R.menu.filechooser_save_menu, menu);
+ this.saveItem = menu.findItem(R.id.fileChooserSaveMenu);
+ }
+ this.upDirectoryItem = menu.findItem(R.id.fileChooserParentDirectoryMenu);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ if (item.getItemId()==R.id.fileChooserParentDirectoryMenu) {
+ onSelectedFile(this.path.getParentFile());
+ return true;
+ }
+ if (!this.isOpen && item.getItemId()==R.id.fileChooserSaveMenu) {
+ EditText editor = (EditText)findViewById(R.id.fileChooserFilenameField);
+ String basename = editor.getText().toString();
+ basename = FileSystem.basename(basename);
+ onValidatedFile(new File(this.path, basename));
+ return true;
+ }
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ updateUpDirectoryAction();
+ if (!this.isOpen) {
+ updateSaveAction();
+ }
+ return true;
+ }
+
+ /** Update the state of the up-directory action.
+ */
+ protected void updateUpDirectoryAction() {
+ if (this.upDirectoryItem!=null) {
+ this.upDirectoryItem.setEnabled(this.path.getParentFile()!=null);
+ }
+ }
+
+ /** Update the state of the save action.
+ */
+ protected void updateSaveAction() {
+ if (this.saveItem!=null) {
+ EditText editor = (EditText)findViewById(R.id.fileChooserFilenameField);
+ String text = editor.getText().toString();
+ this.saveItem.setEnabled(this.path.isDirectory() && !text.isEmpty());
+ }
+ }
+
+ /** Set the file filter to use in the chooser.
+ *
+ * @param fileFilter
+ */
+ public void setFileFilter(FileFilter fileFilter) {
+ this.fileFilter = fileFilter;
+ }
+
+ /** Replies the file filter to use in the chooser.
+ *
+ * @return the file filter.
+ */
+ public FileFilter getFileFilter() {
+ return this.fileFilter;
+ }
+
+ /** Set the selector of icon
+ *
+ * @param iconSelector
+ */
+ public void setIconSelector(FileChooserIconSelector iconSelector) {
+ this.iconSelector = iconSelector;
+ }
+
+ /** Replies the selector of icon.
+ *
+ * @return the icon selector.
+ */
+ public FileChooserIconSelector getIconSelector() {
+ return this.iconSelector;
+ }
+
+ /** Invoked when the SD card state has changed.
+ */
+ @Override
+ public void onBackStackChanged() {
+ this.path = Environment.getExternalStorageDirectory();
+
+ int count = this.fragmentManager.getBackStackEntryCount();
+ if (count > 0) {
+ BackStackEntry fragment = this.fragmentManager.getBackStackEntryAt(count - 1);
+ String path = fragment.getName();
+ if (path!=null)
+ this.path = new File(path);
+ }
+
+ setTitle(this.path.getAbsolutePath());
+ invalidateOptionsMenu();
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterStorageListener();
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerStorageListener();
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ if (this.path!=null) {
+ outState.putString(SAVED_PATH_NAME, this.path.getAbsolutePath());
+ }
+ else {
+ outState.remove(SAVED_PATH_NAME);
+ }
+ if (this.fileFilter!=null) {
+ outState.putString(SAVED_FILE_FILTER, this.fileFilter.getClass().getName());
+ }
+ else {
+ outState.remove(SAVED_FILE_FILTER);
+ }
+ outState.putBoolean(SAVED_IS_OPEN, this.isOpen);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ this.isOpen = savedInstanceState.getBoolean(SAVED_IS_OPEN);
+ String fileFilterClassname = savedInstanceState.getString(SAVED_FILE_FILTER);
+ if (fileFilterClassname!=null) {
+ try {
+ Class<?> type = Class.forName(fileFilterClassname);
+ if (FileFilter.class.isAssignableFrom(type)) {
+ this.fileFilter = (FileFilter)type.newInstance();
+ }
+ }
+ catch(Throwable _) {
+ //
+ }
+ }
+ String path = savedInstanceState.getString(SAVED_PATH_NAME);
+ if (path!=null) {
+ this.path = new File(path);
+ }
+ }
+
+ /**
+ * Invoked when the activity is finished with a selected file.
+ *
+ * @param file is the selected file.
+ */
+ protected void onValidatedFile(File file) {
+ // Force to close the soft keyboard
+ if (!this.isOpen) {
+ InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0);
+ }
+ File validatedFile = file;
+ if (validatedFile!=null) {
+ if (!this.isOpen && this.fileFilter instanceof org.arakhne.afc.io.filefilter.FileFilter) {
+ if (!this.fileFilter.accept(validatedFile)) {
+ String ext = ((org.arakhne.afc.io.filefilter.FileFilter)this.fileFilter).getExtensions()[0];
+ validatedFile = FileSystem.addExtension(validatedFile, ext);
+ }
+ }
+
+ if (this.path!=null) {
+ File preferencePath = this.path;
+ while(preferencePath!=null && !preferencePath.isDirectory())
+ preferencePath = preferencePath.getParentFile();
+ if (preferencePath!=null) {
+ SharedPreferences preferences = getPreferences(DEFAULT_KEYS_SEARCH_LOCAL);
+ Editor preferenceEditor = preferences.edit();
+ preferenceEditor.putString(PREFERENCE_FILE_PATH, preferencePath.getAbsolutePath());
+ preferenceEditor.commit();
+ preferenceEditor.apply();
+ }
+ }
+
+ Intent data = new Intent();
+ data.setData(Uri.fromFile(validatedFile));
+
+ if (this.activityOptions!=null) {
+ data.putExtras(this.activityOptions);
+ }
+
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ else {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+
+
+ /**
+ * Called when the user selects a File.
+ *
+ * @param file is the selected file.
+ */
+ protected void onSelectedFile(File file) {
+ if (file!=null) {
+ this.path = file;
+
+ if (file.isDirectory()) {
+ FileListFragment explorerFragment = FileListFragment.newInstance(this.path, this.fileFilter, this.iconSelector);
+ FragmentTransaction transaction = this.fragmentManager.beginTransaction();
+ transaction.replace(
+ R.id.fileChooserExplorerFragment,
+ explorerFragment)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .addToBackStack(this.path.getAbsolutePath()).commit();
+ }
+ else {
+ onValidatedFile(file);
+ }
+ }
+ }
+
+ /**
+ * Register the external storage BroadcastReceiver.
+ */
+ private void registerStorageListener() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_MEDIA_REMOVED);
+ registerReceiver(this.storageListener, filter);
+ }
+
+ /**
+ * Unregister the external storage BroadcastReceiver.
+ */
+ private void unregisterStorageListener() {
+ unregisterReceiver(this.storageListener);
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private class Listener implements TextWatcher {
+
+ /**
+ */
+ public Listener() {
+ //
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ //
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ //
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ updateSaveAction();
+ }
+
+ } // class Listener
+
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserIconSelector.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserIconSelector.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileChooserIconSelector.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,43 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+
+/**
+ * This selector permits to select an icon for a file in a file chooser.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public interface FileChooserIconSelector {
+
+ /** Replies the preferred icon for the given file.
+ *
+ * @param file is the file for which the icon must be determined.
+ * @param defaultIcon is the default icon to use.
+ * @return the icon.
+ */
+ public int selectIconFor(File file, int defaultIcon);
+
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListAdapter.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListAdapter.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListAdapter.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,162 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.arakhne.afc.ui.android.R;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This class provides a drawing adapter for the elements
+ * of the list in a file chooser.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+class FileListAdapter extends BaseAdapter {
+
+ private List<File> files = new ArrayList<File>();
+
+ private final LayoutInflater layoutInflater;
+ private final FileChooserIconSelector iconSelector;
+
+ /**
+ * @param context is the drawing context.
+ * @param iconSelector is the selector of icon to use.
+ */
+ public FileListAdapter(Context context, FileChooserIconSelector iconSelector) {
+ this.layoutInflater = LayoutInflater.from(context);
+ this.iconSelector = iconSelector;
+ }
+
+ /** Set the list of files.
+ *
+ * @param list
+ */
+ public void set(List<File> list) {
+ this.files = list;
+ notifyDataSetChanged();
+ }
+
+ /** Clear the list of files to display.
+ */
+ public void clear() {
+ this.files.clear();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCount() {
+ return this.files.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public File getItem(int position) {
+ return this.files.get(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ ViewHolder holder = null;
+
+ if (row == null) {
+ row = this.layoutInflater.inflate(R.layout.filechooser_onefile, parent, false);
+ holder = new ViewHolder(row);
+ row.setTag(holder);
+ } else {
+ // Reduce, reuse, recycle!
+ holder = (ViewHolder) row.getTag();
+ }
+
+ // Get the file at the current position
+ File file = getItem(position);
+
+ // Set the TextView as the file name
+ holder.nameView.setText(file.getName());
+
+ // If the item is not a directory, use the file icon
+ int icon;
+ if (file.isDirectory()) {
+ icon = R.drawable.ic_folder;
+ }
+ else {
+ icon = R.drawable.ic_file;
+ if (this.iconSelector!=null) {
+ icon = this.iconSelector.selectIconFor(file, icon);
+ }
+ }
+
+ holder.iconView.setImageResource(icon);
+
+ return row;
+ }
+
+ /**
+ * This class provides a drawing adapter for the elements
+ * of the list in a file chooser.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private static class ViewHolder {
+
+ public final TextView nameView;
+ public final ImageView iconView;
+
+ public ViewHolder(View row) {
+ this.nameView = (TextView) row.findViewById(R.id.fileChooserFileName);
+ this.iconView = (ImageView) row.findViewById(R.id.fileChooserFileIcon);
+ }
+
+ }
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListFragment.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListFragment.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/filechooser/FileListFragment.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,167 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.filechooser;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.List;
+
+import org.arakhne.afc.ui.android.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.ListView;
+
+/**
+ * Fragment that displays a list of Files in a given path.
+ * <p>
+ * The path to open at startup should be stored in the
+ * argument with the name "path".
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class FileListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<File>> {
+
+ private FileFilter fileFilter = null;
+ private FileChooserIconSelector iconSelector = null;
+ private FileListAdapter adapter = null;
+ private File path;
+
+ /**
+ * @param path is the he absolute path of the file (directory) to display.
+ * @param fileFilter is the file filter to use.
+ * @param iconSelector is the selector of icon.
+ * @return the new instance.
+ */
+ public static FileListFragment newInstance(File path, FileFilter fileFilter, FileChooserIconSelector iconSelector) {
+ FileListFragment fragment = new FileListFragment();
+ Bundle args = new Bundle();
+ if (path!=null) {
+ args.putString(FileChooserActivity.SAVED_PATH_NAME, path.getAbsolutePath());
+ }
+ fragment.setArguments(args);
+ fragment.setFileFilter(fileFilter);
+ fragment.setIconSelector(iconSelector);
+ return fragment;
+ }
+
+ /**
+ */
+ public FileListFragment() {
+ //
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.adapter = new FileListAdapter(getActivity(), this.iconSelector);
+ this.path = Environment.getExternalStorageDirectory();
+
+ Bundle args = getArguments();
+ if (args!=null) {
+ String absPath = args.getString(FileChooserActivity.SAVED_PATH_NAME);
+ if (absPath!=null) {
+ this.path = new File(absPath);
+ }
+ }
+ }
+
+ /**
+ * Set the filte filter to use.
+ *
+ * @param fileFilter
+ */
+ public void setFileFilter(FileFilter fileFilter) {
+ this.fileFilter = fileFilter;
+ }
+
+ /**
+ * Set the icon selector to use.
+ *
+ * @param iconSelector
+ */
+ public void setIconSelector(FileChooserIconSelector iconSelector) {
+ this.iconSelector = iconSelector;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ setEmptyText(getString(R.string.empty_directory));
+ setListAdapter(this.adapter);
+ setListShown(false);
+
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ FileListAdapter adapter = (FileListAdapter) l.getAdapter();
+ if (adapter != null) {
+ this.path = adapter.getItem(position);
+ Activity activity = getActivity();
+ if (activity instanceof FileChooserActivity) {
+ ((FileChooserActivity)getActivity()).onSelectedFile(this.path);
+ }
+
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<List<File>> onCreateLoader(int id, Bundle args) {
+ return new AsyncFileLoader(getActivity(), this.path, this.fileFilter);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(Loader<List<File>> loader, List<File> data) {
+ this.adapter.set(data);
+ if (isResumed()) {
+ setListShown(true);
+ }
+ else {
+ setListShownNoAnimation(true);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<File>> loader) {
+ this.adapter.clear();
+ }
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/progress/ProgressDialogProgressionListener.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/progress/ProgressDialogProgressionListener.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/progress/ProgressDialogProgressionListener.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,109 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.progress;
+
+import org.arakhne.afc.progress.ProgressionEvent;
+import org.arakhne.afc.progress.ProgressionListener;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+
+/**
+ * Task progression listener that is linked to a ProgressDialog.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class ProgressDialogProgressionListener implements ProgressionListener {
+
+ private final Activity context;
+ private final ProgressDialog dialog;
+ private final String initialMessage;
+ private String currentMessage;
+
+ /**
+ * @param context
+ * @param dialog
+ * @param initialMessage
+ */
+ public ProgressDialogProgressionListener(Activity context, ProgressDialog dialog, String initialMessage) {
+ this.context = context;
+ this.dialog = dialog;
+ this.currentMessage = this.initialMessage = initialMessage;
+ }
+
+ @Override
+ public void onProgressionValueChanged(ProgressionEvent event) {
+ int v;
+ v = event.getMaximum() - event.getMinimum();
+ if (this.dialog.getMax()!=v) this.dialog.setMax(v);
+ v = event.getValue() - event.getMinimum();
+ if (this.dialog.getProgress()!=v) this.dialog.setProgress(v);
+ updateComment(event.getComment());
+ }
+
+ @Override
+ public void onProgressionStateChanged(ProgressionEvent event) {
+ this.dialog.setIndeterminate(event.isIndeterminate());
+ updateComment(event.getComment());
+ }
+
+ private void updateComment(String newComment) {
+ String rComment = newComment;
+ if (rComment==null) rComment = this.initialMessage;
+ if (!rComment.equals(this.currentMessage)) {
+ this.currentMessage = rComment;
+ this.context.runOnUiThread(new CommentUpdater(this.dialog, rComment));
+ }
+ }
+
+ /**
+ * Task progression listener that is linked to a ProgressDialog.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private static class CommentUpdater implements Runnable {
+
+ private final String comment;
+ private final ProgressDialog dialog;
+
+ /**
+ * @param dialog
+ * @param comment
+ */
+ public CommentUpdater(ProgressDialog dialog, String comment) {
+ this.dialog = dialog;
+ this.comment = comment;
+ }
+
+ @Override
+ public void run() {
+ this.dialog.setMessage(this.comment);
+ }
+
+ } // class CommentUpdater
+
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditorView.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditorView.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditorView.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,136 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.property;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.arakhne.afc.util.PropertyOwner;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/** Abstract implementation of a property editor inside a view.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public abstract class PropertyEditorView extends LinearLayout {
+
+ private Collection<? extends PropertyOwner> editedObjects = null;
+ private boolean isEditable = true;
+
+ /**
+ * @param context
+ */
+ protected PropertyEditorView(Context context) {
+ super(context);
+ View top = onCreateTopContentView();
+ if (top!=null) {
+ addView(top);
+ }
+ if (this.editedObjects!=null) {
+ onCreateFields(this.editedObjects);
+ }
+ LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ setLayoutParams(layoutParams);
+ }
+
+ /** Replies if the fragment enables to edit the properties.
+ *
+ * @return <code>true</code> if the properties are editable.
+ */
+ public boolean isEditable() {
+ return this.isEditable;
+ }
+
+ /** Set if the fragment enables to edit the properties.
+ *
+ * @param editable is <code>true</code> if the properties are editable.
+ */
+ public void setEditable(boolean editable) {
+ this.isEditable = editable;
+ }
+
+ /** Replies the edited objects.
+ *
+ * @return the edited objects.
+ */
+ public Collection<? extends PropertyOwner> getEditedObjects() {
+ return this.editedObjects;
+ }
+
+ /** Change the edited object.
+ *
+ * @param editedObjects
+ */
+ public void setEditedObjects(Collection<? extends PropertyOwner> editedObjects) {
+ if (this.editedObjects!=editedObjects) {
+ if (this.editedObjects!=null) {
+ onResetContentView();
+ }
+ this.editedObjects = editedObjects;
+ if (this.editedObjects!=null) {
+ onCreateFields(this.editedObjects);
+ }
+ }
+ }
+
+ /** Invoked when the content view must be cleared.
+ */
+ protected abstract void onResetContentView();
+
+ /** Invoked when the top-most (the root) content view must be initiated.
+ * @return the top view.
+ */
+ protected abstract View onCreateTopContentView();
+
+ /** Invoked to create the fields of the property editor.
+ *
+ * @param editedObject is the edited object.
+ */
+ protected abstract void onCreateFields(Collection<? extends PropertyOwner> editedObject);
+
+ /** Replies the properties in the fragment.
+ *
+ * @return the properties.
+ */
+ public abstract Map<String,Object> getEditedProperties();
+
+ /** Invoked by the API when the properties of an owner must be set.
+ * <p>
+ * This function shuold be overidden by subclasses to enable specific
+ * settings.
+ *
+ * @param owner is the object to change.
+ * @param properties are the properties to set.
+ */
+ @SuppressWarnings("static-method")
+ public void setPropertiesOf(PropertyOwner owner, Map<String,Object> properties) {
+ owner.setProperties(properties);
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditors.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditors.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/PropertyEditors.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,245 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.property;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.afc.ui.undo.AbstractUndoable;
+import org.arakhne.afc.ui.undo.UndoManager;
+import org.arakhne.afc.util.Pair;
+import org.arakhne.afc.util.PropertyOwner;
+import org.arakhne.util.ref.SoftValueHashMap;
+import org.arakhne.vmutil.ReflectionUtil;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ScrollView;
+
+/** Editors of properties.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class PropertyEditors {
+
+ /** Stored the mapping between type of object and type of property editor.
+ */
+ private static final Map<Class<? extends PropertyOwner>,Class<? extends PropertyEditorView>> mapping = new HashMap<Class<? extends PropertyOwner>,Class<? extends PropertyEditorView>>();
+
+ /** Use to accelerate the queries.
+ */
+ private static final Map<Class<? extends PropertyOwner>,Class<? extends PropertyEditorView>> buffer = new SoftValueHashMap<Class<? extends PropertyOwner>, Class<? extends PropertyEditorView>>();
+
+
+ /** Display the dialog that permits to edit the properties.
+ *
+ * @param context
+ * @param isEditable indicates if the dialog box allows editions.
+ * @param undoManager is the undo manager to use.
+ * @param editedObjects are the edited objects.
+ */
+ @SuppressWarnings("unchecked")
+ public static void showDialog(final Context context, final boolean isEditable, final UndoManager undoManager, final Collection<? extends PropertyOwner> editedObjects) {
+ //Detect the common type of the given objects
+ Class<? extends PropertyOwner> type = null;
+ if (!editedObjects.isEmpty()) {
+ for(PropertyOwner obj : editedObjects) {
+ if (type==null) {
+ type = obj.getClass();
+ }
+ else {
+ type = (Class<? extends PropertyOwner>)ReflectionUtil.getCommonType(type, obj.getClass());
+ }
+ }
+ }
+
+ // Create the views
+ ScrollView scrollView = new ScrollView(context);
+ LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ scrollView.setLayoutParams(layoutParams);
+
+ LinearLayout linearLayout = new LinearLayout(context);
+ linearLayout.setVerticalScrollBarEnabled(true);
+ linearLayout.setHorizontalScrollBarEnabled(false);
+ linearLayout.setScrollbarFadingEnabled(true);
+ layoutParams = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ linearLayout.setLayoutParams(layoutParams);
+ scrollView.addView(linearLayout);
+
+ final PropertyEditorView fragment = PropertyEditors.createFragmentFor(context, type);
+ fragment.setId(123456);
+ fragment.setEditable(isEditable);
+ fragment.setEditedObjects(editedObjects);
+ linearLayout.addView(fragment);
+
+
+ // Initialize the alert dialog builder
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.property_dialog_title);
+ builder.setPositiveButton(
+ android.R.string.ok,
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (isEditable) {
+ Undo undo = new Undo(context.getString(R.string.undo_property_edition), editedObjects, fragment);
+ undo.doEdit();
+ if (undoManager!=null) {
+ undoManager.add(undo);
+ }
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ dialog.dismiss();
+ }
+ });
+ builder.setView(scrollView);
+
+ // Create and show the dialog
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ /** Register a property editor fragment.
+ *
+ * @param fragmentType is the type of the fragment to create for editing the properties of an object of the given type.
+ * @param objectType is the type of the object to edit.
+ */
+ public static void registerEditorFragment(Class<? extends PropertyEditorView> fragmentType, Class<? extends PropertyOwner> objectType) {
+ mapping.put(objectType, fragmentType);
+ }
+
+ /** Create a fragment that permits to edit the properties for
+ * the given object.
+ *
+ * @param context
+ * @param editedObjectType
+ * @return a fragment or <code>null</code> if there is no known fragment.
+ */
+ static PropertyEditorView createFragmentFor(Context context, Class<? extends PropertyOwner> editedObjectType) {
+ assert(editedObjectType!=null);
+
+ Class<?> objectType = editedObjectType;
+ Class<? extends PropertyEditorView> fragmentType = buffer.get(objectType);
+ while (fragmentType==null && objectType!=null) {
+ fragmentType = mapping.get(objectType);
+ if (fragmentType==null) {
+ Class<?>[] interfaces = objectType.getInterfaces();
+ int i=0;
+ while (fragmentType==null && i<interfaces.length) {
+ fragmentType = mapping.get(interfaces[i]);
+ ++i;
+ }
+ }
+ objectType = objectType.getSuperclass();
+ }
+ if (fragmentType!=null) {
+ try {
+ Constructor<? extends PropertyEditorView> cons = fragmentType.getConstructor(Context.class);
+ PropertyEditorView fragment = cons.newInstance(context);
+ buffer.put(editedObjectType,fragmentType);
+ return fragment;
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private static class Undo extends AbstractUndoable {
+
+ private static final long serialVersionUID = -7759672095147438168L;
+
+ private final String label;
+ private final Collection<Pair<PropertyOwner,Map<String,Object>>> originalProperties = new ArrayList<Pair<PropertyOwner,Map<String,Object>>>();
+ private final PropertyEditorView fragment;
+ private final Map<String,Object> newProperties;
+
+ /**
+ * @param label
+ * @param editedObjects
+ * @param fragment
+ */
+ public Undo(String label, Collection<? extends PropertyOwner> editedObjects, PropertyEditorView fragment) {
+ this.label = label;
+ this.fragment = fragment;
+ this.newProperties = this.fragment.getEditedProperties();
+ for(PropertyOwner owner : editedObjects) {
+ this.originalProperties.add(new Pair<PropertyOwner,Map<String,Object>>(
+ owner,
+ owner.getProperties()));
+ }
+ }
+
+ @Override
+ public String getPresentationName() {
+ return this.label;
+ }
+
+ @Override
+ protected void doEdit() {
+ for(Pair<PropertyOwner,Map<String,Object>> object : this.originalProperties) {
+ this.fragment.setPropertiesOf(object.getA(), this.newProperties);
+ }
+ }
+
+ @Override
+ protected void undoEdit() {
+ for(Pair<PropertyOwner,Map<String,Object>> object : this.originalProperties) {
+ this.fragment.setPropertiesOf(object.getA(), object.getB());
+ }
+ }
+
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/TablePropertyEditorView.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/TablePropertyEditorView.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/property/TablePropertyEditorView.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,1043 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.property;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.afc.ui.android.button.ColorButton;
+import org.arakhne.afc.ui.vector.Color;
+import org.arakhne.afc.ui.vector.Colors;
+import org.arakhne.afc.ui.vector.VectorToolkit;
+
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.text.InputType;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+/** Abstract implementation of a property editor inside a fragment.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public abstract class TablePropertyEditorView extends PropertyEditorView {
+
+ /** Replies the field type that is supporting the given Java type.
+ *
+ * @param type
+ * @return the field type or <code>null</code>.
+ */
+ protected static FieldType toFieldType(Class<?> type) {
+ if (String.class.isAssignableFrom(type)) {
+ return FieldType.STRING;
+ }
+ if (Long.class.isAssignableFrom(type)) {
+ return FieldType.INTEGER;
+ }
+ if (Integer.class.isAssignableFrom(type)) {
+ return FieldType.INTEGER;
+ }
+ if (Long.class.isAssignableFrom(type)) {
+ return FieldType.INTEGER;
+ }
+ if (Number.class.isAssignableFrom(type)) {
+ return FieldType.FLOAT;
+ }
+ if (Boolean.class.isAssignableFrom(type)) {
+ return FieldType.BOOLEAN;
+ }
+ if (Color.class.isAssignableFrom(type)) {
+ return FieldType.COLOR;
+ }
+ if (URL.class.isAssignableFrom(type)) {
+ return FieldType.URL;
+ }
+ if (URI.class.isAssignableFrom(type)) {
+ return FieldType.EMAIL;
+ }
+ if (Enum.class.isAssignableFrom(type)) {
+ return FieldType.COMBO;
+ }
+ return null;
+ }
+
+ /** Extract a color from a property object.
+ *
+ * @param obj
+ * @return the color
+ */
+ public static Color toColor(Object obj) {
+ if (obj instanceof Color) {
+ return (Color)obj;
+ }
+ if (obj instanceof Number) {
+ return VectorToolkit.color(((Number)obj).intValue());
+ }
+ return VectorToolkit.color(obj);
+ }
+
+ private TableLayout tableLayout;
+ private final Map<String,FieldType> fieldTypes = new TreeMap<String,FieldType>();
+
+ /**
+ * @param context
+ */
+ protected TablePropertyEditorView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onResetContentView() {
+ if (this.tableLayout!=null) {
+ this.tableLayout.removeAllViews();
+ }
+ }
+
+ @Override
+ protected View onCreateTopContentView() {
+ this.tableLayout = new TableLayout(getContext());
+ this.tableLayout.setOrientation(LinearLayout.VERTICAL);
+ // Layout parameters
+ TableLayout.LayoutParams layoutParams = new TableLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ this.tableLayout.setLayoutParams(layoutParams);
+ return this.tableLayout;
+ }
+
+ /** Replies the type of a field.
+ *
+ * @param fieldId
+ * @return the type; or <code>null</code> if the field is unknown.
+ */
+ protected final FieldType getFieldType(String fieldId) {
+ return this.fieldTypes.get(fieldId);
+ }
+
+ /** Replies the text editor with the given id and of the given type.
+ *
+ * @param fieldId is the identifier of the field.
+ * @return the view or <code>null</code>.
+ */
+ protected final TextEditor getTextEditor(String fieldId) {
+ View v = findViewWithTag(fieldId);
+ if (v instanceof EditText) {
+ return new TextEditor((EditText)v);
+ }
+ if (v instanceof TextView) {
+ return new TextEditor((TextView)v);
+ }
+ return null;
+ }
+
+ /** Replies the boolean editor with the given id and of the given type.
+ *
+ * @param fieldId is the identifier of the field.
+ * @return the view or <code>null</code>.
+ */
+ protected final BooleanEditor getBooleanEditor(String fieldId) {
+ View v = findViewWithTag(fieldId);
+ if (v instanceof Switch) {
+ return new BooleanEditor((Switch)v);
+ }
+ if (v instanceof CheckBox) {
+ return new BooleanEditor((CheckBox)v);
+ }
+ if (v instanceof ImageView) {
+ return new BooleanEditor((ImageView)v);
+ }
+ return null;
+ }
+
+ /** Replies the color editor with the given id and of the given type.
+ *
+ * @param fieldId is the identifier of the field.
+ * @return the view or <code>null</code>.
+ */
+ protected final ColorEditor getColorEditor(String fieldId) {
+ View v = findViewWithTag(fieldId);
+ if (v instanceof ColorButton) {
+ return new ColorEditor((ColorButton)v);
+ }
+ if (v instanceof TextView) {
+ return new ColorEditor((TextView)v);
+ }
+ return null;
+ }
+
+ /** Replies the color editor with the given id and of the given type.
+ *
+ * @param <T> is the type of the elements in the combo.
+ * @param fieldId is the identifier of the field.
+ * @param type is the type of the elements in the combo.
+ * @return the view or <code>null</code>.
+ */
+ protected final <T> ComboEditor<T> getComboEditor(String fieldId, Class<T> type) {
+ View v = findViewWithTag(fieldId);
+ if (v instanceof ColorButton) {
+ return new ComboEditor<T>((ColorButton)v);
+ }
+ if (v instanceof TextView) {
+ return new ComboEditor<T>((TextView)v);
+ }
+ return null;
+ }
+
+ private TextView addLabel(TableRow row, String label) {
+ TextView labelView = new TextView(getContext());
+ labelView.setText(label);
+ row.addView(labelView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ return labelView;
+ }
+
+ private TextView addLabel(TableRow row, String label, int style) {
+ TextView labelView = new TextView(getContext());
+ if (style!=0) labelView.setInputType(style);
+ if (label!=null) labelView.setText(label);
+ row.addView(labelView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ return labelView;
+ }
+
+ /** Add a field in the editor that permits to enter a string.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addStringField(String label, String fieldId, String value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setText(value);
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row, value, 0));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.STRING);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter an integer.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param isPositiveOnly indicates if only the positives numbers are supported.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addIntegerField(String label, String fieldId, boolean isPositiveOnly, long value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType = InputType.TYPE_CLASS_NUMBER;
+ if (!isPositiveOnly) inputType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ editView.setText(Long.toString(
+ isPositiveOnly ? Math.abs(value) : value));
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row,Long.toString(value), inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.INTEGER);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a floating-point number.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param isPositiveOnly indicates if only the positives numbers are supported.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addFloatField(String label, String fieldId, boolean isPositiveOnly, double value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType = InputType.TYPE_CLASS_NUMBER|InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ if (!isPositiveOnly) inputType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ editView.setText(Double.toString(
+ isPositiveOnly ? Math.abs(value) : value));
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row,Double.toString(value), inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.FLOAT);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a boolean value.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final BooleanEditor addBooleanField(String label, String fieldId, boolean value) {
+ BooleanEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ if (isEditable()) {
+ Switch editView = new Switch(getContext());
+ editView.setTag(fieldId);
+ editView.setChecked(value);
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new BooleanEditor(editView);
+ }
+ else {
+ ImageView imageView = new ImageView(getContext());
+ row.addView(imageView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new BooleanEditor(imageView);
+ editor.setChecked(value);
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.BOOLEAN);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a textual password.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addTextPasswordField(String label, String fieldId, String value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType =
+ InputType.TYPE_CLASS_TEXT
+ |InputType.TYPE_TEXT_VARIATION_PASSWORD;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ editView.setText(value);
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row, value, inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.PASSWORD);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a numeric password.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addNumPasswordField(String label, String fieldId, int value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType = InputType.TYPE_CLASS_NUMBER
+ |InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ editView.setText(Integer.toString(value));
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row, Integer.toString(value), inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.PASSWORD);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter an email.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addEmailField(String label, String fieldId, URI value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType = InputType.TYPE_CLASS_TEXT
+ |InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ editView.setText(value.toString());
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ editor = new TextEditor(addLabel(row, value.toString(), inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.EMAIL);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter an uri.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final TextEditor addUriField(String label, String fieldId, URL value) {
+ TextEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ int inputType = InputType.TYPE_CLASS_TEXT
+ |InputType.TYPE_TEXT_VARIATION_URI;
+ if (isEditable()) {
+ EditText editView = new EditText(getContext());
+ editView.setTag(fieldId);
+ editView.setInputType(inputType);
+ if (value!=null) editView.setText(value.toExternalForm());
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new TextEditor(editView);
+ }
+ else {
+ String v = null;
+ if (value!=null) v = value.toExternalForm();
+ editor = new TextEditor(addLabel(row, v, inputType));
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.URL);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a color.
+ *
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final ColorEditor addColorField(String label, String fieldId, Color value) {
+ ColorEditor editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ // Field
+ if (isEditable()) {
+ ColorButton editView = new ColorButton(getContext());
+ editView.setTag(fieldId);
+ editView.setColor(value==null ? null : value.getRGB());
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new ColorEditor(editView);
+ }
+ else {
+ Color c = value==null ? Colors.BLACK : value;
+ editor = new ColorEditor(addLabel(row, null, 0));
+ editor.setColor(c);
+ }
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.COLOR);
+
+ return editor;
+ }
+
+ /** Add a field in the editor that permits to enter a value with a combo.
+ *
+ * @param <T> is the type of the choices.
+ * @param label is the label associated with the field.
+ * @param fieldId is the identifier of the field.
+ * @param choices are the possible choices in the combo.
+ * @param value is the value in the field.
+ * @return the edition view.
+ */
+ protected final <T> ComboEditor<T> addComboField(String label, String fieldId, List<T> choices, T value) {
+ ComboEditor<T> editor;
+ TableRow row = new TableRow(getContext());
+ // Label
+ addLabel(row, label);
+ if (isEditable()) {
+ // Spinner
+ Spinner editView = new Spinner(getContext());
+ editView.setTag(fieldId);
+ ArrayAdapter<T> adapter = new ArrayAdapter<T>(getContext(), android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ editView.setAdapter(adapter);
+ row.addView(editView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new ComboEditor<T>(editView, adapter);
+ }
+ else {
+ TextView textView = new TextView(getContext());
+ textView.setTag(fieldId);
+ row.addView(textView, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f));
+ editor = new ComboEditor<T>(textView);
+ }
+
+ editor.setValue(value);
+
+ this.tableLayout.addView(row, new TableRow.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ this.fieldTypes.put(fieldId, FieldType.COMBO);
+
+ return editor;
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public Map<String,Object> getEditedProperties() {
+ Map<String,Object> properties = new TreeMap<String,Object>();
+ for(Entry<String,FieldType> entry : this.fieldTypes.entrySet()) {
+ switch(entry.getValue()) {
+ case BOOLEAN:
+ {
+ BooleanEditor editor = getBooleanEditor(entry.getKey());
+ if (editor!=null) {
+ properties.put(entry.getKey(), editor.isChecked());
+ }
+ }
+ break;
+ case STRING:
+ case PASSWORD:
+ {
+ TextEditor editor = getTextEditor(entry.getKey());
+ if (editor!=null) {
+ properties.put(entry.getKey(), editor.getText());
+ }
+ }
+ break;
+ case EMAIL:
+ {
+ TextEditor editor = getTextEditor(entry.getKey());
+ if (editor!=null) {
+ try {
+ properties.put(entry.getKey(), new URI(editor.getText()));
+ }
+ catch (URISyntaxException e) {
+ Log.e(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ }
+ }
+ break;
+ case URL:
+ {
+ TextEditor editor = getTextEditor(entry.getKey());
+ if (editor!=null) {
+ try {
+ properties.put(entry.getKey(), new URL(editor.getText()));
+ }
+ catch (MalformedURLException e) {
+ Log.e(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ }
+ }
+ break;
+ case FLOAT:
+ {
+ TextEditor editor = getTextEditor(entry.getKey());
+ if (editor!=null) {
+ try {
+ properties.put(entry.getKey(), Double.parseDouble(editor.getText()));
+ }
+ catch (Exception e) {
+ Log.e(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ }
+ }
+ break;
+ case INTEGER:
+ {
+ TextEditor editor = getTextEditor(entry.getKey());
+ if (editor!=null) {
+ try {
+ properties.put(entry.getKey(), Long.parseLong(editor.getText()));
+ }
+ catch (Exception e) {
+ Log.e(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ }
+ }
+ break;
+ case COLOR:
+ {
+ ColorEditor editor = getColorEditor(entry.getKey());
+ if (editor!=null) {
+ properties.put(entry.getKey(), editor.getColor());
+ }
+ }
+ break;
+ case COMBO:
+ {
+ ComboEditor<?> editor = getComboEditor(entry.getKey(), Object.class);
+ if (editor!=null) {
+ Object value = editor.getValue();
+ properties.put(entry.getKey(), value);
+ }
+ }
+ break;
+ default:
+ }
+ }
+ return properties;
+ }
+
+ /** Wrapper to a TextEdit or a TextView depending the editable flag.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ protected static class TextEditor {
+
+ private final EditText edit;
+ private final TextView view;
+
+
+ /**
+ * @param edit
+ */
+ public TextEditor(EditText edit) {
+ this.edit = edit;
+ this.view = null;
+ }
+
+ /**
+ * @param view
+ */
+ public TextEditor(TextView view) {
+ this.edit = null;
+ this.view = view;
+ }
+
+ /** Change the text
+ *
+ * @param text
+ */
+ public void setText(String text) {
+ if (this.edit!=null) {
+ this.edit.setText(text);
+ }
+ else {
+ this.view.setText(text);
+ }
+ }
+
+ /** Replies the text.
+ *
+ * @return the text.
+ */
+ public String getText() {
+ if (this.edit!=null) {
+ return this.edit.getText().toString();
+ }
+ return this.view.getText().toString();
+ }
+
+ } // class TextEditor
+
+ /** Wrapper to a CheckBox or a ImageView depending the editable flag.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ protected static class BooleanEditor {
+
+ private final CheckBox edit1;
+ private final Switch edit2;
+ private final ImageView view;
+ private int resourceId = 0;
+
+
+ /**
+ * @param edit
+ */
+ public BooleanEditor(CheckBox edit) {
+ this.edit1 = edit;
+ this.edit2 = null;
+ this.view = null;
+ }
+
+ /**
+ * @param edit
+ */
+ public BooleanEditor(Switch edit) {
+ this.edit1 = null;
+ this.edit2 = edit;
+ this.view = null;
+ }
+
+ /**
+ * @param view
+ */
+ public BooleanEditor(ImageView view) {
+ this.edit1 = null;
+ this.edit2 = null;
+ this.view = view;
+ }
+
+ /** Change the check
+ *
+ * @param checked
+ */
+ public void setChecked(boolean checked) {
+ if (this.edit2!=null) {
+ this.edit2.setChecked(checked);
+ }
+ else if (this.edit1!=null) {
+ this.edit1.setChecked(checked);
+ }
+ else {
+ this.resourceId = checked ?
+ android.R.drawable.checkbox_on_background :
+ android.R.drawable.checkbox_off_background;
+ this.view.setImageResource(this.resourceId);
+ }
+ }
+
+ /** Replies the check.
+ *
+ * @return the check.
+ */
+ public boolean isChecked() {
+ if (this.edit2!=null) {
+ return this.edit2.isChecked();
+ }
+ if (this.edit1!=null) {
+ return this.edit1.isChecked();
+ }
+ return this.resourceId==android.R.drawable.checkbox_on_background;
+ }
+
+ } // class BooleanEditor
+
+ /** Wrapper to a ColorButton or a TextView depending the editable flag.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ protected static class ColorEditor {
+
+ private final ColorButton edit;
+ private final TextView view;
+ private Color color = null;
+
+
+ /**
+ * @param edit
+ */
+ public ColorEditor(ColorButton edit) {
+ this.edit = edit;
+ this.view = null;
+ }
+
+ /**
+ * @param view
+ */
+ public ColorEditor(TextView view) {
+ this.edit = null;
+ this.view = view;
+ }
+
+ /** Change the color
+ *
+ * @param color
+ */
+ public void setColor(Color color) {
+ if (this.edit!=null) {
+ if (color==null)
+ this.edit.setColor((Integer)null);
+ else
+ this.edit.setColor(color.getRGB());
+ }
+ else {
+ if (color==null) {
+ this.view.setBackgroundResource(R.drawable.hatchs);
+ }
+ else {
+ this.view.setBackgroundDrawable(new ColorDrawable(this.color.getRGB()));
+ }
+ }
+ }
+
+ /** Replies the color.
+ *
+ * @return the color or <code>null</code> if default color.
+ */
+ public Color getColor() {
+ if (this.edit!=null) {
+ Integer c = this.edit.getColor();
+ if (c==null) return null;
+ return VectorToolkit.color(c.intValue());
+ }
+ return this.color;
+ }
+
+ } // class ColorEditor
+
+ /** Wrapper to a Spinner or a TextView depending the editable flag.
+ *
+ * @param <T> is the type of the values in the combo.
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ protected static class ComboEditor<T> {
+
+ private final TextView view;
+ private final ArrayAdapter<T> adapter;
+ private final Spinner edit;
+ private T value;
+
+
+ /**
+ * @param view
+ */
+ public ComboEditor(TextView view) {
+ this.view = view;
+ this.edit = null;
+ this.adapter = null;
+ }
+
+ /**
+ * @param edit
+ * @param adapter
+ */
+ public ComboEditor(Spinner edit, ArrayAdapter<T> adapter) {
+ this.view = null;
+ this.edit = edit;
+ this.adapter = adapter;
+ this.value = null;
+ }
+
+ /** Change the value
+ *
+ * @param value
+ */
+ public void setValue(T value) {
+ if (value!=null) {
+ if (this.edit!=null) {
+ int pos = this.adapter.getPosition(value);
+ if (pos>=0) {
+ this.edit.setSelection(pos);
+ }
+ }
+ else if (this.view!=null) {
+ this.view.setText(value.toString());
+ }
+ }
+ }
+
+ /** Replies the value.
+ *
+ * @return the value.
+ */
+ @SuppressWarnings("unchecked")
+ public T getValue() {
+ if (this.edit!=null) {
+ return (T)this.edit.getSelectedItem();
+ }
+ return this.value;
+ }
+
+ } // class ComboEditor
+
+ /** Types of fields.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ public enum FieldType {
+ /** String. */
+ STRING,
+ /** Integer. */
+ INTEGER,
+ /** Float. */
+ FLOAT,
+ /** Boolean. */
+ BOOLEAN,
+ /** Color. */
+ COLOR,
+ /** Uri. */
+ URL,
+ /** Email. */
+ EMAIL,
+ /** Password. */
+ PASSWORD,
+ /** Combo. */
+ COMBO;
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/texteditor/TextEditorActivity.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/texteditor/TextEditorActivity.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/texteditor/TextEditorActivity.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,186 @@
+package org.arakhne.afc.ui.android.texteditor;
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URL;
+
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.vmutil.FileSystem;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+
+/**
+ * Text editor embedded inside an activity fragment.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class TextEditorActivity extends Activity {
+
+ private EditText textView = null;
+ private File file = null;
+ private boolean changedSinceOriginal = false;
+ private boolean changedSinceLastSave = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.texteditor);
+
+ this.file = null;
+ this.changedSinceOriginal = false;
+ this.changedSinceLastSave = false;
+ this.textView = (EditText)findViewById(R.id.textEditor);
+
+ Intent intent = getIntent();
+
+ this.textView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ //
+ }
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ //
+ }
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!TextEditorActivity.this.changedSinceLastSave) {
+ TextEditorActivity.this.changedSinceLastSave = true;
+ TextEditorActivity.this.changedSinceOriginal = true;
+ TextEditorActivity.this.invalidateOptionsMenu();
+ }
+ }
+ });
+
+ Uri uri = intent.getData();
+ if (uri!=null) {
+ try {
+ URI jURI = new URI(uri.toString());
+ URL jURL = jURI.toURL();
+
+ try {
+ this.file = FileSystem.convertURLToFile(jURL);
+ }
+ catch(Throwable _) {
+ //
+ }
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(jURL.openStream()));
+ try {
+ StringBuilder text = new StringBuilder();
+ char[] buffer = new char[2048];
+ int n = reader.read(buffer, 0, 2048);
+ while (n>0) {
+ text.append(buffer, 0, n);
+ n = reader.read(buffer, 0, 2048);
+ }
+ this.textView.setText(text);
+ }
+ finally {
+ reader.close();
+ }
+ }
+ catch(Throwable e) {
+ Log.w(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.texteditor_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ MenuItem item;
+ item = menu.findItem(R.id.textEditorSaveMenu);
+ item.setEnabled(this.file!=null && this.changedSinceLastSave);
+ item = menu.findItem(R.id.textEditorUploadMenu);
+ item.setEnabled(this.file!=null && this.changedSinceOriginal);
+ return true;
+ }
+
+ private void save() {
+ if (this.file!=null && this.changedSinceLastSave) {
+ try {
+ FileWriter fw = new FileWriter(this.file);
+ try {
+ fw.write(this.textView.getText().toString());
+ fw.flush();
+ }
+ finally {
+ fw.close();
+ }
+ }
+ catch(Throwable e) {
+ Log.w(getClass().getName(), e.getLocalizedMessage(), e);
+ }
+ this.changedSinceLastSave = false;
+ invalidateOptionsMenu();
+ }
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ int id = item.getItemId();
+ if (id==R.id.textEditorSaveMenu) {
+ save();
+ return true;
+ }
+ if (id==R.id.textEditorUploadMenu) {
+ save();
+ Intent data = new Intent();
+ Uri uri = Uri.fromFile(this.file);
+ data.setData(uri);
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ }
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+}
\ No newline at end of file
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/DroidZoomableGraphics2D.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/DroidZoomableGraphics2D.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/DroidZoomableGraphics2D.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,890 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.zoom;
+
+import java.net.URL;
+
+import org.arakhne.afc.math.continous.object2d.Circle2f;
+import org.arakhne.afc.math.continous.object2d.Ellipse2f;
+import org.arakhne.afc.math.continous.object2d.Path2f;
+import org.arakhne.afc.math.continous.object2d.PathElement2f;
+import org.arakhne.afc.math.continous.object2d.PathIterator2f;
+import org.arakhne.afc.math.continous.object2d.Point2f;
+import org.arakhne.afc.math.continous.object2d.Rectangle2f;
+import org.arakhne.afc.math.continous.object2d.RoundRectangle2f;
+import org.arakhne.afc.math.continous.object2d.Segment2f;
+import org.arakhne.afc.math.continous.object2d.Shape2f;
+import org.arakhne.afc.math.generic.PathWindingRule;
+import org.arakhne.afc.math.matrix.Transform2D;
+import org.arakhne.afc.ui.CenteringTransform;
+import org.arakhne.afc.ui.Graphics2DLOD;
+import org.arakhne.afc.ui.StringAnchor;
+import org.arakhne.afc.ui.ZoomableContext;
+import org.arakhne.afc.ui.ZoomableContextUtil;
+import org.arakhne.afc.ui.vector.AbstractVectorGraphics2D;
+import org.arakhne.afc.ui.vector.Color;
+import org.arakhne.afc.ui.vector.Composite;
+import org.arakhne.afc.ui.vector.Font;
+import org.arakhne.afc.ui.vector.FontMetrics;
+import org.arakhne.afc.ui.vector.Image;
+import org.arakhne.afc.ui.vector.ImageObserver;
+import org.arakhne.afc.ui.vector.Stroke;
+import org.arakhne.afc.ui.vector.VectorToolkit;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.DashPathEffect;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Join;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.PathEffect;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+/**
+ * This is the droid-based implementation of a VectorGraphics2D and a ZoomableContext.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+public class DroidZoomableGraphics2D extends AbstractVectorGraphics2D implements ZoomableContext {
+
+ /** Convert the droid matrix to Arakhne transformation matrix.
+ *
+ * @param m is the matrix to convert to a transformation.
+ * @param scale is the scaling factor to apply.
+ * @return the Arakhne transformation matrix.
+ */
+ public static Transform2D convertMatrix(Matrix m, float scale) {
+ assert(m!=null);
+ if (m.isIdentity()) return new Transform2D();
+ float[] values = new float[9];
+ m.getValues(values);
+ return new Transform2D(
+ ZoomableContextUtil.pixel2logical_size(values[0], scale),
+ values[1],
+ ZoomableContextUtil.pixel2logical_size(values[2], scale),
+ values[3],
+ ZoomableContextUtil.pixel2logical_size(values[4], scale),
+ ZoomableContextUtil.pixel2logical_size(values[5], scale));
+ }
+
+ /** Convert the Arakhne transformation matrix to droid matrix.
+ *
+ * @param t is the transformation to convert to a matrix.
+ * @param scale is the scaling factor to apply.
+ * @return droid matrix.
+ */
+ public static Matrix convertTransformation(Transform2D t, float scale) {
+ assert(t!=null);
+ Matrix at = new Matrix();
+ if (!t.isIdentity()) {
+ float[] values = new float[] {
+ ZoomableContextUtil.logical2pixel_size(t.m00, scale),
+ t.m01,
+ ZoomableContextUtil.logical2pixel_size(t.m02, scale),
+ t.m10,
+ ZoomableContextUtil.logical2pixel_size(t.m11, scale),
+ ZoomableContextUtil.logical2pixel_size(t.m12, scale),
+ t.m20, t.m21, t.m22
+ };
+ at.setValues(values);
+ }
+ return at;
+ }
+
+ /** Set the color of the given paint to the given color.
+ * This function tries the Android color objects that
+ * are able to represent a color (ARGB color, Drawable).
+ *
+ * @param paint
+ * @param color
+ */
+ protected static void setColor(Paint paint, Color color) {
+ Object androidColor = VectorToolkit.nativeUIObject(Object.class, color);
+ if (androidColor instanceof Drawable) {
+ //paint.setColor((Drawable)androidColor);
+ }
+ else if (androidColor instanceof Number) {
+ paint.setColor(((Number)androidColor).intValue());
+ }
+ }
+
+
+ /** Android canvas.
+ */
+ protected final Canvas canvas;
+
+ /** Default color to draw the background.
+ *
+ * @see #background
+ */
+ protected final Color defaultBackground;
+
+ /** Default Level-of-Detail.
+ *
+ * @see #levelOfDetail
+ */
+ protected final Graphics2DLOD defaultLevelOfDetail;
+
+ /** Current Level-of-Detail.
+ *
+ * @see #defaultLevelOfDetail
+ */
+ protected Graphics2DLOD levelOfDetail;
+
+ /** Android painter to use for filling the shapes.
+ */
+ protected Paint fillPainter;
+
+ /** Android painter to use for drawing the outlines of the shapes.
+ */
+ protected Paint linePainter;
+
+ /** Android painter to use for drawing the texts.
+ */
+ protected Paint fontPainter;
+
+ /** Current background color.
+ *
+ * @see #defaultBackground
+ */
+ protected Color background;
+
+ private final int initialSaveCount;
+
+ /** Scaling factor to apply when drawing.
+ */
+ protected final float scale;
+
+ /** Transformation to apply to center the objects on the view.
+ */
+ protected final CenteringTransform centeringTransform;
+
+ /** Temp rectangle used in internal computations.
+ */
+ protected final Rectangle2f tmpRect = new Rectangle2f();
+
+ /** Default color to fill the shapes.
+ *
+ * @see #fillPainter
+ */
+ protected final Color defaultFillColor;
+
+ /** Default color to outline the shapes.
+ *
+ * @see #linePainter
+ */
+ protected final Color defaultLineColor;
+
+ /** Scaling sensitivity.
+ */
+ protected final float scalingSensitivity;
+
+ /** Coordinate of the point that is drawn at the center of the view.
+ */
+ protected final float focusX;
+
+ /** Coordinate of the point that is drawn at the center of the view.
+ */
+ protected final float focusY;
+
+ /** Maximal value for the scale factor.
+ */
+ protected final float maxScale;
+
+ /** Minimal value for the scale factor.
+ */
+ protected final float minScale;
+
+ /**
+ * @param canvas
+ * @param defaultFillColor is the default color for filling.
+ * @param defaultLineColor is the default color for the lines.
+ * @param scaleFactor is the scaling factor to apply.
+ * @param centeringTransform is the transformation used to draw the objects at the center of the view.
+ * @param background is the background color.
+ * @param isAntiAlias indicates if antialiasing is used when drawing.
+ * @param scalingSensitivity is the sensitivity of the scaling actions.
+ * @param focusX is the coordinate of the focus point.
+ * @param focusY is the coordinate of the focus point.
+ * @param minScaleFactor is the minimal allowed scaling factor.
+ * @param maxScaleFactor is the maximal allowed scaling factor.
+ */
+ public DroidZoomableGraphics2D(
+ Canvas canvas,
+ Color defaultFillColor,
+ Color defaultLineColor,
+ float scaleFactor,
+ CenteringTransform centeringTransform,
+ Color background,
+ boolean isAntiAlias,
+ float scalingSensitivity,
+ float focusX,
+ float focusY,
+ float minScaleFactor,
+ float maxScaleFactor) {
+ super(
+ defaultFillColor,
+ defaultLineColor,
+ null,
+ true,
+ true,
+ null);
+ this.defaultFillColor = defaultFillColor;
+ this.defaultLineColor = defaultLineColor;
+ this.scalingSensitivity = scalingSensitivity;
+ this.focusX = focusX;
+ this.focusY = focusY;
+ this.minScale = minScaleFactor;
+ this.maxScale = maxScaleFactor;
+ this.canvas = canvas;
+ this.scale = scaleFactor;
+ this.centeringTransform = centeringTransform;
+ this.defaultBackground = this.background = background;
+ this.initialSaveCount = canvas.getSaveCount();
+ this.defaultLevelOfDetail = this.levelOfDetail = isAntiAlias ? Graphics2DLOD.NORMAL_LEVEL_OF_DETAIL : Graphics2DLOD.LOW_LEVEL_OF_DETAIL;
+ resetPainters();
+ }
+
+ @Override
+ public void dispose() {
+ this.background = null;
+ this.fillPainter = null;
+ this.fontPainter = null;
+ this.linePainter = null;
+ this.levelOfDetail = null;
+ super.dispose();
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ this.levelOfDetail = this.defaultLevelOfDetail;
+ this.background = this.defaultBackground;
+ setFillColor(this.defaultFillColor);
+ setOutlineColor(this.defaultLineColor);
+ resetPainters();
+ while (this.canvas.getSaveCount() > this.initialSaveCount) {
+ this.canvas.restore();
+ }
+ }
+
+ /** Reset the painters to the default attribute values.
+ */
+ protected void resetPainters() {
+ boolean isNormalLod = this.levelOfDetail.compareTo(Graphics2DLOD.NORMAL_LEVEL_OF_DETAIL)>=0;
+
+ this.fillPainter = new Paint();
+ this.fillPainter.setStyle(Style.FILL);
+ this.fillPainter.setAntiAlias(isNormalLod);
+ this.linePainter = new Paint();
+ this.linePainter.setStyle(Style.STROKE);
+ this.linePainter.setAntiAlias(isNormalLod);
+ this.fontPainter = new Paint();
+ this.fontPainter.setStyle(Style.FILL_AND_STROKE);
+ this.fontPainter.setAntiAlias(isNormalLod);
+ this.fontPainter.setTextSize(
+ ZoomableContextUtil.logical2pixel_size(
+ this.fontPainter.getTextSize(),
+ this.scale));
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ protected void log(String message) {
+ Log.d(toString(), message);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ protected void log(String message, Throwable exception) {
+ Log.d(toString(), message, exception);
+ }
+
+ /** Replies the droid canvas.
+ *
+ * @return the droid canvas.
+ */
+ public Canvas getCanvas() {
+ return this.canvas;
+ }
+
+ @Override
+ public Color setOutlineColor(Color color) {
+ setColor(this.linePainter, color);
+ setColor(this.fontPainter, color);
+ return super.setOutlineColor(color);
+ }
+
+ @Override
+ public Color setFillColor(Color color) {
+ setColor(this.fillPainter, color);
+ return super.setFillColor(color);
+ }
+
+ @Override
+ public Graphics2DLOD getLOD() {
+ return this.levelOfDetail;
+ }
+
+ /** Change the LOD of the graphics2D.
+ *
+ * @param lod
+ */
+ public void setLOD(Graphics2DLOD lod) {
+ if (lod!=null) {
+ this.levelOfDetail = lod;
+ boolean isNormalLod = this.levelOfDetail.compareTo(Graphics2DLOD.NORMAL_LEVEL_OF_DETAIL)>=0;
+ this.fillPainter.setAntiAlias(isNormalLod);
+ this.linePainter.setAntiAlias(isNormalLod);
+ this.fontPainter.setAntiAlias(isNormalLod);
+ }
+ }
+
+ @Override
+ public StringAnchor getStringAnchor() {
+ return StringAnchor.LEFT_BASELINE;
+ }
+
+ @Override
+ public void drawPoint(float x, float y) {
+ preDrawing();
+ float px = ZoomableContextUtil.logical2pixel_x(x, this.centeringTransform, this.scale);
+ float py = ZoomableContextUtil.logical2pixel_y(y, this.centeringTransform, this.scale);
+ this.canvas.drawRect(px-.5f, py-.5f, px+.5f, py+.5f, this.fillPainter);
+ postDrawing();
+ }
+
+ @Override
+ public Font getFont() {
+ Paint scaledFont = new Paint(this.fontPainter);
+ scaledFont.setTextSize(
+ ZoomableContextUtil.pixel2logical_size(this.fontPainter.getTextSize(), this.scale));
+ return VectorToolkit.font(scaledFont);
+ }
+
+ @Override
+ public void setFont(Font font) {
+ Paint p = VectorToolkit.nativeUIObject(Paint.class, font);
+ assert(p!=null);
+ this.fontPainter = new Paint(p);
+ this.fontPainter.setTextSize(
+ ZoomableContextUtil.logical2pixel_size(p.getTextSize(), this.scale));
+ }
+
+ @Override
+ public FontMetrics getFontMetrics() {
+ Paint scaledFont = new Paint(this.fontPainter);
+ scaledFont.setTextSize(
+ ZoomableContextUtil.pixel2logical_size(this.fontPainter.getTextSize(), this.scale));
+ return VectorToolkit.fontMetrics(scaledFont);
+ }
+
+ @Override
+ public FontMetrics getFontMetrics(Font f) {
+ return VectorToolkit.fontMetrics(f);
+ }
+
+ @Override
+ public Shape2f getClip() {
+ Rect r = this.canvas.getClipBounds();
+ Rectangle2f rr = new Rectangle2f(r.left, r.right, r.width(), r.height());
+ ZoomableContextUtil.pixel2logical(rr, this.centeringTransform, this.scale);
+ return rr;
+ }
+
+ @Override
+ public void setClip(Shape2f clip) {
+ Rectangle2f r = clip.toBoundingBox();
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scale);
+ this.canvas.clipRect(r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY(), Op.REPLACE);
+ }
+
+ @Override
+ public void clip(Shape2f clip) {
+ Rectangle2f r = clip.toBoundingBox();
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scale);
+ this.canvas.clipRect(r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY(), Op.UNION);
+ }
+
+ @Override
+ public boolean drawImage(URL imageURL, Image img, float dx1, float dy1,
+ float dx2, float dy2, int sx1, int sy1, int sx2, int sy2) {
+ preDrawing();
+ Bitmap bitmap = VectorToolkit.nativeUIObject(Bitmap.class, img);
+ assert(bitmap!=null);
+ this.tmpRect.setFromCorners(dx1, dy1, dx2, dy2);
+ ZoomableContextUtil.logical2pixel(this.tmpRect, this.centeringTransform, this.scale);
+ Rect srcRect = new Rect(sx1, sy1, sx2, sy2);
+ RectF dstRect = new RectF(this.tmpRect.getMinX(), this.tmpRect.getMinY(), this.tmpRect.getMaxX(), this.tmpRect.getMaxY());
+ this.canvas.drawBitmap(bitmap, srcRect, dstRect, VectorToolkit.getObjectWithId(0, Paint.class));
+ postDrawing();
+ return true;
+ }
+
+ @Override
+ public boolean drawImage(URL imageURL, Image img, float dx1, float dy1,
+ float dx2, float dy2, int sx1, int sy1, int sx2, int sy2,
+ ImageObserver observer) {
+ preDrawing();
+ Bitmap bitmap = VectorToolkit.nativeUIObject(Bitmap.class, img);
+ assert(bitmap!=null);
+ this.tmpRect.setFromCorners(dx1, dy1, dx2, dy2);
+ ZoomableContextUtil.logical2pixel(this.tmpRect, this.centeringTransform, this.scale);
+ Rect srcRect = new Rect(sx1, sy1, sx2, sy2);
+ RectF dstRect = new RectF(this.tmpRect.getMinX(), this.tmpRect.getMinY(), this.tmpRect.getMaxX(), this.tmpRect.getMaxY());
+ this.canvas.drawBitmap(bitmap, srcRect, dstRect, VectorToolkit.getObjectWithId(0, Paint.class));
+ postDrawing();
+ return true;
+ }
+
+ @Override
+ public void clear(Shape2f s) {
+ Paint old = this.fillPainter;
+ this.fillPainter = VectorToolkit.getObjectWithId(0, Paint.class);
+ this.fillPainter.setAlpha(1);
+ Color b = getBackground();
+ if (b!=null) {
+ setColor(this.fillPainter, b);
+ }
+ drawOnCanvas(s, this.fillPainter);
+ this.fillPainter = old;
+ postDrawing();
+ }
+
+ private void drawOnCanvas(Shape2f s, Paint painter) {
+ if (s!=null) {
+ PathIterator2f path = null;
+ if (s instanceof Path2f) {
+ path = ((Path2f)s).getPathIterator();
+ }
+ else {
+ if (s instanceof Rectangle2f) {
+ Rectangle2f r = (Rectangle2f)s;
+ this.tmpRect.set(r);
+ ZoomableContextUtil.logical2pixel(this.tmpRect, this.centeringTransform, this.scale);
+ this.canvas.drawRect(this.tmpRect.getMinX(), this.tmpRect.getMinY(), this.tmpRect.getMaxX(), this.tmpRect.getMaxY(), painter);
+ return;
+ }
+ if (s instanceof Ellipse2f) {
+ Ellipse2f r = (Ellipse2f)s;
+ this.tmpRect.set(r);
+ ZoomableContextUtil.logical2pixel(this.tmpRect, this.centeringTransform, this.scale);
+ this.canvas.drawOval(
+ new RectF(this.tmpRect.getMinX(), this.tmpRect.getMinY(), this.tmpRect.getMaxX(), this.tmpRect.getMaxY()), painter);
+ return;
+ }
+ if (s instanceof Circle2f) {
+ Circle2f r = (Circle2f)s;
+ float cx = ZoomableContextUtil.logical2pixel_x(r.getX(), this.centeringTransform, this.scale);
+ float cy = ZoomableContextUtil.logical2pixel_y(r.getY(), this.centeringTransform, this.scale);
+ float radius = ZoomableContextUtil.logical2pixel_size(r.getRadius(), this.scale);
+ this.canvas.drawCircle(cx, cy, radius, painter);
+ return;
+ }
+ if (s instanceof RoundRectangle2f) {
+ RoundRectangle2f r = (RoundRectangle2f)s;
+ this.tmpRect.set(r);
+ ZoomableContextUtil.logical2pixel(this.tmpRect, this.centeringTransform, this.scale);
+ float aw = ZoomableContextUtil.logical2pixel_size(r.getArcWidth()/2f, this.scale);
+ float ah = ZoomableContextUtil.logical2pixel_size(r.getArcHeight()/2f, this.scale);
+ this.canvas.drawRoundRect(
+ new RectF(this.tmpRect.getMinX(), this.tmpRect.getMinY(), this.tmpRect.getMaxX(), this.tmpRect.getMaxY()),
+ aw, ah, painter);
+ return;
+ }
+ if (s instanceof Segment2f) {
+ Segment2f l = (Segment2f)s;
+ this.canvas.drawLine(
+ ZoomableContextUtil.logical2pixel_x(l.getX1(), this.centeringTransform, this.scale),
+ ZoomableContextUtil.logical2pixel_y(l.getY1(), this.centeringTransform, this.scale),
+ ZoomableContextUtil.logical2pixel_x(l.getX2(), this.centeringTransform, this.scale),
+ ZoomableContextUtil.logical2pixel_y(l.getY2(), this.centeringTransform, this.scale),
+ painter);
+ return;
+ }
+
+ path = s.getPathIterator();
+ }
+ if (path!=null) {
+ path = ZoomableContextUtil.logical2pixel(path, this.centeringTransform, this.scale);
+ Path droidPath = new Path();
+ if (path.getWindingRule()==PathWindingRule.EVEN_ODD) {
+ droidPath.setFillType(FillType.EVEN_ODD);
+ }
+ PathElement2f e;
+ while (path.hasNext()) {
+ e = path.next();
+ switch(e.type) {
+ case MOVE_TO:
+ droidPath.moveTo(e.toX, e.toY);
+ break;
+ case LINE_TO:
+ droidPath.lineTo(e.toX, e.toY);
+ break;
+ case CURVE_TO:
+ droidPath.cubicTo(e.ctrlX1, e.ctrlY1, e.ctrlX2, e.ctrlY2, e.toX, e.toY);
+ break;
+ case QUAD_TO:
+ droidPath.quadTo(e.ctrlX1, e.ctrlY1, e.toX, e.toY);
+ break;
+ case CLOSE:
+ droidPath.close();
+ break;
+ default:
+ }
+ }
+ this.canvas.drawPath(droidPath, painter);
+ }
+ else {
+ throw new IllegalArgumentException(s.toString());
+ }
+ }
+ }
+
+ @Override
+ public void draw(Shape2f s) {
+ preDrawing();
+ if (isInteriorPainted()) {
+ drawOnCanvas(s, this.fillPainter);
+ }
+ String text = getInteriorText();
+ if (text!=null && !text.isEmpty()) {
+ paintClippedString(
+ text,
+ s.toBoundingBox(),
+ s);
+ }
+ if (isOutlineDrawn()) {
+ drawOnCanvas(s, this.linePainter);
+ }
+ postDrawing();
+ }
+
+ @Override
+ public void drawString(String str, float x, float y) {
+ preDrawing();
+ this.canvas.drawText(str,
+ ZoomableContextUtil.logical2pixel_x(x, this.centeringTransform, this.scale),
+ ZoomableContextUtil.logical2pixel_y(y, this.centeringTransform, this.scale),
+ this.fontPainter);
+ postDrawing();
+ }
+
+ @Override
+ public void drawString(String str, float x, float y, Shape2f clip) {
+ preDrawing();
+ Shape2f c = getClip();
+ clip(clip);
+ this.canvas.drawText(str,
+ ZoomableContextUtil.logical2pixel_x(x, this.centeringTransform, this.scale),
+ ZoomableContextUtil.logical2pixel_y(y, this.centeringTransform, this.scale),
+ this.fontPainter);
+ setClip(c);
+ postDrawing();
+ }
+
+ @Override
+ public void translate(float tx, float ty) {
+ this.canvas.translate(
+ ZoomableContextUtil.logical2pixel_size(tx, this.scale),
+ ZoomableContextUtil.logical2pixel_size(ty, this.scale));
+ }
+
+ @Override
+ public void scale(float sx, float sy) {
+ this.canvas.scale(
+ ZoomableContextUtil.logical2pixel_size(sx, this.scale),
+ ZoomableContextUtil.logical2pixel_size(sy, this.scale));
+ }
+
+ @Override
+ public void rotate(float theta) {
+ this.canvas.rotate((float)Math.toDegrees(theta));
+ }
+
+ @Override
+ public void shear(float shx, float shy) {
+ this.canvas.skew(
+ ZoomableContextUtil.logical2pixel_size(shx, this.scale),
+ ZoomableContextUtil.logical2pixel_size(shy, this.scale));
+ }
+
+ @Override
+ public void transform(Transform2D Tx) {
+ this.canvas.concat(convertTransformation(Tx, this.scale));
+ }
+
+ @Override
+ public Transform2D setTransform(Transform2D Tx) {
+ Transform2D old = convertMatrix(this.canvas.getMatrix(), this.scale);
+ this.canvas.setMatrix(convertTransformation(Tx, this.scale));
+ return old;
+ }
+
+ @Override
+ public Transform2D getTransform() {
+ return convertMatrix(this.canvas.getMatrix(), this.scale);
+ }
+
+ @Override
+ public void setBackground(Color color) {
+ this.background = color;
+ }
+
+ @Override
+ public Color getBackground() {
+ return this.background;
+ }
+
+ @Override
+ public void setComposite(Composite composite) {
+ int alpha = (int)(composite.getAlpha() * 255);
+ this.fillPainter.setAlpha(alpha);
+ this.linePainter.setAlpha(alpha);
+ this.fontPainter.setAlpha(alpha);
+ }
+
+ @Override
+ public Composite getComposite() {
+ return VectorToolkit.composite(1);
+ }
+
+ @Override
+ public void setStroke(Stroke stroke) {
+ this.linePainter.setStrokeMiter(stroke.getMiterLimit());
+ this.fontPainter.setStrokeMiter(stroke.getMiterLimit());
+
+ Cap cap;
+ switch(stroke.getEndCap()) {
+ case BUTT:
+ cap = Cap.BUTT;
+ break;
+ case SQUARE:
+ cap = Cap.SQUARE;
+ break;
+ case ROUND:
+ cap = Cap.ROUND;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ this.linePainter.setStrokeCap(cap);
+ this.fontPainter.setStrokeCap(cap);
+
+ Join join;
+ switch(stroke.getLineJoin()) {
+ case BEVEL:
+ join = Join.BEVEL;
+ break;
+ case MITER:
+ join = Join.MITER;
+ break;
+ case ROUND:
+ join = Join.ROUND;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ this.linePainter.setStrokeJoin(join);
+ this.fontPainter.setStrokeJoin(join);
+
+ this.linePainter.setStrokeWidth(stroke.getLineWidth());
+ this.fontPainter.setStrokeWidth(stroke.getLineWidth());
+
+ float[] dashes = stroke.getDashArray();
+ PathEffect pe = null;
+ if (dashes!=null && dashes.length>=2) {
+ pe = new DashPathEffect(dashes, stroke.getDashPhase());
+ }
+ this.linePainter.setPathEffect(pe);
+ this.fontPainter.setPathEffect(pe);
+ }
+
+ @Override
+ public Stroke getStroke() {
+ return VectorToolkit.stroke(this.linePainter);
+ }
+
+ @Override
+ public float logical2pixel_size(float l) {
+ return ZoomableContextUtil.logical2pixel_size(l, this.scale);
+ }
+
+ @Override
+ public float logical2pixel_x(float l) {
+ return ZoomableContextUtil.logical2pixel_x(l, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public float logical2pixel_y(float l) {
+ return ZoomableContextUtil.logical2pixel_y(l, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public float pixel2logical_size(float l) {
+ return ZoomableContextUtil.pixel2logical_size(l, this.scale);
+ }
+
+ @Override
+ public float pixel2logical_x(float l) {
+ return ZoomableContextUtil.pixel2logical_x(l, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public float pixel2logical_y(float l) {
+ return ZoomableContextUtil.pixel2logical_y(l, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public PathIterator2f logical2pixel(PathIterator2f p) {
+ return ZoomableContextUtil.logical2pixel(p, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public PathIterator2f pixel2logical(PathIterator2f p) {
+ return ZoomableContextUtil.pixel2logical(p, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(Segment2f s) {
+ ZoomableContextUtil.logical2pixel(s, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(Segment2f s) {
+ ZoomableContextUtil.pixel2logical(s, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(RoundRectangle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(RoundRectangle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(Point2f p) {
+ ZoomableContextUtil.logical2pixel(p, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(Point2f p) {
+ ZoomableContextUtil.pixel2logical(p, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(Ellipse2f e) {
+ ZoomableContextUtil.logical2pixel(e, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(Ellipse2f e) {
+ ZoomableContextUtil.pixel2logical(e, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(Circle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(Circle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void logical2pixel(Rectangle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public void pixel2logical(Rectangle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public Shape2f logical2pixel(Shape2f s) {
+ return ZoomableContextUtil.logical2pixel(s, this.centeringTransform, this.scale);
+ }
+
+ @Override
+ public Shape2f pixel2logical(Shape2f s) {
+ return ZoomableContextUtil.pixel2logical(s, this.centeringTransform, this.scale);
+ }
+
+
+ @Override
+ public float getScalingSensitivity() {
+ return this.scalingSensitivity;
+ }
+
+ @Override
+ public float getFocusX() {
+ return this.focusX;
+ }
+
+ @Override
+ public float getFocusY() {
+ return this.focusY;
+ }
+
+ @Override
+ public float getScalingFactor() {
+ return this.scale;
+ }
+
+ @Override
+ public float getMaxScalingFactor() {
+ return this.maxScale;
+ }
+
+ @Override
+ public float getMinScalingFactor() {
+ return this.minScale;
+ }
+
+ @Override
+ public boolean isXAxisInverted() {
+ return this.centeringTransform.isXAxisFlipped();
+ }
+
+ @Override
+ public boolean isYAxisInverted() {
+ return this.centeringTransform.isYAxisFlipped();
+ }
+
+}
Added: trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/ZoomableView.java
===================================================================
--- trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/ZoomableView.java (rev 0)
+++ trunk/ui/ui-android/src/main/java/org/arakhne/afc/ui/android/zoom/ZoomableView.java 2013-04-11 16:28:25 UTC (rev 422)
@@ -0,0 +1,1297 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Stephane GALLAND.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * This program is free software; you can redistribute it and/or modify
+ */
+package org.arakhne.afc.ui.android.zoom;
+
+import java.util.EventListener;
+
+import org.arakhne.afc.math.continous.object2d.Circle2f;
+import org.arakhne.afc.math.continous.object2d.Ellipse2f;
+import org.arakhne.afc.math.continous.object2d.PathIterator2f;
+import org.arakhne.afc.math.continous.object2d.Point2f;
+import org.arakhne.afc.math.continous.object2d.Rectangle2f;
+import org.arakhne.afc.math.continous.object2d.RoundRectangle2f;
+import org.arakhne.afc.math.continous.object2d.Segment2f;
+import org.arakhne.afc.math.continous.object2d.Shape2f;
+import org.arakhne.afc.ui.CenteringTransform;
+import org.arakhne.afc.ui.ZoomableContext;
+import org.arakhne.afc.ui.ZoomableContextUtil;
+import org.arakhne.afc.ui.android.R;
+import org.arakhne.afc.ui.android.event.PointerEventAndroid;
+import org.arakhne.afc.ui.event.PointerEvent;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.widget.Toast;
+
+/** This abstract view provides the tools to move and scale
+ * the view. It is abstract because it does not draw anything.
+ * <p>
+ * The implementation of the ZoomableView handles the pointer and key events as following:
+ * <table>
+ * <head>
+ * <tr><th>Event</th><th>Status</th><th>Callback</th><th>Note</th></tr>
+ * </head>
+ * <tr><td>POINTER_PRESSED</td><td>supported</td><td>{@link #onPointerPressed(PointerEvent)}</td><td>Allways called</td></tr>
+ * <tr><td>POINTER_DRAGGED</td><td>supported</td><td>{@link #onPointerDragged(PointerEvent)}</td><td>Called only when the scale and move gestures are not in progress</td></tr>
+ * <tr><td>POINTER_RELEASED</td><td>supported</td><td>{@link #onPointerReleased(PointerEvent)}</td><td>Called only when the scale and move gestures are not in progress</td></tr>
+ * <tr><td>POINTER_MOVED</td><td>not supported</td><td></td><td>Pointer move on a touch screen cannot be detected?</td></tr>
+ * <tr><td>POINTER_CLICK</td><td>not supported</td><td></td><td>See {@link #setOnClickListener(OnClickListener)}</td></tr>
+ * <tr><td>POINTER_LONG_CLICK</td><td>not supported</td><td></td><td>See {@link #setOnLongClickListener(OnLongClickListener)}</td></tr>
+ * <tr><td>KEY_PRESSED</td><td>not supported</td><td></td><td>See {@link #onKeyDown(int, android.view.KeyEvent)}</td></tr>
+ * <tr><td>KEY_RELEASED</td><td>not supported</td><td></td><td>See {@link #onKeyUp(int, android.view.KeyEvent)}</td></tr>
+ * <tr><td>KEY_TYPED</td><td>not supported</td><td></td><td>See {@link #onKeyUp(int, android.view.KeyEvent)}</td></tr>
+ * <body>
+ * </body>
+ * </table>
+ * <p>
+ * The function #@link {@link #onDrawView(Canvas, float, CenteringTransform)}} may
+ * use an instance of the graphical context {@link DroidZoomableGraphics2D} to draw
+ * the elements according to the zooming attributes.
+ *
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ * @see DroidZoomableGraphics2D
+ */
+public abstract class ZoomableView extends View implements ZoomableContext {
+
+ /** Constant that is the identifier representing of an invalid pointer
+ * on the droid device.
+ */
+ public static final int INVALID_POINTER_ID = -1;
+
+ /** Current position of the workspace.
+ * This position permits to scroll the view.
+ */
+ private float focusX = 0f;
+
+ /** Current position of the workspace.
+ * This position permits to scroll the view.
+ */
+ private float focusY = 0f;
+
+ /** Scale gesture detector.
+ */
+ private final ScaleGestureDetector scaleGestureDetector;
+
+ /** Manager of the scaling events.
+ */
+ private final ScaleGestureManager scaleGestureManager;
+
+ /** Move gesture detector.
+ */
+ private final MoveGestureDetector moveGestureDetector;
+
+ /** Listener on clicks.
+ */
+ private final ClickListener clickListener;
+
+ /** Current scaling factor.
+ */
+ private float scaleFactor = 1.f;
+
+ /** Minimal scaling factor.
+ */
+ private float minScaleFactor = 0.001f;
+
+ /** Maximal scaling factor.
+ */
+ private float maxScaleFactor = 100.f;
+
+ /** Handler of events.
+ */
+ private final EventListener globalListener;
+
+ /** Zooming sensibility.
+ */
+ private float zoomingSensitivity = 1.5f;
+
+ /** Invert the scrolling direction.
+ */
+ private boolean isInvertScrollingDirection = false;
+
+ /** Indicates if the repaint requests are ignored.
+ */
+ private boolean isIgnoreRepaint = false;
+
+ /** Transformation to center the view used when rendering the view.
+ */
+ private final CenteringTransform centeringTransform = new CenteringTransform();
+
+ /** Indicates if the X axis is inverted or not.
+ */
+ private boolean isXAxisInverted = false;
+
+ /** Indicates if the Y axis is inverted or not.
+ */
+ private boolean isYAxisInverted = false;
+
+ /**
+ * @param context is the droid context of the view.
+ */
+ public ZoomableView(Context context) {
+ this(context, null, 0);
+ }
+
+ /**
+ * @param context is the droid context of the view.
+ * @param attrs are the attributes of the view.
+ */
+ public ZoomableView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * @param context is the droid context of the view.
+ * @param attrs are the attributes of the view.
+ * @param defStyle is the style of the view.
+ */
+ public ZoomableView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.scaleGestureManager = new ScaleGestureManager();
+ this.scaleGestureDetector = new ScaleGestureDetector(context, this.scaleGestureManager);
+ this.moveGestureDetector = new MoveGestureDetector();
+ this.clickListener = new ClickListener();
+ this.globalListener = createEventHandler();
+ setClickable(true);
+ setLongClickable(true);
+ super.setOnLongClickListener(this.clickListener);
+ super.setOnClickListener(this.clickListener);
+ }
+
+ @Override
+ public final boolean isXAxisInverted() {
+ return this.isXAxisInverted;
+ }
+
+ @Override
+ public final boolean isYAxisInverted() {
+ return this.isYAxisInverted;
+ }
+
+ /** Invert or not the X axis.
+ * <p>
+ * If the X axis is inverted, the positives are to the left;
+ * otherwise they are to the right (default UI standard).
+ *
+ * @param invert
+ */
+ public final void setXAxisInverted(boolean invert) {
+ if (invert!=this.isXAxisInverted) {
+ this.isXAxisInverted = invert;
+ onUpdateViewParameters();
+ repaint();
+ }
+ }
+
+ /** Invert or not the Y axis.
+ * <p>
+ * If the Y axis is inverted, the positives are to the left;
+ * otherwise they are to the right (default UI standard).
+ *
+ * @param invert
+ */
+ public final void setYAxisInverted(boolean invert) {
+ if (invert!=this.isYAxisInverted) {
+ this.isYAxisInverted = invert;
+ onUpdateViewParameters();
+ repaint();
+ }
+ }
+
+ @Override
+ public final void setOnLongClickListener(OnLongClickListener l) {
+ this.clickListener.onLongClickListener = l;
+ }
+
+ @Override
+ public final void setOnClickListener(OnClickListener l) {
+ this.clickListener.onClickListener = l;
+ }
+
+ /** Set if the repaint requests are ignored or not.
+ *
+ * @param ignore
+ */
+ public final void setIgnoreRepaint(boolean ignore) {
+ this.isIgnoreRepaint = ignore;
+ }
+
+ /** Replies if the repaint requests are ignored or not.
+ *
+ * @return <code>true</code> if the repaint requests are ignored;
+ * <code>false</code> if not.
+ */
+ public final boolean isIgnoreRepaint() {
+ return this.isIgnoreRepaint;
+ }
+
+ /** Invalidate this view wherever this function is invoked.
+ * If the current thread is the UI-thread, {@link #invalidate()}
+ * is invoked. If not, {@link #postInvalidate()} is invoked.
+ * <p>
+ * If {@link #isIgnoreRepaint()} replies <code>false</code>, this
+ * function does nothing.
+ *
+ * @see #isIgnoreRepaint()
+ */
+ public final void repaint() {
+ if (!this.isIgnoreRepaint) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ invalidate();
+ }
+ else {
+ postInvalidate();
+ }
+ }
+ }
+
+ /** Invalidate this view wherever this function is invoked.
+ * If the current thread is the UI-thread, {@link #invalidate()}
+ * is invoked. If not, {@link #postInvalidate()} is invoked.
+ * <p>
+ * If {@link #isIgnoreRepaint()} replies <code>false</code>, this
+ * function does nothing.
+ *
+ * @param x
+ * @param y
+ * @param width
+ * @param height
+ * @see #isIgnoreRepaint()
+ */
+ public final void repaint(float x, float y, float width, float height) {
+ if (!this.isIgnoreRepaint) {
+ int l = (int)logical2pixel_x(x)-5;
+ int t = (int)logical2pixel_y(y)-5;
+ int r = l + (int)logical2pixel_size(width)+10;
+ int b = t + (int)logical2pixel_size(height)+10;
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ invalidate(l, t, r, b);
+ }
+ else {
+ postInvalidate(l, t, r, b);
+ }
+ }
+ }
+
+ /** Invalidate this view wherever this function is invoked.
+ * If the current thread is the UI-thread, {@link #invalidate()}
+ * is invoked. If not, {@link #postInvalidate()} is invoked.
+ * <p>
+ * If {@link #isIgnoreRepaint()} replies <code>false</code>, this
+ * function does nothing.
+ *
+ * @param r
+ * @see #isIgnoreRepaint()
+ */
+ public final void repaint(Rectangle2f r) {
+ if (r!=null)
+ repaint(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
+ }
+
+ /** Invalidate this view wherever this function is invoked.
+ * If the current thread is the UI-thread, {@link #invalidate()}
+ * is invoked. If not, {@link #postInvalidate()} is invoked.
+ * <p>
+ * If {@link #isIgnoreRepaint()} replies <code>false</code>, this
+ * function does nothing.
+ *
+ * @param r
+ * @see #isIgnoreRepaint()
+ */
+ public final void repaint(Rect r) {
+ if (r!=null && !this.isIgnoreRepaint) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ invalidate(r.left, r.top, r.right, r.bottom);
+ }
+ else {
+ postInvalidate(r.left, r.top, r.right, r.bottom);
+ }
+ }
+ }
+
+ /** Invalidate this view wherever this function is invoked.
+ * If the current thread is the UI-thread, {@link #invalidate()}
+ * is invoked. If not, {@link #postInvalidate()} is invoked.
+ * <p>
+ * If {@link #isIgnoreRepaint()} replies <code>false</code>, this
+ * function does nothing.
+ *
+ * @param r
+ * @see #isIgnoreRepaint()
+ */
+ public final void repaint(RectF r) {
+ if (r!=null) {
+ repaint(r.left, r.top, r.right, r.bottom);
+ }
+ }
+
+ /** Invoked to create the instance of the event handler that must
+ * be used by this view.
+ *
+ * @return the event handler.
+ */
+ protected abstract EventListener createEventHandler();
+
+ /** Replies the event handler of the given type.
+ *
+ * @param type
+ * @return the event handler of the given type.
+ */
+ public final <T> T getEventHandler(Class<T> type) {
+ if (this.globalListener!=null && type.isInstance(this.globalListener))
+ return type.cast(this.globalListener);
+ throw new IllegalStateException(type.toString());
+ }
+
+ /** Replies if the direction of moving is inverted.
+ *
+ * @return <code>true</code> if the direction of moving
+ * is inverted; otherwise <code>false</code>.
+ */
+ public final boolean isMoveDirectionInverted() {
+ return this.isInvertScrollingDirection;
+ }
+
+ /** Set if the direction of moving is inverted.
+ *
+ * @param invert is <code>true</code> if the direction of moving
+ * is inverted; otherwise <code>false</code>.
+ */
+ public final void setMoveDirectionInverted(boolean invert) {
+ this.isInvertScrollingDirection = invert;
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float logical2pixel_size(float l) {
+ /*float s = l * this.scaleFactor;
+ if ((l!=0)&&(s==0f)) s = 1f;
+ return s;*/
+ return ZoomableContextUtil.logical2pixel_size(l, this.scaleFactor);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float logical2pixel_x(float l) {
+ /*float dx = l - this.focusX;
+ dx *= getScalingFactor();
+ return getViewportCenterX() + dx;*/
+ return ZoomableContextUtil.logical2pixel_x(l, this.centeringTransform, this.scaleFactor);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float logical2pixel_y(float l) {
+ /*float dy = l - this.focusY;
+ dy *= getScalingFactor();
+ return getViewportCenterY() + dy;*/
+ return ZoomableContextUtil.logical2pixel_y(l, this.centeringTransform, this.scaleFactor);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float pixel2logical_size(float l) {
+ //return l / this.scaleFactor;
+ return ZoomableContextUtil.pixel2logical_size(l, this.scaleFactor);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float pixel2logical_x(float l) {
+ /*float dx = l - getViewportCenterX();
+ dx /= getScalingFactor();
+ return this.focusX + dx;*/
+ return ZoomableContextUtil.pixel2logical_x(l, this.centeringTransform, this.scaleFactor);
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float pixel2logical_y(float l) {
+ /*float dy = l - getViewportCenterY();
+ dy /= getScalingFactor();
+ return this.focusY + dy;*/
+ return ZoomableContextUtil.pixel2logical_y(l, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public PathIterator2f logical2pixel(PathIterator2f p) {
+ return ZoomableContextUtil.logical2pixel(p, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public PathIterator2f pixel2logical(PathIterator2f p) {
+ return ZoomableContextUtil.pixel2logical(p, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(Segment2f s) {
+ ZoomableContextUtil.logical2pixel(s, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(Segment2f s) {
+ ZoomableContextUtil.pixel2logical(s, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(RoundRectangle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(RoundRectangle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(Point2f p) {
+ ZoomableContextUtil.logical2pixel(p, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(Point2f p) {
+ ZoomableContextUtil.pixel2logical(p, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(Ellipse2f e) {
+ ZoomableContextUtil.logical2pixel(e, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(Ellipse2f e) {
+ ZoomableContextUtil.pixel2logical(e, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(Circle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(Circle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void logical2pixel(Rectangle2f r) {
+ ZoomableContextUtil.logical2pixel(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public void pixel2logical(Rectangle2f r) {
+ ZoomableContextUtil.pixel2logical(r, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public Shape2f logical2pixel(Shape2f s) {
+ return ZoomableContextUtil.logical2pixel(s, this.centeringTransform, this.scaleFactor);
+ }
+
+ @Override
+ public Shape2f pixel2logical(Shape2f s) {
+ return ZoomableContextUtil.pixel2logical(s, this.centeringTransform, this.scaleFactor);
+ }
+
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getScalingSensitivity() {
+ return this.zoomingSensitivity;
+ }
+
+ /** Replies the sensivility of the {@code zoomIn()}
+ * and {@code zoomOut()} actions.
+ *
+ * @param sensivility
+ */
+ public final void setScalingSensitivity(float sensivility) {
+ this.zoomingSensitivity = Math.max(sensivility, Float.MIN_NORMAL);
+ }
+
+ /** Replies the X coordinate of the center of the viewport (in screen coordinate).
+ *
+ * @return the center of the viewport.
+ */
+ public final float getViewportCenterX() {
+ return getMeasuredWidth()/2f;
+ }
+
+ /** Replies the Y coordinate of the center of the viewport (in screen coordinate).
+ *
+ * @return the center of the viewport.
+ */
+ public final float getViewportCenterY() {
+ return getMeasuredHeight()/2f;
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getFocusX() {
+ return pixel2logical_x(getViewportCenterX());
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getFocusY() {
+ return pixel2logical_y(getViewportCenterY());
+ }
+
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getScalingFactor() {
+ return this.scaleFactor;
+ }
+
+ /** Set the scaling factor.
+ *
+ * @param factor is the scaling factor.
+ * @return <code>true</code> if the scaling factor has changed;
+ * otherwise <code>false</code>.
+ */
+ public final boolean setScalingFactor(float factor) {
+ if (setScalingFactorAndFocus(Float.NaN, Float.NaN, factor)) {
+ repaint();
+ return true;
+ }
+ return false;
+ }
+
+ /** Set the scaling factor. This function does not repaint.
+ *
+ * @param scalingX is the coordinate of the point (on the screen) where the focus occurs.
+ * @param scalingY is the coordinate of the point (on the screen) where the focus occurs.
+ * @param factor is the scaling factor.
+ * @return <code>true</code> if the scaling factor or the focus point has changed;
+ * otherwise <code>false</code>.
+ */
+ protected final boolean setScalingFactorAndFocus(float scalingX, float scalingY, float factor) {
+ // Normalize the scaling factor
+ float normalizedFactor = factor;
+ if (normalizedFactor<this.minScaleFactor)
+ normalizedFactor = this.minScaleFactor;
+ if (normalizedFactor>this.maxScaleFactor)
+ normalizedFactor = this.maxScaleFactor;
+
+ // Determine the new position of the focus.
+ // The new position of the focus depends on the current position,
+ // the new scaling factor and where the scaling occured.
+ if (!Float.isNaN(scalingX) && !Float.isNaN(scalingY)) {
+ // Get screen coordinates
+ float screenCenterX = getViewportCenterX();
+ float screenCenterY = getViewportCenterY();
+ float vectorToScreenCenterX = screenCenterX - scalingX;
+ float vectorToScreenCenterY = screenCenterY - scalingY;
+
+ // Get logical coordinates
+ float sX = pixel2logical_x(scalingX);
+ float sY = pixel2logical_y(scalingY);
+
+ float newX = sX + vectorToScreenCenterX / normalizedFactor;
+ float newY = sY + vectorToScreenCenterY / normalizedFactor;
+
+ if (normalizedFactor!=this.scaleFactor || newX!=this.focusX || newY!=this.focusY) {
+ this.scaleFactor = normalizedFactor;
+ this.focusX = newX;
+ this.focusY = newY;
+ onUpdateViewParameters();
+ return true;
+ }
+ }
+ else if (normalizedFactor!=this.scaleFactor) {
+ this.scaleFactor = normalizedFactor;
+ onUpdateViewParameters();
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Zoom the view in.
+ */
+ public final void zoomIn() {
+ if (onScale(getFocusX(), getFocusY(), getScalingSensitivity())) {
+ repaint();
+ }
+ }
+
+ /** Zoom the view out.
+ */
+ public final void zoomOut() {
+ if (onScale(getFocusX(), getFocusY(), 1f/getScalingSensitivity())) {
+ repaint();
+ }
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getMaxScalingFactor() {
+ return this.maxScaleFactor;
+ }
+
+ /** Set the maximal scaling factor allowing in the view
+ *
+ * @param factor is the maximal scaling factor.
+ */
+ public final void setMaxScalingFactor(float factor) {
+ if (factor>0f && factor!=this.maxScaleFactor) {
+ this.maxScaleFactor = factor;
+ if (this.minScaleFactor>this.maxScaleFactor)
+ this.minScaleFactor = this.maxScaleFactor;
+ if (this.scaleFactor>this.maxScaleFactor)
+ this.scaleFactor = this.maxScaleFactor;
+ repaint();
+ }
+ }
+
+ /** {@inheritDoc}
+ */
+ @Override
+ public final float getMinScalingFactor() {
+ return this.minScaleFactor;
+ }
+
+ /** Set the minimal scaling factor allowing in the view
+ *
+ * @param factor is the minimal scaling factor.
+ */
+ public final void setMinScalingFactor(float factor) {
+ if (factor>0f && factor!=this.minScaleFactor) {
+ this.minScaleFactor = factor;
+ if (this.maxScaleFactor<this.minScaleFactor)
+ this.maxScaleFactor = this.minScaleFactor;
+ if (this.scaleFactor<this.minScaleFactor)
+ this.scaleFactor = this.minScaleFactor;
+ repaint();
+ }
+ }
+
+ /** Set the position of the focus point.
+ *
+ * @param x
+ * @param y
+ */
+ public final void setFocusPoint(float x, float y) {
+ if (this.focusX!=x || this.focusY!=y) {
+ this.focusX = x;
+ this.focusY = y;
+ onUpdateViewParameters();
+ repaint();
+ }
+ }
+
+ /** Translate the position of the focus point.
+ *
+ * @param dx
+ * @param dy
+ */
+ public final void translateFocusPoint(float dx, float dy) {
+ if (dx!=0f || dy!=0f) {
+ this.focusX += dx;
+ this.focusY += dy;
+ onUpdateViewParameters();
+ repaint();
+ }
+ }
+
+ /** Update any viewing parameter according to the
+ * current value of the focus point and the scaling factor.
+ * <p>
+ * This function is invoked when the coordinates of the
+ * focus point or the scaling factor has been changed to
+ * ensure that all the drawing attributes are properly set.
+ */
+ protected void onUpdateViewParameters() {
+ /*float sf = getScalingFactor();
+ float w = getMeasuredWidth() / sf;
+ float h = getMeasuredHeight() / sf;
+ this.translateToCenterX = -(getFocusX() - w/2f);
+ this.translateToCenterY = -(getFocusY() - h/2f);*/
+ float t;
+
+ t = pixel2logical_size(getViewportCenterX());
+ if (isXAxisInverted()) {
+ this.centeringTransform.setCenteringX(
+ true,
+ -1,
+ t + this.focusX);
+ }
+ else {
+ this.centeringTransform.setCenteringX(
+ false,
+ 1,
+ t - this.focusX);
+ }
+
+ t = pixel2logical_size(getViewportCenterY());
+ if (isYAxisInverted()) {
+ this.centeringTransform.setCenteringY(
+ true,
+ -1,
+ t + this.focusY);
+ }
+ else {
+ this.centeringTransform.setCenteringY(
+ false,
+ 1,
+ t - this.focusY);
+ }
+ }
+
+ /** Replies the preferred position of the focus point.
+ *
+ * @return the preferred position of the focus point.
+ */
+ protected abstract float getPreferredFocusX();
+
+ /** Replies the preferred position of the focus point.
+ *
+ * @return the preferred position of the focus point.
+ */
+ protected abstract float getPreferredFocusY();
+
+ /** Reset the view to the default configuration.
+ *
+ * @return <code>true</code> if the view has changed; <code>false</code> otherwise.
+ */
+ public final boolean resetView() {
+ float px = getPreferredFocusX();
+ float py = getPreferredFocusY();
+ if (this.focusX!=px || this.focusY!=py || getScalingFactor()!=1f) {
+ this.focusX = px;
+ this.focusY = py;
+ if (!setScalingFactor(1f)) {
+ onUpdateViewParameters();
+ repaint(); // Force to refresh the UI
+ }
+
+ toast(getContext().getString(R.string.reset_view_done), false);
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ onUpdateViewParameters();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if(!isInEditMode()) {
+ onDrawView(canvas, this.scaleFactor, this.centeringTransform);
+ }
+ }
+
+ /** Invoked to paint the view after it is translated and scaled.
+ *
+ * @param canvas is the canvas in which the view must be painted.
+ * @param scaleFactor is the scaling factor to use for drawing.
+ * @param centeringTransform is the transform to use to put the draws at the center of the view.
+ */
+ protected abstract void onDrawView(Canvas canvas, float scaleFactor, CenteringTransform centeringTransform);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final boolean onTouchEvent(MotionEvent ev) {
+ // A disabled view that is clickable still consumes the touch
+ // events, it just doesn't respond to them.
+ if (!isEnabled()) return isClickable() || isLongClickable();
+
+ boolean consumed = false;
+ boolean underScalingGesture;
+
+ // Let the ScaleGestureDetector inspect all events.
+ underScalingGesture = this.scaleGestureDetector.onTouchEvent(ev);
+ if (underScalingGesture) {
+ consumed = true;
+ }
+
+ // Let the move gesture detector inspect all events
+ if (this.moveGestureDetector.onTouchEvent(ev,
+ this.scaleGestureDetector.isInProgress(),
+ this.scaleGestureDetector.getFocusX(),
+ this.scaleGestureDetector.getFocusY())) {
+ consumed = true;
+ }
+
+ // Let the inherited function inspect all events for click and long click events.
+ if (!this.scaleGestureDetector.isInProgress() && super.onTouchEvent(ev)) {
+ consumed = true;
+ }
+
+ return consumed;
+ }
+
+ /** Invoked when the a touch-down event is detected.
+ *
+ * @param e
+ */
+ protected void onPointerPressed(PointerEvent e) {
+ //
+ }
+
+ /** Invoked when the a touch-up event is detected.
+ *
+ * @param e
+ */
+ protected void onPointerReleased(PointerEvent e) {
+ //
+ }
+
+ /** Invoked when the pointer is moved with a button down.
+ *
+ * @param e
+ */
+ protected void onPointerDragged(PointerEvent e) {
+ //
+ }
+
+ /** Invoked when a long-click is detected.
+ *
+ * @param e
+ */
+ protected void onLongClick(PointerEvent e) {
+ //
+ }
+
+ /** Invoked when a short-click is detected.
+ *
+ * @param e
+ */
+ protected void onClick(PointerEvent e) {
+ //
+ }
+
+ /**
+ * Invoked when the view must be scaled.
+ * <p>
+ * One of the border effect if this function replies <code>true</code>
+ * is that the view will be repaint.
+ * <p>
+ * The default implementation of this function invokes
+ * {@link #setScalingFactorAndFocus(float, float, float)}.
+ *
+ * @param focusX is the position of the focal point on the screen.
+ * @param focusY is the position of the focal point on the screen.
+ * @param requestedScaleFactor is the new scale factor.
+ * @return Whether or not the detector should consider this event as handled.
+ * If an event was not handled, the detector will continue to accumulate movement
+ * until an event is handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is greater than 0.01.
+ * @see #setScalingFactorAndFocus(float, float, float)
+ */
+ public boolean onScale(float focusX, float focusY, float requestedScaleFactor) {
+ setScalingFactorAndFocus(focusX, focusY, this.scaleFactor * requestedScaleFactor);
+ return true;
+ }
+
+ /** Show a toast message.
+ *
+ * @param message is the message to display
+ * @param isLong indicates if it is a long-time (<code>true</code>)
+ * or a short-time (<code>false</code>) message.
+ */
+ public final void toast(String message, boolean isLong) {
+ Context context = getContext();
+ if (context instanceof Activity) {
+ ((Activity)context).runOnUiThread(new AsynchronousToaster(context, message, isLong));
+ }
+ }
+
+ /** Show a toast message.
+ *
+ * @param message is the message to display
+ * @param isLong indicates if it is a long-time (<code>true</code>)
+ * or a short-time (<code>false</code>) message.
+ */
+ public final void toast(int message, boolean isLong) {
+ Context context = getContext();
+ if (context instanceof Activity) {
+ ((Activity)context).runOnUiThread(new AsynchronousToaster(context, message, isLong));
+ }
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private class ScaleGestureManager extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+
+ /**
+ */
+ public ScaleGestureManager() {
+ //
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (ZoomableView.this.onScale(
+ detector.getFocusX(), detector.getFocusY(),
+ detector.getScaleFactor())) {
+ repaint();
+ }
+ return true;
+ }
+
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private class ClickListener implements OnLongClickListener, OnClickListener {
+
+ public OnLongClickListener onLongClickListener = null;
+ public OnClickListener onClickListener = null;
+ private boolean isClickEnabled = true;
+
+ /**
+ */
+ public ClickListener() {
+ //
+ }
+
+ /** Reset the internal flags of this click listener.
+ */
+ public void reset() {
+ this.isClickEnabled = true;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public boolean onLongClick(View v) {
+ boolean b = false;
+ this.isClickEnabled = false;
+ if (isLongClickable()
+ && !ZoomableView.this.scaleGestureDetector.isInProgress()) {
+ if (this.onLongClickListener!=null) {
+ b = this.onLongClickListener.onLongClick(v);
+ }
+ if (!b) {
+ PointerEventAndroid e = ZoomableView.this.moveGestureDetector.lastPointerEvent;
+ if (e!=null) {
+ e.unconsume();
+ e.setWhen(System.currentTimeMillis());
+ ZoomableView.this.onLongClick(e);
+ b = e.isConsumed();
+ }
+ }
+ }
+ return b;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void onClick(View v) {
+ if (isClickable()
+ && !ZoomableView.this.scaleGestureDetector.isInProgress()) {
+ if (this.isClickEnabled) {
+ if (this.onClickListener!=null) {
+ this.onClickListener.onClick(v);
+ }
+ PointerEventAndroid e = ZoomableView.this.moveGestureDetector.lastPointerEvent;
+ if (e!=null) {
+ e.unconsume();
+ e.setWhen(System.currentTimeMillis());
+ ZoomableView.this.onClick(e);
+ }
+ }
+ }
+ this.isClickEnabled = true;
+ }
+
+ }
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private class MoveGestureDetector {
+
+ /** Coordinate of the last touch.
+ */
+ private float lastTouchX = Float.NaN;
+
+ /** Coordinate of the last touch.
+ */
+ private float lastTouchY = Float.NaN;
+
+ /** Indicates if the move gesture is in progress.
+ */
+ private boolean inProgress = false;
+
+ /** Identifier of the current active pointer.
+ */
+ private int activePointerId = INVALID_POINTER_ID;
+
+ public PointerEventAndroid lastPointerEvent = null;
+
+ /**
+ */
+ public MoveGestureDetector() {
+ //
+ }
+
+ /** Invoked for detecting touch gestures.
+ *
+ * @param ev
+ * @param isScaleGestureInProgression indicates if the scale gesture is under progress
+ * @param scaleGestureX is the X coordinate of the scale gesture, if under progress; otherwise the value is indetermined.
+ * @param scaleGestureY is the Y coordinate of the scale gesture, if under progress; otherwise the value is indetermined.
+ * @return <code>true</code> if the event is consumed;
+ * <code>false</code> otherwise.
+ */
+ @SuppressWarnings("synthetic-access")
+ public boolean onTouchEvent(MotionEvent ev,
+ boolean isScaleGestureInProgression,
+ float scaleGestureX,
+ float scaleGestureY) {
+ int pointerIndex, pointerId;
+ float x, y;
+ int action = ev.getActionMasked();
+
+ if (isScaleGestureInProgression) {
+ x = scaleGestureX;
+ y = scaleGestureY;
+ }
+ else {
+ x = ev.getX();
+ y = ev.getY();
+ }
+
+ this.lastPointerEvent = new PointerEventAndroid(
+ ZoomableView.this,
+ x,
+ y,
+ ev);
+
+ boolean consumed = false;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ // The screen was touched. The touch-down event permits
+ // to initialize the attributes.
+ this.activePointerId = INVALID_POINTER_ID;
+ this.lastPointerEvent.unconsume();
+ if (!isScaleGestureInProgression) {
+ onPointerPressed(this.lastPointerEvent);
+ }
+ consumed = this.lastPointerEvent.isConsumed();
+ if (!consumed) {
+ this.inProgress = true;
+ this.activePointerId = ev.getPointerId(0);
+ this.lastTouchX = ev.getX(0);
+ this.lastTouchY = ev.getY(0);
+ consumed = true;
+ this.lastPointerEvent.setXY(this.lastTouchX, this.lastTouchY);
+ }
+ ZoomableView.this.clickListener.reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (this.inProgress) {
+ pointerIndex = ev.findPointerIndex(this.activePointerId);
+ x = ev.getX(pointerIndex);
+ y = ev.getY(pointerIndex);
+
+ this.lastPointerEvent.setXY(x, y);
+ float dx = pixel2logical_size(x - this.lastTouchX);
+ float dy = pixel2logical_size(y - this.lastTouchY);
+
+ this.lastTouchX = x;
+ this.lastTouchY = y;
+
+ if (isMoveDirectionInverted()) {
+ translateFocusPoint(dx, dy);
+ }
+ else {
+ translateFocusPoint(-dx, -dy);
+ }
+
+ // Always consume the event on moves
+ consumed = true;
+ }
+ else if (!isScaleGestureInProgression) {
+ this.lastPointerEvent.unconsume();
+ onPointerDragged(this.lastPointerEvent);
+ consumed = this.lastPointerEvent.isConsumed();
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ this.lastTouchX = ev.getX();
+ this.lastTouchY = ev.getY();
+ if (this.inProgress) {
+ this.inProgress = false;
+ this.activePointerId = INVALID_POINTER_ID;
+ repaint();
+ consumed = true;
+ }
+ else {
+ this.lastPointerEvent.unconsume();
+ if (!isScaleGestureInProgression) {
+ onPointerReleased(this.lastPointerEvent);
+ }
+ consumed = this.lastPointerEvent.isConsumed();
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ this.lastTouchX = ev.getX();
+ this.lastTouchY = ev.getY();
+ if (this.inProgress) {
+ this.inProgress = false;
+ this.activePointerId = INVALID_POINTER_ID;
+ repaint();
+ consumed = true;
+ }
+ else {
+ this.lastPointerEvent.unconsume();
+ if (!isScaleGestureInProgression) {
+ onPointerReleased(this.lastPointerEvent);
+ }
+ consumed = this.lastPointerEvent.isConsumed();
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (this.inProgress) {
+ pointerIndex = ((ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == this.activePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ this.lastTouchX = ev.getX(newPointerIndex);
+ this.lastTouchY = ev.getY(newPointerIndex);
+ this.activePointerId = ev.getPointerId(newPointerIndex);
+ repaint();
+ consumed = true;
+ }
+ else {
+ this.lastTouchX = ev.getX();
+ this.lastTouchY = ev.getY();
+ }
+ }
+ else {
+ this.lastTouchX = ev.getX();
+ this.lastTouchY = ev.getY();
+ }
+ break;
+
+ default:
+ // The other events are ignored.
+ }
+
+ return consumed;
+ }
+
+ } // class MoveGestureDetector
+
+ /**
+ * @author $Author: galland$
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+ private static class AsynchronousToaster implements Runnable {
+
+ private final Context context;
+ private final String message;
+ private final int messageId;
+ private final boolean isLong;
+
+ /**
+ * @param context
+ * @param message is the message to toast
+ * @param isLong indicates if it is a long-time (<code>true</code>)
+ * or a short-time (<code>false</code>) message.
+ */
+ public AsynchronousToaster(Context context, String message, boolean isLong) {
+ this.context = context;
+ this.message = message;
+ this.messageId = -1;
+ this.isLong = isLong;
+ }
+
+ /**
+ * @param context
+ * @param message is the message to toast
+ * @param isLong indicates if it is a long-time (<code>true</code>)
+ * or a short-time (<code>false</code>) message.
+ */
+ public AsynchronousToaster(Context context, int message, boolean isLong) {
+ this.context = context;
+ this.message = null;
+ this.messageId = message;
+ this.isLong = isLong;
+ }
+
+ @Override
+ public void run() {
+ if (this.message!=null && !this.message.isEmpty()) {
+ Toast.makeText(this.context, this.message,
+ this.isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
+ }
+ else if (this.messageId>=0) {
+ Toast.makeText(this.context, this.messageId,
+ this.isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+}