blob: e45efd865a2acec4026b99090d98860c217dd94d [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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.example.android.notepad;
import com.example.android.notepad.NotePad.Notes;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentValues;
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.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
/**
* A generic activity for editing a note in a database. This can be used
* either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
* {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
*/
public class NoteEditor extends Activity {
private static final String TAG = "Notes";
/**
* Standard projection for the interesting columns of a normal note.
*/
private static final String[] PROJECTION = new String[] {
Notes._ID, // 0
Notes.NOTE, // 1
};
/** The index of the note column */
private static final int COLUMN_INDEX_NOTE = 1;
// 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 Uri mUri;
private Cursor mCursor;
private EditText mText;
private String mOriginalContent;
/**
* A custom EditText that draws lines between each line of text that is displayed.
*/
public static class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
@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 savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
// Do some setup based on the action being performed.
final String action = intent.getAction();
if (Intent.ACTION_EDIT.equals(action)) {
// Requested to edit: set that state, and the data being edited.
mState = STATE_EDIT;
mUri = intent.getData();
} else if (Intent.ACTION_INSERT.equals(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(TAG, "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, (new Intent()).setAction(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/note_editor.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, null);
// If an instance of this activity had previously stopped, we can
// get the original text it started with.
if (savedInstanceState != null) {
mOriginalContent = savedInstanceState.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.moveToFirst();
// 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 tricky: we may be resumed after previously being
// paused/stopped. We want to put the new text in the text view,
// but leave the user where they were (retain the cursor position
// etc). This version of setText does that for us.
String note = mCursor.getString(COLUMN_INDEX_NOTE);
mText.setTextKeepState(note);
// 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 onSaveInstanceState(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 {
ContentValues values = new ContentValues();
// This stuff is only done when working with a full-fledged note.
if (!mNoteOnly) {
// Bump the modification time to now.
values.put(Notes.MODIFIED_DATE, 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);
}
}
values.put(Notes.TITLE, title);
}
}
// Write our text back into the provider.
values.put(Notes.NOTE, text);
// Commit all of our changes to persistent storage. When the update completes
// the content provider will notify the cursor of the change, which will
// cause the UI to be updated.
getContentResolver().update(mUri, values, null, null);
}
}
}
@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, 0, R.string.menu_revert)
.setShortcut('0', 'r')
.setIcon(android.R.drawable.ic_menu_revert);
if (!mNoteOnly) {
menu.add(0, DELETE_ID, 0, R.string.menu_delete)
.setShortcut('1', 'd')
.setIcon(android.R.drawable.ic_menu_delete);
}
// Build the menus that are shown when inserting.
} else {
menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
.setShortcut('0', 'd')
.setIcon(android.R.drawable.ic_menu_delete);
}
// If we are working on a full 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.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NoteEditor.class), null, intent, 0, null);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle all of the possible menu actions.
switch (item.getItemId()) {
case DELETE_ID:
deleteNote();
finish();
break;
case DISCARD_ID:
cancelNote();
break;
case REVERT_ID:
cancelNote();
break;
}
return super.onOptionsItemSelected(item);
}
/**
* Take care of canceling 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) {
// Put the original note text back into the database
mCursor.close();
mCursor = null;
ContentValues values = new ContentValues();
values.put(Notes.NOTE, mOriginalContent);
getContentResolver().update(mUri, values, null, null);
} else if (mState == STATE_INSERT) {
// We inserted an empty note, make sure to delete it
deleteNote();
}
}
setResult(RESULT_CANCELED);
finish();
}
/**
* Take care of deleting a note. Simply deletes the entry.
*/
private final void deleteNote() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
getContentResolver().delete(mUri, null, null);
mText.setText("");
}
}
}