FileDocCategorySizeDatePackage
NoteEditor.javaAPI DocGoogle Android v1.5 Example12699Sun Nov 11 13:01:04 GMT 2007com.google.android.notepad

NoteEditor.java

/* 
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.notepad;

import com.google.provider.NotePad;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.ContentURI;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Config;
import android.view.KeyEvent;
import android.view.Menu;
import android.widget.EditText;

import java.util.Map;

/**
 * A generic activity for editing a note in a database.  This can be used
 * either to simply view a note (Intent.VIEW_ACTION), view and edit a note
 * (Intent.EDIT_ACTION), or create a new note (Intent.INSERT_ACTION).  
 */
public class NoteEditor extends Activity {

    private static final String TAG = "Notes";

    private static final int NOTE_INDEX = 1;
    private static final int TITLE_INDEX = 2;
    private static final int MODIFIED_INDEX = 3;
    
    /**
     * Standard projection for the interesting columns of a normal note.
     */
    private static final String[] PROJECTION = new String[] {
            NotePad.Notes._ID, // 0
            NotePad.Notes.NOTE, // 1
            NotePad.Notes.TITLE, // 2
            NotePad.Notes.MODIFIED_DATE // 3
    };
    
    // This is our state data that is stored when freezing.
    private static final String ORIGINAL_CONTENT = "origContent";

    // Identifiers for our menu items.
    private static final int REVERT_ID = Menu.FIRST;
    private static final int DISCARD_ID = Menu.FIRST + 1;
    private static final int DELETE_ID = Menu.FIRST + 2;

    // The different distinct states the activity can be run in.
    private static final int STATE_EDIT = 0;
    private static final int STATE_INSERT = 1;

    private int mState;
    private boolean mNoteOnly = false;
    private ContentURI mURI;
    private Cursor mCursor;
    private EditText mText;
    private String mOriginalContent;

    public static class MyEditText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for ViewInflate
        public MyEditText(Context context, AttributeSet attrs, Map params) {
            super(context, attrs, params);
            
            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }
        
        @Override
        protected void onDraw(Canvas canvas) {

            int count = getLineCount();
            Rect r = mRect;
            Paint paint = mPaint;

            for (int i = 0; i < count; i++) {
                int baseline = getLineBounds(i, r);

                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
                                paint);
            }

            super.onDraw(canvas);
        }
    }

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        final Intent intent = getIntent();
        final String type = intent.resolveType(this);

        // Do some setup based on the action being performed.

        final String action = intent.getAction();
        if (action.equals(Intent.EDIT_ACTION)) {
            // Requested to edit: set that state, and the data being edited.
            mState = STATE_EDIT;
            mURI = intent.getData();

        } else if (action.equals(Intent.INSERT_ACTION)) {
            // Requested to insert: set that state, and create a new entry
            // in the container.
            mState = STATE_INSERT;
            mURI = getContentResolver().insert(intent.getData(), null);

            // If we were unable to create a new note, then just finish
            // this activity.  A RESULT_CANCELED will be sent back to the
            // original activity if they requested a result.
            if (mURI == null) {
                Log.e("Notes", "Failed to insert new note into "
                        + getIntent().getData());
                finish();
                return;
            }

            // The new entry was created, so assume all will end well and
            // set the result to be returned.
            setResult(RESULT_OK, mURI.toString());

        } else {
            // Whoops, unknown action!  Bail.
            Log.e(TAG, "Unknown action, exiting");
            finish();
            return;
        }

        // Set the layout for this activity.  You can find it
        // in res/layout/hello_activity.xml
        setContentView(R.layout.note_editor);
        
        // The text view for our note, identified by its ID in the XML file.
        mText = (EditText) findViewById(R.id.note);

        // Get the note!
        mCursor = managedQuery(mURI, PROJECTION, null, null);

        // If an instance of this activity had previously stopped, we can
        // get the original text it started with.
        if (icicle != null) {
            mOriginalContent = icicle.getString(ORIGINAL_CONTENT);
        }
    }


    @Override
    protected void onResume() {
        super.onResume();

        // If we didn't have any trouble retrieving the data, it is now
        // time to get at the stuff.
        if (mCursor != null) {
            // Make sure we are at the one and only row in the cursor.
            mCursor.first();

            // Modify our overall title depending on the mode we are running in.
            if (mState == STATE_EDIT) {
                setTitle(getText(R.string.title_edit));
            } else if (mState == STATE_INSERT) {
                setTitle(getText(R.string.title_create));
            }

            // This is a little nasty: we be resumed after previously being
            // paused/stopped.  We want to re-retrieve the data to make sure
            // we are still accurately showing what is in the cursor...  but
            // we don't want to lose any UI state like the current cursor
            // position.  This trick accomplishes that.  In the future we
            // should have a better API for doing this...
            Bundle curState = mText.saveState();
            String note = mCursor.getString(NOTE_INDEX);
            mText.setText(note);
            mText.restoreState(curState);
            
            // If we hadn't previously retrieved the original text, do so
            // now.  This allows the user to revert their changes.
            if (mOriginalContent == null) {
                mOriginalContent = note;
            }

        } else {
            setTitle(getText(R.string.error_title));
            mText.setText(getText(R.string.error_message));
        }
    }

    @Override
    protected void onFreeze(Bundle outState) {
        // Save away the original text, so we still have it if the activity
        // needs to be killed while paused.
        outState.putString(ORIGINAL_CONTENT, mOriginalContent);
    }

    @Override
    protected void onPause() {
        super.onPause();

        // The user is going somewhere else, so make sure their current
        // changes are safely saved away in the provider.  We don't need
        // to do this if only editing.
        if (mCursor != null) {
            String text = mText.getText().toString();
            int length = text.length();

            // If this activity is finished, and there is no text, then we
            // do something a little special: simply delete the note entry.
            // Note that we do this both for editing and inserting...  it
            // would be reasonable to only do it when inserting.
            if (isFinishing() && (length == 0) && !mNoteOnly) {
                setResult(RESULT_CANCELED);
                deleteNote();

            // Get out updates into the provider.
            } else {
                // This stuff is only done when working with a full-fledged note.
                if (!mNoteOnly) {
                    // Bump the modification time to now.
                    mCursor.updateLong(MODIFIED_INDEX, System.currentTimeMillis());

                    // If we are creating a new note, then we want to also create
                    // an initial title for it.
                    if (mState == STATE_INSERT) {
                        String title = text.substring(0, Math.min(30, length));
                        if (length > 30) {
                            int lastSpace = title.lastIndexOf(' ');
                            if (lastSpace > 0) {
                                title = title.substring(0, lastSpace);
                            }
                        }
                        mCursor.updateString(TITLE_INDEX, title);
                    }
                }

                // Write our text back into the provider.
                mCursor.updateString(NOTE_INDEX, text);

                // Commit all of our changes to persistent storage.  Note the
                // use of managedCommitUpdates() instead of
                // mCursor.commitUpdates() -- this lets Activity take care of
                // requerying the new data if needed.
                managedCommitUpdates(mCursor);
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Build the menus that are shown when editing.
        if (mState == STATE_EDIT) {
            menu.add(0, REVERT_ID, R.string.menu_revert).setShortcut(
                    KeyEvent.KEYCODE_0, 0, KeyEvent.KEYCODE_R);
            if (!mNoteOnly) {
                menu.add(0, DELETE_ID, R.string.menu_delete).setShortcut(
                        KeyEvent.KEYCODE_1, 0, KeyEvent.KEYCODE_D);
            }

        // Build the menus that are shown when inserting.
        } else {
            menu.add(0, DISCARD_ID, R.string.menu_discard).setShortcut(
                    KeyEvent.KEYCODE_0, 0, KeyEvent.KEYCODE_D);
        }

        // If we are working on a real honest-to-ghod note, then append to the
        // menu items for any other activities that can do stuff with it
        // as well.  This does a query on the system for any activities that
        // implement the ALTERNATIVE_ACTION for our data, adding a menu item
        // for each one that is found.
        if (!mNoteOnly) {
            Intent intent = new Intent(null, getIntent().getData());
            intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
            menu.addIntentOptions(
                Menu.ALTERNATIVE, 0,
                new ComponentName(this, NoteEditor.class), null,
                intent, 0, null);
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(Menu.Item item) {
        // Handle all of the possible menu actions.
        switch (item.getId()) {
        case DELETE_ID:
            deleteNote();
            finish();
            break;
        case DISCARD_ID:
            cancelNote();
            break;
        case REVERT_ID:
            cancelNote();
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Take care of cancelling work on a note.  Deletes the note if we
     * had created it, otherwise reverts to the original text.
     */
    private final void cancelNote() {
        if (mCursor != null) {
            if (mState == STATE_EDIT) {
                mCursor.updateString(NOTE_INDEX, mOriginalContent);
                mCursor.commitUpdates();
                mCursor.deactivate();
                mCursor = null;
            } else if (mState == STATE_INSERT) {
                deleteNote();
            }
        }
        setResult(RESULT_CANCELED);
        finish();
    }

    /**
     * Take care of deleting a note.  Simply deletes the entry.
     */
    private final void deleteNote() {
        if (mCursor != null) {
            mText.setText("");
            mCursor.deleteRow();
            mCursor.deactivate();
            mCursor = null;
        }
    }
}