diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7146e39 --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 44a26b6..2d9361b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,22 +8,40 @@ # Java class files *.class +# built native files +*.o +*.so + # generated files bin/ gen/ +# Ignore gradle files +.gradle/ +build/ + # Local configuration file (sdk path, etc) local.properties -# Eclipse project files -.classpath -.project +# Proguard folder generated by Eclipse +proguard/ + +# Eclipse Metadata +.metadata/ + +# Mac OS X clutter +*.DS_Store + +# Windows clutter +Thumbs.db -# backup files of the examples.mm Mindmap -res/raw/.backup +# Intellij IDEA (see https://intellij-support.jetbrains.com/entries/23393067) +.idea/workspace.xml +.idea/tasks.xml +.idea/datasources.xml +.idea/dataSources.ids -# VIM swap files *.swp -# don't ignore the release folder -!/release/**/*.apk +# Mindmap backup files +.backup diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..f719fd3 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +droidplane \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/droidplane.iml b/.idea/droidplane.iml new file mode 100644 index 0000000..ef582b1 --- /dev/null +++ b/.idea/droidplane.iml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..fe865d3 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/.idea/libraries/acra_4_4_0.xml b/.idea/libraries/acra_4_4_0.xml new file mode 100644 index 0000000..47e73c7 --- /dev/null +++ b/.idea/libraries/acra_4_4_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/libGoogleAnalyticsV2.xml b/.idea/libraries/libGoogleAnalyticsV2.xml new file mode 100644 index 0000000..4e72c46 --- /dev/null +++ b/.idea/libraries/libGoogleAnalyticsV2.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v4_18_0_0.xml b/.idea/libraries/support_v4_18_0_0.xml new file mode 100644 index 0000000..cef7c39 --- /dev/null +++ b/.idea/libraries/support_v4_18_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e544072 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..337f2de --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..275077f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..6a111e1 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + DroidPlane + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..1e14141 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..0f2d5b5 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 16 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "ch.benediktkoeppel.code.droidplane" + minSdkVersion 8 + targetSdkVersion 17 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.android.support:support-v4:18.0.0' + compile files('libs/acra-4.4.0.jar') + compile files('libs/libGoogleAnalyticsV2.jar') +} diff --git a/app/libs/acra-4.4.0.jar b/app/libs/acra-4.4.0.jar new file mode 100644 index 0000000..1df9bfd Binary files /dev/null and b/app/libs/acra-4.4.0.jar differ diff --git a/app/libs/libGoogleAnalyticsV2.jar b/app/libs/libGoogleAnalyticsV2.jar new file mode 100644 index 0000000..46d0e44 Binary files /dev/null and b/app/libs/libGoogleAnalyticsV2.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6d14e8e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/HorizontalMindmapView.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/HorizontalMindmapView.java new file mode 100644 index 0000000..32e1e60 --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/HorizontalMindmapView.java @@ -0,0 +1,582 @@ +package ch.benediktkoeppel.code.droidplane; + +import java.util.ArrayList; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.ListView; + +public class HorizontalMindmapView extends HorizontalScrollView implements OnTouchListener, OnItemClickListener/*, OnItemLongClickListener*/ { + + /** + * HorizontalScrollView can only have one view, so we need to add a + * LinearLayout underneath it, and then stuff all NodeColumns into this + * linearLayout. + */ + private LinearLayout linearLayout; + + /** + * nodeColumns holds the list of columns that are displayed in this + * HorizontalScrollView. + */ + private ArrayList nodeColumns; + + /** + * Gesture detector + */ + private GestureDetector gestureDetector; + + private HorizontalMindmapViewGestureDetector horizontalMindmapViewGestureDetector; + + /** + * constants to determine the minimum swipe distance and speed + */ + private static final int SWIPE_THRESHOLD_VELOCITY = 300; + + /** + * Setting up a HorizontalMindmapView. We initialize the nodeColumns, define + * the layout parameters for the HorizontalScrollView and create the + * LinearLayout view inside the HorizontalScrollView. + * @param context the Application Context + */ + public HorizontalMindmapView(Context context) { + super(context); + + // list where all columns are stored + nodeColumns = new ArrayList(); + + // set the layout for the HorizontalScrollView itself + setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + // create the layout parameters for a new LinearLayout + int height = LayoutParams.MATCH_PARENT; + int width = LayoutParams.MATCH_PARENT; + ViewGroup.LayoutParams linearLayoutParams = new ViewGroup.LayoutParams(width, height); + + // create a LinearLayout in this HorizontalScrollView. All NodeColumns will go into that LinearLayout. + linearLayout = new LinearLayout(context); + linearLayout.setLayoutParams(linearLayoutParams); + this.addView(linearLayout); + + // add a new gesture controller + horizontalMindmapViewGestureDetector = new HorizontalMindmapViewGestureDetector(); + gestureDetector = new GestureDetector(getContext(), horizontalMindmapViewGestureDetector); + + // register HorizontalMindmapView to receive all touch events on itself + setOnTouchListener(this); + } + + /** + * Add a new NodeColumn to the HorizontalMindmapView + * @param nodeColumn the NodeColumn to add to the HorizontalMindmapView + */ + public void addColumn(NodeColumn nodeColumn) { + + // add the column to the layout + nodeColumns.add(nodeColumn); + linearLayout.addView(nodeColumn, linearLayout.getChildCount()); + Log.d(MainApplication.TAG, "linearLayout now has " + linearLayout.getChildCount() + " items"); + + // register as onItemClickListener and onItemLongClickListener + nodeColumn.setOnItemClickListener(this); + } + + /** + * GUI Helper to scroll the HorizontalMindmapView all the way to the right. + * Should be called after adding a NodeColumn. + * @return true if the key event is consumed by this method, false otherwise + */ + public void scrollToRight() { + + // a runnable that knows "this" + final class HorizontalMindmapViewRunnable implements Runnable { + HorizontalMindmapView horizontalMindmapView; + + public HorizontalMindmapViewRunnable(HorizontalMindmapView horizontalMindmapView) { + this.horizontalMindmapView = horizontalMindmapView; + } + + @Override + public void run() { + horizontalMindmapView.fullScroll(FOCUS_RIGHT); + } + } + + new Handler().postDelayed(new HorizontalMindmapViewRunnable(this), 100L); + } + + /** + * Removes all columns from this HorizontalMindmapView + */ + public void removeAllColumns() { + nodeColumns.clear(); + linearLayout.removeAllViews(); + } + + /** + * Adjusts the width of all columns in the HorizontalMindmapView + * @param columnWidth the width of each column + */ + public void resizeAllColumns() { + for (NodeColumn nodeColumn : nodeColumns) { + nodeColumn.resizeColumnWidth(); + } + } + + /** + * Removes the rightmost column and returns true. If there was no column to + * remove, returns false. It never removes the last column, i.e. it never + * removes the root node of the mind map. + * @return True if a column was removed, false if no column was removed. + */ + public boolean removeRightmostColumn() { + + // only remove a column if we have at least 2 columns. If there is only + // one column, it will not be removed. + if ( nodeColumns.size() >= 2 ) { + + // the column to remove + NodeColumn rightmostColumn = nodeColumns.get(nodeColumns.size()-1); + + // remove it from the linear layout + linearLayout.removeView(rightmostColumn); + + // remove it from the nodeColumns list + nodeColumns.remove(nodeColumns.size()-1); + + // then deselect all nodes on the now newly rightmost column and let the column redraw + nodeColumns.get(nodeColumns.size()-1).deselectAllNodes(); + + // a column was removed, so we return true + return true; + } + + // no column was removed, so we return false + else { + return false; + } + } + + /** + * Returns the number of columns in the HorizontalMindmapView. + * @return + */ + public int getNumberOfColumns() { + return nodeColumns.size(); + } + + /** + * Returns the title of the parent node of the rightmost column. This is the + * same as the node name of the selected node from the 2nd-rightmost column. + * So this is the last node that the user has clicked. + * If the rightmost column has no parent, an empty string is returned. + * + * @return Title of the right most parent node or an empty string. + */ + public String getTitleOfRightmostParent() { + + if ( !nodeColumns.isEmpty() ) { + + MindmapNode parent = nodeColumns.get(nodeColumns.size()-1).getParentNode(); + return parent.text; + + } + + // there were no columns + else { + Log.d(MainApplication.TAG, "getTitleOfRightmostParent returned \"\" because nodeColumns is empty"); + return ""; + } + } + + /** + * Remove all columns at the right of the specified column. + * @param nodeColumn + */ + public void removeAllColumnsRightOf(NodeColumn nodeColumn) { + + // we go from right to left, from the end of nodeColumns back to one + // element after nodeColumn + // + // nodeColumns = [ col1, col2, col3, col4, col5 ]; + // removeAllColumnsRightOf(col2) will do: + // nodeColumns.size()-1 => 4 + // nodeColumns.lastIndexOf(col2)+1 => 2 + // + // for i in (4, 3, 2): remove rightmost column + // i = 4: remove col5 + // i = 3: remove col4 + // i = 2: remove col3 + // + // so at the end, we have + // nodeColumns = [ col1, col2 ]; + for (int i = nodeColumns.size()-1; i >= nodeColumns.lastIndexOf(nodeColumn)+1; i--) { + + // remove this column + removeRightmostColumn(); + } + } + + /** + * navigates to the top of the Mindmap + */ + public void top() { + + // remove all ListView layouts in linearLayout parent_list_view + removeAllColumns(); + + // go down into the root node + down(MainApplication.getInstance().mindmap.getRootNode()); + } + + /** + * navigates back up one level in the Mindmap, if possible (otherwise does + * nothing) + */ + public void up() { + up(false); + } + + /** + * navigates back up one level in the Mindmap. If we already display the + * root node, the application will finish + */ + public void upOrClose() { + up(true); + } + + /** + * navigates back up one level in the Mindmap, if possible. If force is + * true, the application closes if we can't go further up + * + * @param force + */ + public void up(boolean force) { + + boolean wasColumnRemoved = removeRightmostColumn(); + + // close the application if no column was removed, and the force switch + // was on + if (!wasColumnRemoved && force) { + MainApplication.getMainActivityInstance().finish(); + } + + // enable the up navigation with the Home (app) button (top left corner) + enableHomeButtonIfEnoughColumns(); + + // get the title of the parent of the rightmost column (i.e. the + // selected node in the 2nd-rightmost column) + setApplicationTitle(); + + } + + /** + * open up Node node, and display all its child nodes + * + * @param node + */ + public void down(MindmapNode node) { + + // add a new column for this node and add it to the + // HorizontalMindmapView + NodeColumn nodeColumn = new NodeColumn(getContext(), node); + addColumn(nodeColumn); + + // then scroll all the way to the right + scrollToRight(); + + // enable the up navigation with the Home (app) button (top left corner) + enableHomeButtonIfEnoughColumns(); + + // get the title of the parent of the rightmost column (i.e. the + // selected node in the 2nd-rightmost column) + setApplicationTitle(); + + } + + /** + * Sets the application title to the name of the parent node of the + * rightmost column, which is the most recently clicked node. + */ + void setApplicationTitle() { + // get the title of the parent of the rightmost column (i.e. the + // selected node in the 2nd-rightmost column) + // set the application title to this nodeTitle. If the nodeTitle is + // empty, we set the default Application title + String nodeTitle = getTitleOfRightmostParent(); + Log.d(MainApplication.TAG, "nodeTitle = " + nodeTitle); + if (nodeTitle.equals("")) { + Log.d(MainApplication.TAG, "Setting application title to default string: " + getResources().getString(R.string.app_name)); + MainApplication.getMainActivityInstance().setTitle(R.string.app_name); + } else { + Log.d(MainApplication.TAG,"Setting application title to node name: " + nodeTitle); + MainApplication.getMainActivityInstance().setTitle(nodeTitle); + } + } + + /** + * Enables the Home button in the application if we have enough columns, + * i.e. if "Up" will remove a column. + */ + void enableHomeButtonIfEnoughColumns() { + // if we only have one column (i.e. this is the root node), then we + // disable the home button + int numberOfColumns = getNumberOfColumns(); + if (numberOfColumns >= 2) { + MainApplication.getMainActivityInstance().enableHomeButton(); + } else { + MainApplication.getMainActivityInstance().disableHomeButton(); + } + } + + + + /* (non-Javadoc) + * Handler when one of the ListItem's item is clicked + * Find the node which was clicked, and redraw the screen with this node as new parent + * if the clicked node has no child, then we stop here + * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long) + */ + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + + // the clicked column + // parent is the ListView in which the user clicked. Because + // NodeColumn does not extend ListView (it only wraps a ListView), we + // have to find out which NodeColumn it was. We can do so because + // NodeColumn.getNodeColumnFromListView uses a static HashMap to do the + // translation. + NodeColumn clickedNodeColumn = NodeColumn.getNodeColumnFromListView((ListView)parent); + + // remove all columns right of the column which was clicked + removeAllColumnsRightOf(clickedNodeColumn); + + // then get the clicked node + MindmapNode clickedNode = clickedNodeColumn.getNodeAtPosition(position); + + // if the clicked node has child nodes, we set it to selected and drill down + if ( clickedNode.getNumChildMindmapNodes() > 0 ) { + + // give it a special color + clickedNodeColumn.setItemColor(position); + + // and drill down + down(clickedNode); + } + + // otherwise (no children) then we just update the application title to the new parent node + else { + setApplicationTitle(); + } + } + + + /* + * (non-Javadoc) + * Will be called whenever the HorizontalScrollView is + * touched. We have to capture the move left and right events here, and snap + * to the appropriate column borders. + * + * @see android.view.View.OnTouchListener#onTouch(android.view.View, + * android.view.MotionEvent) + */ + @Override + public boolean onTouch(View view, MotionEvent event) { + + // first, we let the gestureDetector examine the event. It will process + // the event if it was a gesture, i.e. if it was fast enough to trigger + // a Fling. If it handled the event, we don't process it further. + // This gesture can be triggered if the user moves the finger fast + // enough. He does not necessarily have to move so far that the next + // column is mostly visible. + if ( gestureDetector.onTouchEvent(event) ) { + Log.d(MainApplication.TAG, "Touch event was processed by HorizontalMindmapView (gesture)"); + return true; + } + + // If it was not a gesture (i.e. the user moved his finger too slow), we + // simply snap to the next closest column border. + else if ( event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ) { + + // now we need to find out where the HorizontalMindmapView is horizontally scrolled + int scrollX = getScrollX(); + Log.d(MainApplication.TAG, "HorizontalMindmapView is scrolled horizontally to " + scrollX); + + // get the leftmost column that is still (partially) visible + NodeColumn leftmostVisibleColumn = getLeftmostVisibleColumn(); + + // get the number of visible pixels of this column + int numVisiblePixelsOnColumn = getVisiblePixelOfLeftmostColumn(); + + // if we couldn't find a column, we could not process this event. I'm not sure how this might ever happen + if ( leftmostVisibleColumn == null ) { + Log.e(MainApplication.TAG, "No leftmost visible column was detected. Not sure how this could happen!"); + return false; + } + + // and then determine if the leftmost visible column shows more than 50% of its full width + // if it shows more than 50%, then we scroll to the left, so that we can see it fully + if ( numVisiblePixelsOnColumn < leftmostVisibleColumn.getWidth()/2 ) { + Log.d(MainApplication.TAG, "Scrolling to the left, so that we can see the column fully"); + smoothScrollTo(scrollX + numVisiblePixelsOnColumn, 0); + } + + // if it shows less than 50%, then we scroll to the right, so that is not visible anymore + else { + Log.d(MainApplication.TAG, "Scrolling to the right, so that the column is not visible anymore"); + smoothScrollTo(scrollX + numVisiblePixelsOnColumn - leftmostVisibleColumn.getWidth(), 0); + } + + // we have processed this event + Log.d(MainApplication.TAG, "Touch event was processed by HorizontalMindmapView (no gesture)"); + return true; + + } + + // if we did not process the event ourself we let the caller know + else { + Log.d(MainApplication.TAG, "Touch event was not processed by HorizontalMindmapView"); + return false; + } + } + + /** + * Get the column at the left edge of the screen. + * @return NodeColumn + */ + private NodeColumn getLeftmostVisibleColumn() { + + // how much we are horizontally scrolled + int scrollX = getScrollX(); + + // how many columns fit into less than scrollX space? as soon as + // sumColumnWdiths > scrollX, we have just added the first visible + // column at the left. + int sumColumnWidths = 0; + NodeColumn leftmostVisibleColumn = null; + for (int i = 0; i < nodeColumns.size(); i++) { + sumColumnWidths += nodeColumns.get(i).getWidth(); + + // if the sum of all columns so far exceeds scrollX, the current NodeColumn is (at least a little bit) visible + if (sumColumnWidths >= scrollX) { + leftmostVisibleColumn = nodeColumns.get(i); + break; + } + } + + return leftmostVisibleColumn; + } + + /** + * Get the number of pixels that are visible on the leftmost column. + * @return + */ + private int getVisiblePixelOfLeftmostColumn() { + + // how much we are horizontally scrolled + int scrollX = getScrollX(); + + // how many columns fit into less than scrollX space? as soon as + // sumColumnWdiths > scrollX, we have just added the first visible + // column at the left. + int sumColumnWidths = 0; + int numVisiblePixelsOnColumn = 0; + for (int i = 0; i < nodeColumns.size(); i++) { + sumColumnWidths += nodeColumns.get(i).getWidth(); + + // if the sum of all columns so far exceeds scrollX, the current NodeColumn is (at least a little bit) visible + if (sumColumnWidths >= scrollX) { + // how many pixels are visible of this column? + numVisiblePixelsOnColumn = sumColumnWidths - scrollX; + break; + } + } + + return numVisiblePixelsOnColumn; + } + + /** + * The HorizontalMindmapViewGestureDetector should detect the onFling event. + * However, it never receives the onDown event, so when it gets the onFling + * the event1 is empty, and we can't detect the fling properly. + */ + class HorizontalMindmapViewGestureDetector extends SimpleOnGestureListener { + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + /* + * (non-Javadoc) onFling is called whenever a Fling (a fast swipe) event + * is detected. However, for some reason, our onDown method is never + * called, and the onFling method never gets a valid event1 (it's always + * null). So instead of relying on event1 and event2 (and determine the + * distance the finger moved), we only consider the velocity of the + * fling. This is not as accurate as it could be, but it works. + * + * @see + * android.view.GestureDetector.SimpleOnGestureListener#onFling(android + * .view.MotionEvent, android.view.MotionEvent, float, float) + */ + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { + + try { + + // how much we are horizontally scrolled + int scrollX = getScrollX(); + Log.d(MainApplication.TAG, "Velocity = " + velocityX); + + // get the leftmost column that is still (partially) visible + NodeColumn leftmostVisibleColumn = getLeftmostVisibleColumn(); + + // get the number of visible pixels of this column + int numVisiblePixelsOnColumn = getVisiblePixelOfLeftmostColumn(); + + // if we have moved at least the SWIPE_MIN_DISTANCE to the right and at faster than SWIPE_THRESHOLD_VELOCITY + if (velocityX < 0 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + + // scroll to the target column + smoothScrollTo(scrollX + numVisiblePixelsOnColumn, 0); + + Log.d(MainApplication.TAG, "processed the Fling to Right gesture"); + return true; + } + + // the same as above but from to the left + else if ( velocityX > 0 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + + // scroll to the target column + // scrolls in the wrong direction + smoothScrollTo(scrollX + numVisiblePixelsOnColumn - leftmostVisibleColumn.getWidth(), 0); + + Log.d(MainApplication.TAG, "processed the Fling to Left gesture"); + return true; + } + + // we did not process this gesture + else { + Log.d(MainApplication.TAG, "Fling was no real fling"); + return false; + } + + } catch (Exception e) { + Log.d(MainApplication.TAG, "A whole lot of stuff could have gone wrong here"); + e.printStackTrace(); + return false; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainActivity.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainActivity.java new file mode 100644 index 0000000..977cf4f --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainActivity.java @@ -0,0 +1,390 @@ +package ch.benediktkoeppel.code.droidplane; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.acra.ACRA; + +import android.annotation.SuppressLint; +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.webkit.MimeTypeMap; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.google.analytics.tracking.android.EasyTracker; + +/** + * The MainActivity can be started from the App Launcher, or with a File Open + * intent. If the MainApplication was already running, the previously used + * document is re-used. Also, most of the information about the mind map and the + * currently opened views is stored in the MainApplication. This enables the + * MainActivity to resume wherever it was before it got restarted. A restart + * can happen when the screen is rotated, and we want to continue wherever we + * were before the screen rotate. + */ +public class MainActivity extends Activity { + + MainApplication application; + + public final static String INTENT_START_HELP = "ch.benediktkoeppel.code.droidplane.INTENT_START_HELP"; + + @Override + public void onStart() { + super.onStart(); + EasyTracker.getInstance().activityStart(this); + } + + @Override + public void onStop() { + super.onStop(); + EasyTracker.getInstance().activityStop(this); + EasyTracker.getInstance().dispatch(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + application = (MainApplication)getApplication(); + MainApplication.setMainActivityInstance(this); + + // initialize android stuff + // EasyTracker + EasyTracker.getInstance().setContext(this); + EasyTracker.getTracker().sendView("MainActivity"); + + // enable the Android home button + enableHomeButton(); + + // intents (how we are called) + Intent intent = getIntent(); + String action = intent.getAction(); + String type = intent.getType(); + + // if the application was reset, or the document has changed, we need to re-initialize everything + if ( application.mindmap == null || application.mindmap.document == null + || (application.mindmap.getUri()!=intent.getData() && intent.getData()!=null) + || (intent.getBooleanExtra(INTENT_START_HELP,false)) + ) { + + // create a new Mindmap + application.mindmap = new Mindmap(); + + // create a new HorizontalMindmapView + application.horizontalMindmapView = new HorizontalMindmapView(getApplicationContext()); + + // prepare loading of the Mindmap file + InputStream mm = null; + + // determine whether we are started from the EDIT or VIEW intent, or whether we are started from the launcher + // started from ACTION_EDIT/VIEW intent + if ((Intent.ACTION_EDIT.equals(action)||Intent.ACTION_VIEW.equals(action)) && type != null) { + + Log.d(MainApplication.TAG, "started from ACTION_EDIT/VIEW intent"); + + // get the URI to the target document (the Mindmap we are opening) and open the InputStream + Uri uri = intent.getData(); + if ( uri != null ) { + ContentResolver cr = getContentResolver(); + try { + mm = cr.openInputStream(uri); + } catch (FileNotFoundException e) { + + abortWithPopup(R.string.filenotfound); + + ACRA.getErrorReporter().putCustomData("Exception", "FileNotFoundException"); + ACRA.getErrorReporter().putCustomData("Intent", "ACTION_EDIT/VIEW"); + ACRA.getErrorReporter().putCustomData("URI", uri.toString()); + e.printStackTrace(); + } + } else { + abortWithPopup(R.string.novalidfile); + } + + // store the Uri. Next time the MainActivity is started, we'll + // check whether the Uri has changed (-> load new document) or + // remained the same (-> reuse previous document) + application.mindmap.setUri(uri); + } + + // started from the launcher + else { + Log.d(MainApplication.TAG, "started from app launcher intent"); + + // display the default Mindmap "example.mm", from the resources + mm = getApplicationContext().getResources().openRawResource(R.raw.example); + } + + // load the mindmap + Log.d(MainApplication.TAG, "InputStream fetched, now starting to load document"); + application.mindmap.loadDocument(mm); + + // add the HorizontalMindmapView to the Layout Wrapper + ((LinearLayout)findViewById(R.id.layout_wrapper)).addView(application.horizontalMindmapView); + + // navigate down into the root node + application.horizontalMindmapView.down(application.mindmap.getRootNode()); + } + + // otherwise, we can display the existing HorizontalMindmapView again + else { + + // add the HorizontalMindmapView to the Layout Wrapper + LinearLayout tmp_parent = ((LinearLayout)application.horizontalMindmapView.getParent()); + if ( tmp_parent != null ) { + tmp_parent.removeView(application.horizontalMindmapView); + } + ((LinearLayout)findViewById(R.id.layout_wrapper)).addView(application.horizontalMindmapView); + + // fix the widths of all columns + application.horizontalMindmapView.resizeAllColumns(); + + // and then scroll to the right + application.horizontalMindmapView.scrollToRight(); + + // enable the up navigation with the Home (app) button (top left corner) + application.horizontalMindmapView.enableHomeButtonIfEnoughColumns(); + + // get the title of the parent of the rightmost column (i.e. the selected node in the 2nd-rightmost column) + application.horizontalMindmapView.setApplicationTitle(); + } + } + + + + + /** + * enables the home button if the Android version allows it + */ + @SuppressLint("NewApi") void enableHomeButton() { + // menu bar: if we are at least at API 11, the Home button is kind of a back button in the app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + ActionBar bar = getActionBar(); + bar.setDisplayHomeAsUpEnabled(true); + } + } + + /** + * disables the home button if the Android version allows it + */ + @SuppressLint("NewApi") void disableHomeButton() { + // menu bar: if we are at least at API 11, the Home button is kind of a back button in the app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + ActionBar bar = getActionBar(); + bar.setDisplayHomeAsUpEnabled(false); + } + } + + + + /* (non-Javadoc) + * Creates the options menu + * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) + */ + @Override + public boolean onCreateOptionsMenu(android.view.Menu menu) { + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return true; + } + + + /* (non-Javadoc) + * Handler for the back button + * Navigate one level up, and stay at the root node + * @see android.app.Activity#onBackPressed() + */ + @Override + public void onBackPressed() { + application.horizontalMindmapView.upOrClose(); + } + + /* + * (non-Javadoc) Handler of all menu events Home button: navigate one level + * up, and exit the application if the home button is pressed at the root + * node Menu Up: navigate one level up, and stay at the root node + * + * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) + */ + @Override + public boolean onOptionsItemSelected(android.view.MenuItem item) { + + switch (item.getItemId()) { + + // "Up" menu action + case R.id.up: + application.horizontalMindmapView.up(); + break; + + // "Top" menu action + case R.id.top: + application.horizontalMindmapView.top(); + break; + + // "Help" menu action + case R.id.help: + + // create a new intent (without URI) + Intent helpIntent = new Intent(this, MainActivity.class); + helpIntent.putExtra(INTENT_START_HELP, true); + startActivity(helpIntent); + + // App button (top left corner) + case android.R.id.home: + application.horizontalMindmapView.up(); + break; + } + + return true; + } + + /* + * (non-Javadoc) + * + * It looks like the onContextItemSelected has to be overwritten in a class + * extending Activity. It was not possible to have this callback in the + * NodeColumn. As a result, we have to find out here again where the event happened + * + * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) + */ + @SuppressWarnings("deprecation") + @SuppressLint("NewApi") + @Override + public boolean onContextItemSelected(MenuItem item) { + + AdapterView.AdapterContextMenuInfo contextMenuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); + + // contextMenuInfo.position is the position in the ListView where the + // context menu was loaded, i.e. the index of the item in our + // mindmapNodes list + + // MindmapNode extends LinearView, so we can cast targetView back to MindmapNode + MindmapNode mindmapNode = (MindmapNode)contextMenuInfo.targetView; + Log.d(MainApplication.TAG, "mindmapNode.text = " + mindmapNode.text); + + Log.d(MainApplication.TAG, "contextMenuInfo.position = " + contextMenuInfo.position); + Log.d(MainApplication.TAG, "item.getTitle() = " + item.getTitle()); + + switch (item.getItemId()) { + + // copy node text to clipboard + case R.id.contextcopy: + Log.d(MainApplication.TAG, "Copying text to clipboard"); + ClipboardManager clipboardManager = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + ClipData clipData = ClipData.newPlainText("node", mindmapNode.text); + clipboardManager.setPrimaryClip(clipData); + } else { + clipboardManager.setText(mindmapNode.text); + } + + break; + + // open the URI specified in the "LINK" tag + case R.id.contextopenlink: + Log.d(MainApplication.TAG, "Opening node link " + mindmapNode.link); + + // try opening the link normally with an intent + try { + Intent openUriIntent = new Intent(Intent.ACTION_VIEW); + openUriIntent.setData(mindmapNode.link); + startActivity(openUriIntent); + break; + } catch (ActivityNotFoundException e) { + Log.d(MainApplication.TAG, "ActivityNotFoundException when opening link as normal intent"); + } + + // try to prepend file:// to open it as a file link + try { + // get path of mindmap file + String fileName = "no file"; + if (mindmapNode.link.getPath().startsWith("/")) { + // absolute filename + fileName = mindmapNode.link.getPath(); + } else { + + // link is relative to mindmap file + String mindmapPath = application.mindmap.getUri().getPath(); + Log.d(MainApplication.TAG, "Mindmap path " + mindmapPath); + String mindmapDirectoryPath = mindmapPath.substring(0, mindmapPath.lastIndexOf("/")); + Log.d(MainApplication.TAG, "Mindmap directory path " + mindmapDirectoryPath); + fileName = mindmapDirectoryPath + "/" + mindmapNode.link.getPath(); + + } + File file = new File(fileName); + if (!file.exists()) { + Toast.makeText(this, "File " + fileName + " does not exist.", Toast.LENGTH_SHORT).show(); + Log.d(MainApplication.TAG, "File " + fileName + " does not exist."); + break; + } + if (!file.canRead()) { + Toast.makeText(this, "Can not read file " + fileName + ".", Toast.LENGTH_SHORT).show(); + Log.d(MainApplication.TAG, "Can not read file " + fileName + "."); + break; + } + Log.d(MainApplication.TAG, "Opening file " + Uri.fromFile(file)); + // http://stackoverflow.com/a/3571239/1067124 + String extension = ""; + int i = fileName.lastIndexOf('.'); + int p = fileName.lastIndexOf('/'); + if (i > p) { + extension = fileName.substring(i+1); + } + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(file), mime); + startActivity(intent); + } catch (Exception e1) { + Toast.makeText(this, "No application found to open " + mindmapNode.link, Toast.LENGTH_SHORT).show(); + e1.printStackTrace(); + } + + break; + + default: + break; + } + + return true; + } + + /** + * Shows a popup with an error message and then closes the application + * @param stringResourceId + */ + public void abortWithPopup(int stringResourceId) { + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(stringResourceId); + builder.setCancelable(true); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + + AlertDialog alert = builder.create(); + alert.show(); + } +} diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainApplication.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainApplication.java new file mode 100644 index 0000000..36b29fe --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MainApplication.java @@ -0,0 +1,91 @@ +package ch.benediktkoeppel.code.droidplane; + +import org.acra.ACRA; +import org.acra.annotation.ReportsCrashes; + +import android.app.Application; +import android.content.Context; + +/** + * The DroidPlane main application. It stores the loaded Uri and document, so + * that we can recreate the MainActivity after a screen rotation. + */ +@ReportsCrashes(formKey = "dE1VQVpQN2FNTWlLQXg1UUQ1b1VSN3c6MQ") +public class MainApplication extends Application { + + /** + * Android Logging TAG + */ + public static final String TAG = "DroidPlane"; + + /** + * HorizontalMindmapView that contains all NodeColumns + */ + public HorizontalMindmapView horizontalMindmapView; + + /** + * the application context + */ + private static Context context; + + /** + * the main application instance + */ + private static MainApplication instance; + + /** + * the main activity instance + */ + private static MainActivity mainActivityInstance; + + /** + * A reference to the Mindmap document + */ + public Mindmap mindmap; + + + + @Override + public void onCreate() { + super.onCreate(); + + // initialize ACRA crash reports + ACRA.init(this); + + // save the context + MainApplication.context = getApplicationContext(); + + // save the instance + MainApplication.instance = this; + } + + /** + * Helper to return the application context, even for static methods. + * @return + */ + public static Context getStaticApplicationContext() { + return MainApplication.context; + } + + /** + * Get the main application instance + * @return the main application + */ + public static MainApplication getInstance() { + return MainApplication.instance; + } + + /** + * Stores the MainActivity instance + * @param mainActivityInstance + */ + public static void setMainActivityInstance(MainActivity mainActivityInstance) { + MainApplication.mainActivityInstance = mainActivityInstance; + } + + public static MainActivity getMainActivityInstance() { + return MainApplication.mainActivityInstance; + } + + +} diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/Mindmap.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/Mindmap.java new file mode 100644 index 0000000..4a76957 --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/Mindmap.java @@ -0,0 +1,104 @@ +package ch.benediktkoeppel.code.droidplane; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.acra.ACRA; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import com.google.analytics.tracking.android.EasyTracker; + +import android.net.Uri; +import android.util.Log; + +/** + * Mindmap handles the loading and storing of a mind map document. + */ +public class Mindmap { + + /** + * the XML DOM document, the mind map + */ + public Document document; + + /** + * The currently loaded Uri + */ + private Uri uri; + + /** + * The root node of the document. + */ + private MindmapNode rootNode; + + /** + * Returns the Uri which is currently loaded in document. + * @return Uri + */ + public Uri getUri() { + return this.uri; + } + + /** + * Set the Uri after loading a new document. + * @param uri + */ + public void setUri(Uri uri) { + this.uri = uri; + } + + /** + * Loads a mind map (*.mm) XML document into its internal DOM tree + * @param inputStream the inputStream to load + */ + public void loadDocument(InputStream inputStream) { + + // start measuring the document load time + long loadDocumentStartTime = System.currentTimeMillis(); + + // XML document builder + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + + // load the Mindmap from the InputStream + try { + docBuilder = docBuilderFactory.newDocumentBuilder(); + document = docBuilder.parse(inputStream); + } catch (ParserConfigurationException e) { + ACRA.getErrorReporter().putCustomData("Exception", "ParserConfigurationException"); + e.printStackTrace(); + return; + } catch (SAXException e) { + ACRA.getErrorReporter().putCustomData("Exception", "SAXException"); + e.printStackTrace(); + return; + } catch (IOException e) { + ACRA.getErrorReporter().putCustomData("Exception", "IOException"); + e.printStackTrace(); + return; + } + + long loadDocumentEndTime = System.currentTimeMillis(); + EasyTracker.getTracker().sendTiming("document", loadDocumentEndTime-loadDocumentStartTime, "loadDocument", "loadTime"); + Log.d(MainApplication.TAG, "Document loaded"); + + long numNodes = document.getElementsByTagName("node").getLength(); + EasyTracker.getTracker().sendEvent("document", "loadDocument", "numNodes", numNodes); + + rootNode = new MindmapNode(MainApplication.getStaticApplicationContext(), document.getDocumentElement().getElementsByTagName("node").item(0)); + } + + /** + * Returns the root node of the currently loaded mind map + * @return the root node + */ + public MindmapNode getRootNode() { + return rootNode; + } + +} diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/MindmapNode.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MindmapNode.java new file mode 100644 index 0000000..3d31ada --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/MindmapNode.java @@ -0,0 +1,363 @@ +package ch.benediktkoeppel.code.droidplane; + +import java.util.ArrayList; +import java.util.Locale; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Gravity; +import android.view.View; +import android.widget.AbsListView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.util.Log; + + + +/** + * A MindMapNode is a special type of DOM Node. A DOM Node can be converted to a + * MindMapNode if it has type ELEMENT, and tag "node". + */ +public class MindmapNode extends LinearLayout { + + /** + * the Text of the node (TEXT attribute). + */ + public String text; + + /** + * the name of the icon + */ + public String icon_name; + + /** + * the Android resource ID of the icon + */ + public int icon_res_id; + + /** + * whether the node is expandable, i.e. whether it has child nodes + */ + public boolean isExpandable; + + /** + * if the node has a LINK attribute, it will be stored in Uri link + */ + public Uri link; + + /** + * the XML DOM node from which this MindMapNode is derived + */ + private Node node; + + /** + * whether the node is selected or not, will be set after it was clicked by the user + */ + public boolean selected; + + /** + * The list of child MindmapNodes. We support lazy loading. + */ + ArrayList childMindmapNodes; + + /** + * True, when this MindmapNode has an inflated layout. Otherwise false. + */ + private Boolean isLayoutInflated = false; + + /** + * This constructor is only used to make graphical GUI layout tools happy. If used in running code, it will always throw a IllegalArgumentException. + * @deprecated + * @param context + */ + public MindmapNode(Context context) { + super(context); + if ( !isInEditMode() ) { + throw new IllegalArgumentException( + "The constructor public MindmapNode(Context context) may only be called by graphical layout tools, i.e. when View#isInEditMode() is true. In production, use the constructor public MindmapNode(Context context, Node node)."); + } + } + + /** + * Creates a new MindMapNode from Node. The node needs to be of type ELEMENT and have tag "node". + * Throws a {@link ClassCastException} if the Node can not be converted to a MindmapNode. + * @param node + */ + public MindmapNode(Context context, Node node) { + + super(context); + + // convert the XML Node to a XML Element + Element tmp_element; + if ( isMindmapNode(node) ) { + tmp_element = (Element)node; + } else { + throw new ClassCastException("Can not convert Node to MindmapNode"); + } + + // store the Node + this.node = node; + + // extract the string (TEXT attribute) of the nodes + text = tmp_element.getAttribute("TEXT"); + + String linkAttribute = tmp_element.getAttribute("LINK"); + if ( !linkAttribute.equals("") ) { + link = Uri.parse(linkAttribute); + } + + // extract icons + ArrayList icons = getIcons(); + String icon=""; + icon_res_id = 0; + if ( icons.size() > 0 ) { + icon = icons.get(0); + icon_res_id = MainApplication.getStaticApplicationContext().getResources().getIdentifier("@drawable/" + icon, "id", MainApplication.getStaticApplicationContext().getPackageName()); + } + + // find out if it has sub nodes + isExpandable = ( getNumChildMindmapNodes() > 0 ); + + // load the layout from the XML file + // TODO: we should not inflate the layout straight away, but only when the MindmapNode is displaye (lazy loading) +// MindmapNode.inflate(context, R.layout.mindmap_node_list_item, this); +// refreshView(); + + } + + /** + * Inflates the layout from the XML file if it is not yet inflated + * @param context + */ + private void inflateLayout(Context context) { + synchronized (isLayoutInflated ) { + if (!isLayoutInflated) { + MindmapNode.inflate(context, R.layout.mindmap_node_list_item, this); + } + } + } + + @SuppressLint("InlinedApi") + public void refreshView() { + + // inflate the layout if we haven't done so yet + inflateLayout(getContext()); + + // the mindmap_node_list_item consists of a ImageView (icon), a TextView (node text), and another TextView ("+" button) + ImageView iconView = (ImageView)findViewById(R.id.icon); + iconView.setImageResource(icon_res_id); + iconView.setContentDescription(icon_name); + + TextView textView = (TextView) findViewById(R.id.label); + textView.setTextColor(getContext().getResources().getColor(android.R.color.primary_text_light)); + textView.setText(text); + + ImageView expandable = (ImageView) findViewById(R.id.expandable); + if ( isExpandable ) { + if ( getIsSelected() ) { + expandable.setImageResource(R.drawable.minus_alt); + } else { + expandable.setImageResource(R.drawable.plus_alt); + } + } + + // if the node is selected and has child nodes, give it a special background + if ( getIsSelected() && getNumChildMindmapNodes() > 0 ) { + int backgroundColor; + + // menu bar: if we are at least at API 11, the Home button is kind of a back button in the app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + backgroundColor = getContext().getResources().getColor(android.R.color.holo_blue_bright); + } else { + backgroundColor = getContext().getResources().getColor(android.R.color.darker_gray); + } + + setBackgroundColor(backgroundColor); + } else { + setBackgroundColor(0); + } + + // set the layout parameter + // TODO: this should not be necessary. The problem is that the inflate + // (in the constructor) loads the XML as child of this LinearView, so + // the MindmapNode-LinearView wraps the root LinearView from the + // mindmap_node_list_item XML file. + setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT)); + setGravity(Gravity.LEFT | Gravity.CENTER); + } + + /** + * Selects or deselects this node + * @param selected + */ + public void setSelected(boolean selected) { + this.selected = selected; + } + + /** + * Returns whether this node is selected + */ + public boolean getIsSelected() { + return this.selected; + } + + /** + * Checks whether a given node can be converted to a Mindmap node, i.e. + * whether it has type ELEMENT_NODE and tag "node" + * @param node + * @return + */ + public static boolean isMindmapNode(Node node) { + + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + + if (element.getTagName().equals("node")) { + return true; + } + } + return false; + } + + + /** + * Extracts the list of icons from a node and returns the names of the icons + * as ArrayList. + * + * @return list of names of the icons + */ + private ArrayList getIcons() { + + ArrayList icons = new ArrayList(); + + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + + Node n = childNodes.item(i); + if ( n.getNodeType() == Node.ELEMENT_NODE ) { + Element e = (Element)n; + + if ( e.getTagName().equals("icon") && e.hasAttribute("BUILTIN") ) { + icons.add(getDrawableNameFromMindmapIcon(e.getAttribute("BUILTIN"))); + } + } + } + + return icons; + } + + /** + * Mindmap icons have names such as 'button-ok', but resources have to have + * names with pattern [a-z0-9_.]. This method translates the Mindmap icon + * names to Android resource names. + * + * @param iconName the icon name as it is specified in the XML + * @return the name of the corresponding android resource icon + */ + private String getDrawableNameFromMindmapIcon(String iconName) { + Locale locale = MainApplication.getStaticApplicationContext().getResources().getConfiguration().locale; + String name = "icon_" + iconName.toLowerCase(locale).replaceAll("[^a-z0-9_.]", "_"); + name.replaceAll("_$", ""); + return name; + } + + /** + * returns the number of child Mindmap nodes + * @return + */ + public int getNumChildMindmapNodes() { + + int numMindmapNodes = 0; + + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + + Node n = childNodes.item(i); + if ( isMindmapNode(n) ) { + numMindmapNodes++; + } + } + + return numMindmapNodes; + } + + /** + * The NodeColumn forwards the CreateContextMenu event to the appropriate + * MindmapNode, which can then generate the context menu as it likes. Note + * that the MindmapNode itself is not registered as the listener for such + * events per se, because the NodeColumn first has to decide for which + * MindmapNode the event applies. + * + * @param menu + * @param v + * @param menuInfo + */ + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + + // build the menu + menu.setHeaderTitle(text); + menu.setHeaderIcon(icon_res_id); + + // TODO: add a submenu showing all icons + // Context menus do not support icons. +// SubMenu iconMenu = menu.addSubMenu("All icons"); +// ArrayList icons = getIcons(); +// for (int i = 0; i < icons.size(); i++) { +// String icon = icons.get(i); +// MenuItem iconMenuItem = iconMenu.add(icon); +// int iconMenuItemResId = MainApplication.getStaticApplicationContext().getResources().getIdentifier("@drawable/" + icon, "id", MainApplication.getStaticApplicationContext().getPackageName()); +// iconMenuItem.setIcon(MainApplication.getStaticApplicationContext().getResources().getDrawable(iconMenuItemResId)); +// } + + // allow copying the node text + menu.add(0, R.id.contextcopy, 0, R.string.copynodetext); + + // add menu to open link, if the node has a hyperlink + if ( link != null ) { + menu.add(0, R.id.contextopenlink, 0, R.string.openlink); + } + } + + + /* + * Generates and returns the child nodes of this MindmapNode. + * getChildNodes() does lazy loading, i.e. it generates the child nodes on + * demand and stores them in childMindmapNodes. + * @return ArrayList of this MindmapNode's child nodes + */ + public ArrayList getChildNodes() { + + // if we haven't loaded the childMindmapNodes before + if ( childMindmapNodes == null ) { + + // fetch all child DOM Nodes, convert them to MindmapNodes + childMindmapNodes = new ArrayList(); + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node tmpNode = childNodes.item(i); + + if ( isMindmapNode(tmpNode) ) { + MindmapNode mindmapNode = new MindmapNode(getContext(), tmpNode); + childMindmapNodes.add(mindmapNode); + } + } + Log.d(MainApplication.TAG, "Returning newly generated childMindmapNodes"); + return childMindmapNodes; + } + + // we already did that before, so return the previous result + else { + Log.d(MainApplication.TAG, "Returning cached childMindmapNodes"); + return childMindmapNodes; + } + } +} diff --git a/app/src/main/java/ch/benediktkoeppel/code/droidplane/NodeColumn.java b/app/src/main/java/ch/benediktkoeppel/code/droidplane/NodeColumn.java new file mode 100644 index 0000000..4e77f1c --- /dev/null +++ b/app/src/main/java/ch/benediktkoeppel/code/droidplane/NodeColumn.java @@ -0,0 +1,339 @@ +package ch.benediktkoeppel.code.droidplane; + +import java.util.ArrayList; +import java.util.HashMap; + +//import org.w3c.dom.Node; +//import org.w3c.dom.NodeList; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Point; +import android.os.Build; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; +import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; + +/** + * A column of MindmapNodes, i.e. one level in the mind map. It extends + * LinearLayout, and then embeds a ListView. This is because we want to have a + * fine border around the ListView and we can only achieve this by having it + * wrapped in a LinearLayout with a padding. + */ +public class NodeColumn extends LinearLayout implements OnCreateContextMenuListener { + + /** + * This translates ListViews to NodeColumns. We need this because the + * OnItemClicked Events come with a ListView (i.e. the ListView which was + * clicked) as parent, but we need to find out which NodeColumn was clicked. + * This would have been a simple cast if NodeColumn extended ListView, but + * we extend LinearLayout and wrap the ListView. + */ + private static HashMap listViewToNodeColumn = new HashMap(); + + /** + * the parent node (i.e. the node that is parent to everything we display in this column) + */ + private MindmapNode parent; + + /** + * the list of all MindmapNodes which we display in this column + */ + private ArrayList mindmapNodes; + + /** + * the adapter for this column + */ + MindmapNodeAdapter adapter; + + /** + * the actual ListView that we'll display + */ + ListView listView; + + /** + * This constructor is only used to make graphical GUI layout tools happy. If used in running code, it will always throw a IllegalArgumentException. + * @deprecated + * @param context + */ + public NodeColumn(Context context) { + super(context); + if ( !isInEditMode() ) { + throw new IllegalArgumentException( + "The constructor public NodeColumn(Context context) may only be called by graphical layout tools, i.e. when View#isInEditMode() is true. In production, use the constructor public NodeColumn(Context context, Node parent)."); + } + } + + /** + * Creates a new NodeColumn for a parent node. This NodeColumn is a + * LinearLayout, which contains a ListView, which displays all child nodes + * of the parent node. + * + * @param context + * @param parent + */ + public NodeColumn(Context context, MindmapNode parent) { + super(context); + + // extract all elements from the parent node, and add them to the mindmapNodes list + mindmapNodes = parent.getChildNodes(); + + // store the parent node + this.parent = parent; + + + // define the layout of this LinearView + int linearViewHeight = LayoutParams.MATCH_PARENT; + int linearViewWidth = getOptimalColumnWidth(); + LinearLayout.LayoutParams linearViewLayout = new LinearLayout.LayoutParams(linearViewWidth, linearViewHeight); + setLayoutParams(linearViewLayout); + setPadding(0, 0, 1, 0); + setBackgroundColor(context.getResources().getColor(android.R.color.darker_gray)); + + + + // create a ListView + listView = new ListView(context); + + // define the layout of the listView + // should be as high as the parent (i.e. full screen height) + int listViewHeight = LayoutParams.MATCH_PARENT; + int listViewWidth = LayoutParams.MATCH_PARENT; + ViewGroup.LayoutParams listViewLayout = new ViewGroup.LayoutParams(listViewWidth, listViewHeight); + listView.setLayoutParams(listViewLayout); + listView.setBackgroundColor(context.getResources().getColor(android.R.color.background_light)); + + + // create adapter (i.e. data provider) for the column + adapter = new MindmapNodeAdapter(getContext(), R.layout.mindmap_node_list_item, mindmapNodes); + + // add the content adapter + listView.setAdapter(adapter); + + // store the ListView to NodeColumn mapping in listViewToNodeColumn + listViewToNodeColumn.put(listView, this); + + // call NodeColumn's onCreateContextMenu when a context menu for one of + // the listView items should be generated. + listView.setOnCreateContextMenuListener(this); + + // add the listView to the linearView + this.addView(listView); + } + + /** + * Finds the NodeColumn which contains the given listView. + * @param listView + * @return the node column which contains listView + */ + public static NodeColumn getNodeColumnFromListView(ListView listView) { + return listViewToNodeColumn.get(listView); + } + + /** + * GUI Helper to detach this LinearView from its parent + */ + public void removeFromParent() { + if ( this.getParent() != null ) { + ((ViewGroup)this.getParent()).removeView(this); + } + } + + /** + * Sets the width of this column to columnWidth + * @param columnWidth width of the column + */ + private void setWidth(int columnWidth) { + Log.d(MainApplication.TAG, "Setting column width to " + columnWidth); + + ViewGroup.LayoutParams listViewParam = this.getLayoutParams(); + listViewParam.width = columnWidth; + this.setLayoutParams(listViewParam); + } + + /** + * Deselects all nodes of this column + */ + public void deselectAllNodes() { + // deselect all nodes + for (MindmapNode mindmapNode : mindmapNodes) { + mindmapNode.setSelected(false); + } + + // then notify about the GUI change + adapter.notifyDataSetChanged(); + } + + /** + * Returns the parent node of this column. + * @return the parent node of this colunn + */ + public MindmapNode getParentNode() { + return parent; + } + + /** + * Calculates the column width which this column should have + * @return + */ + @SuppressWarnings("deprecation") + @SuppressLint("NewApi") + private static int getOptimalColumnWidth() { + + // and R.integer.horizontally_visible_panes defines how many columns should be visible side by side + // so we need 1/(horizontall_visible_panes) * displayWidth as column width + int horizontallyVisiblePanes = MainApplication.getStaticApplicationContext().getResources().getInteger(R.integer.horizontally_visible_panes); + android.view.Display display = ((android.view.WindowManager)MainApplication.getStaticApplicationContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + int displayWidth; + + // get the Display width. Before HONEYCOMB_MR2, this was display.getWidth, now it is display.getSize + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + Point displaySize = new Point(); + display.getSize(displaySize); + displayWidth = displaySize.x; + } else { + displayWidth = (int)display.getWidth(); + } + int columnWidth = displayWidth / horizontallyVisiblePanes; + + Log.d(MainApplication.TAG, "Calculated column width = " + columnWidth); + + return columnWidth; + } + + /** + * Resizes the column to its optimal column width + */ + public void resizeColumnWidth() { + setWidth(getOptimalColumnWidth()); + } + + /** + * Fetches the MindmapNode at the given position + * @param position the position from which the MindmapNode should be returned + * @return MindmapNode + */ + public MindmapNode getNodeAtPosition(int position) { + return mindmapNodes.get(position); + } + + /** + * Sets the color on the node at the specified position + * @param position + */ + public void setItemColor(int position) { + + // deselect all nodes + for (int i = 0; i < mindmapNodes.size(); i++) { + mindmapNodes.get(i).setSelected(false); + } + + // then select node at position + mindmapNodes.get(position).setSelected(true); + + // then notify about the GUI change + adapter.notifyDataSetChanged(); + } + + /** + * Clears the item color on all nodes + */ + public void clearAllItemColor() { + for (int i = 0; i < mindmapNodes.size(); i++) { + mindmapNodes.get(i).setSelected(false); + } + + // then notify about the GUI change + adapter.notifyDataSetChanged(); + } + + /** + * Simply a wrapper for ListView's setOnItemClickListener. Technically, the + * NodeColumn (which is a LinearView) does not generate OnItemClick Events, + * but it's child view (the ListView) does. But it's simpler if the outside + * world does not have to care about that detail, so we implement + * setOnItemClickListener and just forward the listener to the actual + * ListView. + * @param listener + */ + public void setOnItemClickListener(OnItemClickListener listener) { + listView.setOnItemClickListener(listener); + } + + + /* (non-Javadoc) + * This is called when a context menu for one of the list items is generated. + * @see android.view.View.OnCreateContextMenuListener#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo) + */ + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + + // get the menu information + AdapterView.AdapterContextMenuInfo contextMenuInfo = (AdapterView.AdapterContextMenuInfo)menuInfo; + + // get the clicked node + MindmapNode clickedNode = mindmapNodes.get(contextMenuInfo.position); + + // forward the event to the clicked node + clickedNode.onCreateContextMenu(menu, v, menuInfo); + + } +} + + + +/** + * The MindmapNodeAdapter is the data provider for the NodeColumn (respectively + * its ListView). + */ +class MindmapNodeAdapter extends ArrayAdapter { + + private ArrayList mindmapNodes; + + public MindmapNodeAdapter(Context context, int textViewResourceId, ArrayList mindmapNodes) { + super(context, textViewResourceId, mindmapNodes); + this.mindmapNodes = mindmapNodes; + } + + /* (non-Javadoc) + * getView is responsible to return a view for each individual element in the ListView + * @param int position: the position in the mindmapNodes array, for which we need to generate a view + * @param View convertView: the view we should recycle + * @param ViewGroup parent: not sure, is this the NodeColumn for which the Adapter is generating views? + * @see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup) + */ + @SuppressLint("InlinedApi") + @Override + public View getView(int position, View convertView, ViewGroup parent) { + +// if ( convertView != null ) { +// if ( convertView instanceof MindmapNode ) { +// ((MindmapNode)convertView).discardView(); +// } +// } + + // when convertView != null, we should take the convertView and update + // it appropriately. Android is optimizing the performance + // and thus recycling GUI elements. However, we don't want to recycle + // anything, because these are genuine Mindmap nodes. Recycling + // the view here would show one node twice in the tree, while leaving + // out the actual node we should display. + + MindmapNode view = mindmapNodes.get(position); + + // tell the node to refresh it's view + view.refreshView(); + + return view; + } + + +} diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..124720e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..3c95942 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e23f325 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/minus_alt.png b/app/src/main/res/drawable-xhdpi/minus_alt.png new file mode 100644 index 0000000..a84aea9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/minus_alt.png differ diff --git a/app/src/main/res/drawable-xhdpi/plus_alt.png b/app/src/main/res/drawable-xhdpi/plus_alt.png new file mode 100644 index 0000000..2646ffd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/plus_alt.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d2c3f34 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable/icon_0.png b/app/src/main/res/drawable/icon_0.png new file mode 100644 index 0000000..797c04d Binary files /dev/null and b/app/src/main/res/drawable/icon_0.png differ diff --git a/app/src/main/res/drawable/icon_100.png b/app/src/main/res/drawable/icon_100.png new file mode 100644 index 0000000..20c2a13 Binary files /dev/null and b/app/src/main/res/drawable/icon_100.png differ diff --git a/app/src/main/res/drawable/icon_25.png b/app/src/main/res/drawable/icon_25.png new file mode 100644 index 0000000..9e81757 Binary files /dev/null and b/app/src/main/res/drawable/icon_25.png differ diff --git a/app/src/main/res/drawable/icon_50.png b/app/src/main/res/drawable/icon_50.png new file mode 100644 index 0000000..9dd37d5 Binary files /dev/null and b/app/src/main/res/drawable/icon_50.png differ diff --git a/app/src/main/res/drawable/icon_75.png b/app/src/main/res/drawable/icon_75.png new file mode 100644 index 0000000..ee6e6f3 Binary files /dev/null and b/app/src/main/res/drawable/icon_75.png differ diff --git a/app/src/main/res/drawable/icon_addition.png b/app/src/main/res/drawable/icon_addition.png new file mode 100644 index 0000000..931bd85 Binary files /dev/null and b/app/src/main/res/drawable/icon_addition.png differ diff --git a/app/src/main/res/drawable/icon_attach.png b/app/src/main/res/drawable/icon_attach.png new file mode 100644 index 0000000..2e4657e Binary files /dev/null and b/app/src/main/res/drawable/icon_attach.png differ diff --git a/app/src/main/res/drawable/icon_audio.png b/app/src/main/res/drawable/icon_audio.png new file mode 100644 index 0000000..d83124b Binary files /dev/null and b/app/src/main/res/drawable/icon_audio.png differ diff --git a/app/src/main/res/drawable/icon_back.png b/app/src/main/res/drawable/icon_back.png new file mode 100644 index 0000000..27930f9 Binary files /dev/null and b/app/src/main/res/drawable/icon_back.png differ diff --git a/app/src/main/res/drawable/icon_bee.png b/app/src/main/res/drawable/icon_bee.png new file mode 100644 index 0000000..a5ef8d7 Binary files /dev/null and b/app/src/main/res/drawable/icon_bee.png differ diff --git a/app/src/main/res/drawable/icon_bell.png b/app/src/main/res/drawable/icon_bell.png new file mode 100644 index 0000000..354f6d5 Binary files /dev/null and b/app/src/main/res/drawable/icon_bell.png differ diff --git a/app/src/main/res/drawable/icon_bookmark.png b/app/src/main/res/drawable/icon_bookmark.png new file mode 100644 index 0000000..2b61e4a Binary files /dev/null and b/app/src/main/res/drawable/icon_bookmark.png differ diff --git a/app/src/main/res/drawable/icon_broken_line.png b/app/src/main/res/drawable/icon_broken_line.png new file mode 100644 index 0000000..cc55444 Binary files /dev/null and b/app/src/main/res/drawable/icon_broken_line.png differ diff --git a/app/src/main/res/drawable/icon_button.png b/app/src/main/res/drawable/icon_button.png new file mode 100644 index 0000000..70e4314 Binary files /dev/null and b/app/src/main/res/drawable/icon_button.png differ diff --git a/app/src/main/res/drawable/icon_button_cancel.png b/app/src/main/res/drawable/icon_button_cancel.png new file mode 100644 index 0000000..0711270 Binary files /dev/null and b/app/src/main/res/drawable/icon_button_cancel.png differ diff --git a/app/src/main/res/drawable/icon_button_ok.png b/app/src/main/res/drawable/icon_button_ok.png new file mode 100644 index 0000000..dd1c408 Binary files /dev/null and b/app/src/main/res/drawable/icon_button_ok.png differ diff --git a/app/src/main/res/drawable/icon_calendar.png b/app/src/main/res/drawable/icon_calendar.png new file mode 100644 index 0000000..c7fa6be Binary files /dev/null and b/app/src/main/res/drawable/icon_calendar.png differ diff --git a/app/src/main/res/drawable/icon_checked.png b/app/src/main/res/drawable/icon_checked.png new file mode 100644 index 0000000..a75a4b5 Binary files /dev/null and b/app/src/main/res/drawable/icon_checked.png differ diff --git a/app/src/main/res/drawable/icon_clanbomber.png b/app/src/main/res/drawable/icon_clanbomber.png new file mode 100644 index 0000000..270f771 Binary files /dev/null and b/app/src/main/res/drawable/icon_clanbomber.png differ diff --git a/app/src/main/res/drawable/icon_clock.png b/app/src/main/res/drawable/icon_clock.png new file mode 100644 index 0000000..380c419 Binary files /dev/null and b/app/src/main/res/drawable/icon_clock.png differ diff --git a/app/src/main/res/drawable/icon_clock2.png b/app/src/main/res/drawable/icon_clock2.png new file mode 100644 index 0000000..2bfcf44 Binary files /dev/null and b/app/src/main/res/drawable/icon_clock2.png differ diff --git a/app/src/main/res/drawable/icon_closed.png b/app/src/main/res/drawable/icon_closed.png new file mode 100644 index 0000000..7f0fa48 Binary files /dev/null and b/app/src/main/res/drawable/icon_closed.png differ diff --git a/app/src/main/res/drawable/icon_decrypted.png b/app/src/main/res/drawable/icon_decrypted.png new file mode 100644 index 0000000..90b0193 Binary files /dev/null and b/app/src/main/res/drawable/icon_decrypted.png differ diff --git a/app/src/main/res/drawable/icon_desktop_new.png b/app/src/main/res/drawable/icon_desktop_new.png new file mode 100644 index 0000000..0337ddd Binary files /dev/null and b/app/src/main/res/drawable/icon_desktop_new.png differ diff --git a/app/src/main/res/drawable/icon_division.png b/app/src/main/res/drawable/icon_division.png new file mode 100644 index 0000000..dbb04e4 Binary files /dev/null and b/app/src/main/res/drawable/icon_division.png differ diff --git a/app/src/main/res/drawable/icon_down.png b/app/src/main/res/drawable/icon_down.png new file mode 100644 index 0000000..9ea6d45 Binary files /dev/null and b/app/src/main/res/drawable/icon_down.png differ diff --git a/app/src/main/res/drawable/icon_edit.png b/app/src/main/res/drawable/icon_edit.png new file mode 100644 index 0000000..aeca7aa Binary files /dev/null and b/app/src/main/res/drawable/icon_edit.png differ diff --git a/app/src/main/res/drawable/icon_encrypted.png b/app/src/main/res/drawable/icon_encrypted.png new file mode 100644 index 0000000..4f15c63 Binary files /dev/null and b/app/src/main/res/drawable/icon_encrypted.png differ diff --git a/app/src/main/res/drawable/icon_executable.png b/app/src/main/res/drawable/icon_executable.png new file mode 100644 index 0000000..d4d9f96 Binary files /dev/null and b/app/src/main/res/drawable/icon_executable.png differ diff --git a/app/src/main/res/drawable/icon_family.png b/app/src/main/res/drawable/icon_family.png new file mode 100644 index 0000000..abeb561 Binary files /dev/null and b/app/src/main/res/drawable/icon_family.png differ diff --git a/app/src/main/res/drawable/icon_fema.png b/app/src/main/res/drawable/icon_fema.png new file mode 100644 index 0000000..f2691a9 Binary files /dev/null and b/app/src/main/res/drawable/icon_fema.png differ diff --git a/app/src/main/res/drawable/icon_female1.png b/app/src/main/res/drawable/icon_female1.png new file mode 100644 index 0000000..a67091f Binary files /dev/null and b/app/src/main/res/drawable/icon_female1.png differ diff --git a/app/src/main/res/drawable/icon_female2.png b/app/src/main/res/drawable/icon_female2.png new file mode 100644 index 0000000..7202015 Binary files /dev/null and b/app/src/main/res/drawable/icon_female2.png differ diff --git a/app/src/main/res/drawable/icon_females.png b/app/src/main/res/drawable/icon_females.png new file mode 100644 index 0000000..84b1349 Binary files /dev/null and b/app/src/main/res/drawable/icon_females.png differ diff --git a/app/src/main/res/drawable/icon_flag.png b/app/src/main/res/drawable/icon_flag.png new file mode 100644 index 0000000..be26f26 Binary files /dev/null and b/app/src/main/res/drawable/icon_flag.png differ diff --git a/app/src/main/res/drawable/icon_flag_black.png b/app/src/main/res/drawable/icon_flag_black.png new file mode 100644 index 0000000..3394ebb Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_black.png differ diff --git a/app/src/main/res/drawable/icon_flag_blue.png b/app/src/main/res/drawable/icon_flag_blue.png new file mode 100644 index 0000000..3bee08a Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_blue.png differ diff --git a/app/src/main/res/drawable/icon_flag_green.png b/app/src/main/res/drawable/icon_flag_green.png new file mode 100644 index 0000000..07850a2 Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_green.png differ diff --git a/app/src/main/res/drawable/icon_flag_orange.png b/app/src/main/res/drawable/icon_flag_orange.png new file mode 100644 index 0000000..a5a4346 Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_orange.png differ diff --git a/app/src/main/res/drawable/icon_flag_pink.png b/app/src/main/res/drawable/icon_flag_pink.png new file mode 100644 index 0000000..221114c Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_pink.png differ diff --git a/app/src/main/res/drawable/icon_flag_yellow.png b/app/src/main/res/drawable/icon_flag_yellow.png new file mode 100644 index 0000000..c29b9cf Binary files /dev/null and b/app/src/main/res/drawable/icon_flag_yellow.png differ diff --git a/app/src/main/res/drawable/icon_folder.png b/app/src/main/res/drawable/icon_folder.png new file mode 100644 index 0000000..f34809f Binary files /dev/null and b/app/src/main/res/drawable/icon_folder.png differ diff --git a/app/src/main/res/drawable/icon_forward.png b/app/src/main/res/drawable/icon_forward.png new file mode 100644 index 0000000..af3668f Binary files /dev/null and b/app/src/main/res/drawable/icon_forward.png differ diff --git a/app/src/main/res/drawable/icon_freemind_butterfly.png b/app/src/main/res/drawable/icon_freemind_butterfly.png new file mode 100644 index 0000000..896188f Binary files /dev/null and b/app/src/main/res/drawable/icon_freemind_butterfly.png differ diff --git a/app/src/main/res/drawable/icon_full_0.png b/app/src/main/res/drawable/icon_full_0.png new file mode 100644 index 0000000..253af36 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_0.png differ diff --git a/app/src/main/res/drawable/icon_full_1.png b/app/src/main/res/drawable/icon_full_1.png new file mode 100644 index 0000000..a2adb49 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_1.png differ diff --git a/app/src/main/res/drawable/icon_full_2.png b/app/src/main/res/drawable/icon_full_2.png new file mode 100644 index 0000000..4796906 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_2.png differ diff --git a/app/src/main/res/drawable/icon_full_3.png b/app/src/main/res/drawable/icon_full_3.png new file mode 100644 index 0000000..66acf1c Binary files /dev/null and b/app/src/main/res/drawable/icon_full_3.png differ diff --git a/app/src/main/res/drawable/icon_full_4.png b/app/src/main/res/drawable/icon_full_4.png new file mode 100644 index 0000000..595d587 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_4.png differ diff --git a/app/src/main/res/drawable/icon_full_5.png b/app/src/main/res/drawable/icon_full_5.png new file mode 100644 index 0000000..bffcee9 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_5.png differ diff --git a/app/src/main/res/drawable/icon_full_6.png b/app/src/main/res/drawable/icon_full_6.png new file mode 100644 index 0000000..9618296 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_6.png differ diff --git a/app/src/main/res/drawable/icon_full_7.png b/app/src/main/res/drawable/icon_full_7.png new file mode 100644 index 0000000..69f057d Binary files /dev/null and b/app/src/main/res/drawable/icon_full_7.png differ diff --git a/app/src/main/res/drawable/icon_full_8.png b/app/src/main/res/drawable/icon_full_8.png new file mode 100644 index 0000000..64b20a8 Binary files /dev/null and b/app/src/main/res/drawable/icon_full_8.png differ diff --git a/app/src/main/res/drawable/icon_full_9.png b/app/src/main/res/drawable/icon_full_9.png new file mode 100644 index 0000000..754226f Binary files /dev/null and b/app/src/main/res/drawable/icon_full_9.png differ diff --git a/app/src/main/res/drawable/icon_go.png b/app/src/main/res/drawable/icon_go.png new file mode 100644 index 0000000..0fe8843 Binary files /dev/null and b/app/src/main/res/drawable/icon_go.png differ diff --git a/app/src/main/res/drawable/icon_gohome.png b/app/src/main/res/drawable/icon_gohome.png new file mode 100644 index 0000000..a47001d Binary files /dev/null and b/app/src/main/res/drawable/icon_gohome.png differ diff --git a/app/src/main/res/drawable/icon_group.png b/app/src/main/res/drawable/icon_group.png new file mode 100644 index 0000000..c2c76e5 Binary files /dev/null and b/app/src/main/res/drawable/icon_group.png differ diff --git a/app/src/main/res/drawable/icon_help.png b/app/src/main/res/drawable/icon_help.png new file mode 100644 index 0000000..31c4e67 Binary files /dev/null and b/app/src/main/res/drawable/icon_help.png differ diff --git a/app/src/main/res/drawable/icon_hourglass.png b/app/src/main/res/drawable/icon_hourglass.png new file mode 100644 index 0000000..84f9cdd Binary files /dev/null and b/app/src/main/res/drawable/icon_hourglass.png differ diff --git a/app/src/main/res/drawable/icon_idea.png b/app/src/main/res/drawable/icon_idea.png new file mode 100644 index 0000000..b2c4be8 Binary files /dev/null and b/app/src/main/res/drawable/icon_idea.png differ diff --git a/app/src/main/res/drawable/icon_image.png b/app/src/main/res/drawable/icon_image.png new file mode 100644 index 0000000..bc38937 Binary files /dev/null and b/app/src/main/res/drawable/icon_image.png differ diff --git a/app/src/main/res/drawable/icon_info.png b/app/src/main/res/drawable/icon_info.png new file mode 100644 index 0000000..c7719ba Binary files /dev/null and b/app/src/main/res/drawable/icon_info.png differ diff --git a/app/src/main/res/drawable/icon_internet.png b/app/src/main/res/drawable/icon_internet.png new file mode 100644 index 0000000..4824f4f Binary files /dev/null and b/app/src/main/res/drawable/icon_internet.png differ diff --git a/app/src/main/res/drawable/icon_internet_warning.png b/app/src/main/res/drawable/icon_internet_warning.png new file mode 100644 index 0000000..c62303f Binary files /dev/null and b/app/src/main/res/drawable/icon_internet_warning.png differ diff --git a/app/src/main/res/drawable/icon_kaddressbook.png b/app/src/main/res/drawable/icon_kaddressbook.png new file mode 100644 index 0000000..e1067af Binary files /dev/null and b/app/src/main/res/drawable/icon_kaddressbook.png differ diff --git a/app/src/main/res/drawable/icon_kmail.png b/app/src/main/res/drawable/icon_kmail.png new file mode 100644 index 0000000..56325b6 Binary files /dev/null and b/app/src/main/res/drawable/icon_kmail.png differ diff --git a/app/src/main/res/drawable/icon_knotify.png b/app/src/main/res/drawable/icon_knotify.png new file mode 100644 index 0000000..9bea5a0 Binary files /dev/null and b/app/src/main/res/drawable/icon_knotify.png differ diff --git a/app/src/main/res/drawable/icon_korn.png b/app/src/main/res/drawable/icon_korn.png new file mode 100644 index 0000000..275f026 Binary files /dev/null and b/app/src/main/res/drawable/icon_korn.png differ diff --git a/app/src/main/res/drawable/icon_ksmiletris.png b/app/src/main/res/drawable/icon_ksmiletris.png new file mode 100644 index 0000000..54ab5b0 Binary files /dev/null and b/app/src/main/res/drawable/icon_ksmiletris.png differ diff --git a/app/src/main/res/drawable/icon_launch.png b/app/src/main/res/drawable/icon_launch.png new file mode 100644 index 0000000..30f8e3d Binary files /dev/null and b/app/src/main/res/drawable/icon_launch.png differ diff --git a/app/src/main/res/drawable/icon_licq.png b/app/src/main/res/drawable/icon_licq.png new file mode 100644 index 0000000..5ea5e4d Binary files /dev/null and b/app/src/main/res/drawable/icon_licq.png differ diff --git a/app/src/main/res/drawable/icon_list.png b/app/src/main/res/drawable/icon_list.png new file mode 100644 index 0000000..1827654 Binary files /dev/null and b/app/src/main/res/drawable/icon_list.png differ diff --git a/app/src/main/res/drawable/icon_mail.png b/app/src/main/res/drawable/icon_mail.png new file mode 100644 index 0000000..66553c3 Binary files /dev/null and b/app/src/main/res/drawable/icon_mail.png differ diff --git a/app/src/main/res/drawable/icon_male1.png b/app/src/main/res/drawable/icon_male1.png new file mode 100644 index 0000000..6dfa853 Binary files /dev/null and b/app/src/main/res/drawable/icon_male1.png differ diff --git a/app/src/main/res/drawable/icon_male2.png b/app/src/main/res/drawable/icon_male2.png new file mode 100644 index 0000000..f2e9819 Binary files /dev/null and b/app/src/main/res/drawable/icon_male2.png differ diff --git a/app/src/main/res/drawable/icon_males.png b/app/src/main/res/drawable/icon_males.png new file mode 100644 index 0000000..54b7fe4 Binary files /dev/null and b/app/src/main/res/drawable/icon_males.png differ diff --git a/app/src/main/res/drawable/icon_messagebox_warning.png b/app/src/main/res/drawable/icon_messagebox_warning.png new file mode 100644 index 0000000..dfc69db Binary files /dev/null and b/app/src/main/res/drawable/icon_messagebox_warning.png differ diff --git a/app/src/main/res/drawable/icon_mindmap.png b/app/src/main/res/drawable/icon_mindmap.png new file mode 100644 index 0000000..8f70fac Binary files /dev/null and b/app/src/main/res/drawable/icon_mindmap.png differ diff --git a/app/src/main/res/drawable/icon_multiplication.png b/app/src/main/res/drawable/icon_multiplication.png new file mode 100644 index 0000000..dc607a5 Binary files /dev/null and b/app/src/main/res/drawable/icon_multiplication.png differ diff --git a/app/src/main/res/drawable/icon_narrative.png b/app/src/main/res/drawable/icon_narrative.png new file mode 100644 index 0000000..209350c Binary files /dev/null and b/app/src/main/res/drawable/icon_narrative.png differ diff --git a/app/src/main/res/drawable/icon_negative.png b/app/src/main/res/drawable/icon_negative.png new file mode 100644 index 0000000..26dbff4 Binary files /dev/null and b/app/src/main/res/drawable/icon_negative.png differ diff --git a/app/src/main/res/drawable/icon_neutral.png b/app/src/main/res/drawable/icon_neutral.png new file mode 100644 index 0000000..4f9eb54 Binary files /dev/null and b/app/src/main/res/drawable/icon_neutral.png differ diff --git a/app/src/main/res/drawable/icon_password.png b/app/src/main/res/drawable/icon_password.png new file mode 100644 index 0000000..f7e8e59 Binary files /dev/null and b/app/src/main/res/drawable/icon_password.png differ diff --git a/app/src/main/res/drawable/icon_pencil.png b/app/src/main/res/drawable/icon_pencil.png new file mode 100644 index 0000000..b371127 Binary files /dev/null and b/app/src/main/res/drawable/icon_pencil.png differ diff --git a/app/src/main/res/drawable/icon_penguin.png b/app/src/main/res/drawable/icon_penguin.png new file mode 100644 index 0000000..808dabd Binary files /dev/null and b/app/src/main/res/drawable/icon_penguin.png differ diff --git a/app/src/main/res/drawable/icon_positive.png b/app/src/main/res/drawable/icon_positive.png new file mode 100644 index 0000000..cf254b1 Binary files /dev/null and b/app/src/main/res/drawable/icon_positive.png differ diff --git a/app/src/main/res/drawable/icon_prepare.png b/app/src/main/res/drawable/icon_prepare.png new file mode 100644 index 0000000..260be28 Binary files /dev/null and b/app/src/main/res/drawable/icon_prepare.png differ diff --git a/app/src/main/res/drawable/icon_redo.png b/app/src/main/res/drawable/icon_redo.png new file mode 100644 index 0000000..69cff22 Binary files /dev/null and b/app/src/main/res/drawable/icon_redo.png differ diff --git a/app/src/main/res/drawable/icon_revision.png b/app/src/main/res/drawable/icon_revision.png new file mode 100644 index 0000000..9e54025 Binary files /dev/null and b/app/src/main/res/drawable/icon_revision.png differ diff --git a/app/src/main/res/drawable/icon_smiley_angry.png b/app/src/main/res/drawable/icon_smiley_angry.png new file mode 100644 index 0000000..43d0871 Binary files /dev/null and b/app/src/main/res/drawable/icon_smiley_angry.png differ diff --git a/app/src/main/res/drawable/icon_smiley_neutral.png b/app/src/main/res/drawable/icon_smiley_neutral.png new file mode 100644 index 0000000..89b9b27 Binary files /dev/null and b/app/src/main/res/drawable/icon_smiley_neutral.png differ diff --git a/app/src/main/res/drawable/icon_smiley_oh.png b/app/src/main/res/drawable/icon_smiley_oh.png new file mode 100644 index 0000000..3e1284c Binary files /dev/null and b/app/src/main/res/drawable/icon_smiley_oh.png differ diff --git a/app/src/main/res/drawable/icon_smily_bad.png b/app/src/main/res/drawable/icon_smily_bad.png new file mode 100644 index 0000000..15bd61c Binary files /dev/null and b/app/src/main/res/drawable/icon_smily_bad.png differ diff --git a/app/src/main/res/drawable/icon_stop.png b/app/src/main/res/drawable/icon_stop.png new file mode 100644 index 0000000..a092f72 Binary files /dev/null and b/app/src/main/res/drawable/icon_stop.png differ diff --git a/app/src/main/res/drawable/icon_stop_sign.png b/app/src/main/res/drawable/icon_stop_sign.png new file mode 100644 index 0000000..a978ed2 Binary files /dev/null and b/app/src/main/res/drawable/icon_stop_sign.png differ diff --git a/app/src/main/res/drawable/icon_subtraction.png b/app/src/main/res/drawable/icon_subtraction.png new file mode 100644 index 0000000..924fb6f Binary files /dev/null and b/app/src/main/res/drawable/icon_subtraction.png differ diff --git a/app/src/main/res/drawable/icon_unchecked.png b/app/src/main/res/drawable/icon_unchecked.png new file mode 100644 index 0000000..38e2b19 Binary files /dev/null and b/app/src/main/res/drawable/icon_unchecked.png differ diff --git a/app/src/main/res/drawable/icon_up.png b/app/src/main/res/drawable/icon_up.png new file mode 100644 index 0000000..284181c Binary files /dev/null and b/app/src/main/res/drawable/icon_up.png differ diff --git a/app/src/main/res/drawable/icon_user_icon.png b/app/src/main/res/drawable/icon_user_icon.png new file mode 100644 index 0000000..d43c949 Binary files /dev/null and b/app/src/main/res/drawable/icon_user_icon.png differ diff --git a/app/src/main/res/drawable/icon_very_negative.png b/app/src/main/res/drawable/icon_very_negative.png new file mode 100644 index 0000000..9588243 Binary files /dev/null and b/app/src/main/res/drawable/icon_very_negative.png differ diff --git a/app/src/main/res/drawable/icon_very_positive.png b/app/src/main/res/drawable/icon_very_positive.png new file mode 100644 index 0000000..e3269ce Binary files /dev/null and b/app/src/main/res/drawable/icon_very_positive.png differ diff --git a/app/src/main/res/drawable/icon_video.png b/app/src/main/res/drawable/icon_video.png new file mode 100644 index 0000000..af904d5 Binary files /dev/null and b/app/src/main/res/drawable/icon_video.png differ diff --git a/app/src/main/res/drawable/icon_wizard.png b/app/src/main/res/drawable/icon_wizard.png new file mode 100644 index 0000000..be4f3e8 Binary files /dev/null and b/app/src/main/res/drawable/icon_wizard.png differ diff --git a/app/src/main/res/drawable/icon_xmag.png b/app/src/main/res/drawable/icon_xmag.png new file mode 100644 index 0000000..08d31e0 Binary files /dev/null and b/app/src/main/res/drawable/icon_xmag.png differ diff --git a/app/src/main/res/drawable/icon_yes.png b/app/src/main/res/drawable/icon_yes.png new file mode 100644 index 0000000..d3c09a2 Binary files /dev/null and b/app/src/main/res/drawable/icon_yes.png differ diff --git a/app/src/main/res/drawable/minus_alt.png b/app/src/main/res/drawable/minus_alt.png new file mode 100644 index 0000000..ca7b695 Binary files /dev/null and b/app/src/main/res/drawable/minus_alt.png differ diff --git a/app/src/main/res/drawable/plus_alt.png b/app/src/main/res/drawable/plus_alt.png new file mode 100644 index 0000000..cbf339f Binary files /dev/null and b/app/src/main/res/drawable/plus_alt.png differ diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..9613049 --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9613049 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/mindmap_node_list_item.xml b/app/src/main/res/layout/mindmap_node_list_item.xml new file mode 100644 index 0000000..7bb769d --- /dev/null +++ b/app/src/main/res/layout/mindmap_node_list_item.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 0000000..d4aee4f --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/example.mm b/app/src/main/res/raw/example.mm new file mode 100644 index 0000000..de5a377 --- /dev/null +++ b/app/src/main/res/raw/example.mm @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..34a51f0 --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,10 @@ + + + + 16dp + 16dp + + + 3 + + \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 0000000..1ba777d --- /dev/null +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-sw720dp-land/dimens.xml b/app/src/main/res/values-sw720dp-land/dimens.xml new file mode 100644 index 0000000..eee741a --- /dev/null +++ b/app/src/main/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,9 @@ + + + + 128dp + + \ No newline at end of file diff --git a/app/src/main/res/values-v11/styles.xml b/app/src/main/res/values-v11/styles.xml new file mode 100644 index 0000000..541752f --- /dev/null +++ b/app/src/main/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v14/styles.xml b/app/src/main/res/values-v14/styles.xml new file mode 100644 index 0000000..f20e015 --- /dev/null +++ b/app/src/main/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/analytics.xml b/app/src/main/res/values/analytics.xml new file mode 100644 index 0000000..6aa6590 --- /dev/null +++ b/app/src/main/res/values/analytics.xml @@ -0,0 +1,16 @@ + + + + + UA-39295651-1 + + + true + + + true + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..78d5c30 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + + + 16dp + 16dp + + + 1 + + \ No newline at end of file diff --git a/app/src/main/res/values/identifiers.xml b/app/src/main/res/values/identifiers.xml new file mode 100644 index 0000000..27017ea --- /dev/null +++ b/app/src/main/res/values/identifiers.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..56554b5 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + + DroidPlane + Settings + MainActivity + Up + Top + Yes + OK + Sorry, I could not find the file you wanted to open! + no icon + Please start DroidPlane by opening a *.mm file" + Copy Node Text + Open Link + Help + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..4a10ca4 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e26cdee --- /dev/null +++ b/build.gradle @@ -0,0 +1,15 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/droidplane.iml b/droidplane.iml new file mode 100644 index 0000000..2a02201 --- /dev/null +++ b/droidplane.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0c71e76 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/test/With Attachment/Attachment.mm b/test/With Attachment/Attachment.mm new file mode 100644 index 0000000..c7ab9a3 --- /dev/null +++ b/test/With Attachment/Attachment.mm @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/With Attachment/subfolder/a/whyfp.pdf b/test/With Attachment/subfolder/a/whyfp.pdf new file mode 100644 index 0000000..69b94cf Binary files /dev/null and b/test/With Attachment/subfolder/a/whyfp.pdf differ