Home | History | Annotate | Download | only in notepad
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.notepad;
     18 
     19 import android.app.Activity;
     20 import android.content.ComponentName;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.res.Resources;
     25 import android.database.Cursor;
     26 import android.graphics.Canvas;
     27 import android.graphics.Paint;
     28 import android.graphics.Rect;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.util.AttributeSet;
     32 import android.util.Log;
     33 import android.view.Menu;
     34 import android.view.MenuInflater;
     35 import android.view.MenuItem;
     36 import android.widget.EditText;
     37 import android.widget.Toast;
     38 
     39 import com.example.android.notepad.NotePad.NoteColumns;
     40 
     41 /**
     42  * A generic activity for editing a note in a database.  This can be used
     43  * either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
     44  * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
     45  */
     46 public class NoteEditor extends Activity {
     47     private static final String TAG = "NoteEditor";
     48 
     49     /**
     50      * Standard projection for the interesting columns of a normal note.
     51      */
     52     private static final String[] PROJECTION = new String[] {
     53         NoteColumns._ID, // 0
     54         NoteColumns.NOTE, // 1
     55         NoteColumns.TITLE, // 2
     56     };
     57     /** The index of the note column */
     58     private static final int COLUMN_INDEX_NOTE = 1;
     59     /** The index of the title column */
     60     private static final int COLUMN_INDEX_TITLE = 2;
     61 
     62     // This is our state data that is stored when freezing.
     63     private static final String ORIGINAL_CONTENT = "origContent";
     64 
     65     // The different distinct states the activity can be run in.
     66     private static final int STATE_EDIT = 0;
     67     private static final int STATE_INSERT = 1;
     68 
     69     private int mState;
     70     private Uri mUri;
     71     private Cursor mCursor;
     72     private EditText mText;
     73     private String mOriginalContent;
     74 
     75     /**
     76      * A custom EditText that draws lines between each line of text that is displayed.
     77      */
     78     public static class LinedEditText extends EditText {
     79         private Rect mRect;
     80         private Paint mPaint;
     81 
     82         // we need this constructor for LayoutInflater
     83         public LinedEditText(Context context, AttributeSet attrs) {
     84             super(context, attrs);
     85 
     86             mRect = new Rect();
     87             mPaint = new Paint();
     88             mPaint.setStyle(Paint.Style.STROKE);
     89             mPaint.setColor(0x800000FF);
     90         }
     91 
     92         @Override
     93         protected void onDraw(Canvas canvas) {
     94             int count = getLineCount();
     95             Rect r = mRect;
     96             Paint paint = mPaint;
     97 
     98             for (int i = 0; i < count; i++) {
     99                 int baseline = getLineBounds(i, r);
    100 
    101                 canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
    102             }
    103 
    104             super.onDraw(canvas);
    105         }
    106     }
    107 
    108     @Override
    109     protected void onCreate(Bundle savedInstanceState) {
    110         super.onCreate(savedInstanceState);
    111 
    112         final Intent intent = getIntent();
    113 
    114         // Do some setup based on the action being performed.
    115         final String action = intent.getAction();
    116         if (Intent.ACTION_EDIT.equals(action)) {
    117             // Requested to edit: set that state, and the data being edited.
    118             mState = STATE_EDIT;
    119             mUri = intent.getData();
    120         } else if (Intent.ACTION_INSERT.equals(action)) {
    121             // Requested to insert: set that state, and create a new entry
    122             // in the container.
    123             mState = STATE_INSERT;
    124             mUri = getContentResolver().insert(intent.getData(), null);
    125 
    126             // If we were unable to create a new note, then just finish
    127             // this activity.  A RESULT_CANCELED will be sent back to the
    128             // original activity if they requested a result.
    129             if (mUri == null) {
    130                 Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
    131                 finish();
    132                 return;
    133             }
    134 
    135             // The new entry was created, so assume all will end well and
    136             // set the result to be returned.
    137             setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
    138 
    139         } else {
    140             // Whoops, unknown action!  Bail.
    141             Log.e(TAG, "Unknown action, exiting");
    142             finish();
    143             return;
    144         }
    145 
    146         // Set the layout for this activity.  You can find it in res/layout/note_editor.xml
    147         setContentView(R.layout.note_editor);
    148 
    149         // The text view for our note, identified by its ID in the XML file.
    150         mText = (EditText) findViewById(R.id.note);
    151 
    152         // Get the note!
    153         mCursor = managedQuery(mUri, PROJECTION, null, null, null);
    154 
    155         // If an instance of this activity had previously stopped, we can
    156         // get the original text it started with.
    157         if (savedInstanceState != null) {
    158             mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
    159         }
    160     }
    161 
    162     @Override
    163     protected void onResume() {
    164         super.onResume();
    165         // If we didn't have any trouble retrieving the data, it is now
    166         // time to get at the stuff.
    167         if (mCursor != null) {
    168             // Requery in case something changed while paused (such as the title)
    169             mCursor.requery();
    170             // Make sure we are at the one and only row in the cursor.
    171             mCursor.moveToFirst();
    172 
    173             // Modify our overall title depending on the mode we are running in.
    174             if (mState == STATE_EDIT) {
    175                 // Set the title of the Activity to include the note title
    176                 String title = mCursor.getString(COLUMN_INDEX_TITLE);
    177                 Resources res = getResources();
    178                 String text = String.format(res.getString(R.string.title_edit), title);
    179                 setTitle(text);
    180             } else if (mState == STATE_INSERT) {
    181                 setTitle(getText(R.string.title_create));
    182             }
    183 
    184             // This is a little tricky: we may be resumed after previously being
    185             // paused/stopped.  We want to put the new text in the text view,
    186             // but leave the user where they were (retain the cursor position
    187             // etc).  This version of setText does that for us.
    188             String note = mCursor.getString(COLUMN_INDEX_NOTE);
    189             mText.setTextKeepState(note);
    190 
    191             // If we hadn't previously retrieved the original text, do so
    192             // now.  This allows the user to revert their changes.
    193             if (mOriginalContent == null) {
    194                 mOriginalContent = note;
    195             }
    196 
    197         } else {
    198             setTitle(getText(R.string.error_title));
    199             mText.setText(getText(R.string.error_message));
    200         }
    201     }
    202 
    203     @Override
    204     protected void onSaveInstanceState(Bundle outState) {
    205         // Save away the original text, so we still have it if the activity
    206         // needs to be killed while paused.
    207         outState.putString(ORIGINAL_CONTENT, mOriginalContent);
    208     }
    209 
    210     @Override
    211     protected void onPause() {
    212         super.onPause();
    213         // The user is going somewhere, so make sure changes are saved
    214 
    215         String text = mText.getText().toString();
    216         int length = text.length();
    217 
    218         // If this activity is finished, and there is no text, then we
    219         // simply delete the note entry.
    220         // Note that we do this both for editing and inserting...  it
    221         // would be reasonable to only do it when inserting.
    222         if (isFinishing() && (length == 0) && mCursor != null) {
    223             setResult(RESULT_CANCELED);
    224             deleteNote();
    225         } else {
    226             saveNote();
    227         }
    228     }
    229 
    230     @Override
    231     public boolean onCreateOptionsMenu(Menu menu) {
    232         // Inflate menu from XML resource
    233         MenuInflater inflater = getMenuInflater();
    234         inflater.inflate(R.menu.editor_options_menu, menu);
    235 
    236         // Append to the
    237         // menu items for any other activities that can do stuff with it
    238         // as well.  This does a query on the system for any activities that
    239         // implement the ALTERNATIVE_ACTION for our data, adding a menu item
    240         // for each one that is found.
    241         Intent intent = new Intent(null, getIntent().getData());
    242         intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
    243         menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
    244                 new ComponentName(this, NoteEditor.class), null, intent, 0, null);
    245 
    246         return super.onCreateOptionsMenu(menu);
    247     }
    248 
    249 
    250 
    251     @Override
    252     public boolean onPrepareOptionsMenu(Menu menu) {
    253         if (mState == STATE_EDIT) {
    254             menu.setGroupVisible(R.id.menu_group_edit, true);
    255             menu.setGroupVisible(R.id.menu_group_insert, false);
    256 
    257             // Check if note has changed and enable/disable the revert option
    258             String savedNote = mCursor.getString(COLUMN_INDEX_NOTE);
    259             String currentNote = mText.getText().toString();
    260             if (savedNote.equals(currentNote)) {
    261                 menu.findItem(R.id.menu_revert).setEnabled(false);
    262             } else {
    263                 menu.findItem(R.id.menu_revert).setEnabled(true);
    264             }
    265         } else {
    266             menu.setGroupVisible(R.id.menu_group_edit, false);
    267             menu.setGroupVisible(R.id.menu_group_insert, true);
    268         }
    269         return super.onPrepareOptionsMenu(menu);
    270     }
    271 
    272     @Override
    273     public boolean onOptionsItemSelected(MenuItem item) {
    274         // Handle all of the possible menu actions.
    275         switch (item.getItemId()) {
    276         case R.id.menu_save:
    277             saveNote();
    278             finish();
    279             break;
    280         case R.id.menu_delete:
    281             deleteNote();
    282             finish();
    283             break;
    284         case R.id.menu_revert:
    285         case R.id.menu_discard:
    286             cancelNote();
    287             break;
    288         }
    289         return super.onOptionsItemSelected(item);
    290 
    291     }
    292 
    293     private final void saveNote() {
    294         // Make sure their current
    295         // changes are safely saved away in the provider.  We don't need
    296         // to do this if only editing.
    297         if (mCursor != null) {
    298             // Get out updates into the provider.
    299             ContentValues values = new ContentValues();
    300 
    301             // Bump the modification time to now.
    302             values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
    303 
    304             String text = mText.getText().toString();
    305             int length = text.length();
    306             // If we are creating a new note, then we want to also create
    307             // an initial title for it.
    308             if (mState == STATE_INSERT) {
    309                 if (length == 0) {
    310                     Toast.makeText(this, R.string.nothing_to_save, Toast.LENGTH_SHORT).show();
    311                     return;
    312                 }
    313                 String title = text.substring(0, Math.min(30, length));
    314                 if (length > 30) {
    315                     int lastSpace = title.lastIndexOf(' ');
    316                     if (lastSpace > 0) {
    317                         title = title.substring(0, lastSpace);
    318                     }
    319                 }
    320                 values.put(NoteColumns.TITLE, title);
    321             }
    322 
    323             // Write our text back into the provider.
    324             values.put(NoteColumns.NOTE, text);
    325 
    326             // Commit all of our changes to persistent storage. When the update completes
    327             // the content provider will notify the cursor of the change, which will
    328             // cause the UI to be updated.
    329             try {
    330                 getContentResolver().update(mUri, values, null, null);
    331             } catch (NullPointerException e) {
    332                 Log.e(TAG, e.getMessage());
    333             }
    334 
    335         }
    336     }
    337 
    338     /**
    339      * Take care of canceling work on a note.  Deletes the note if we
    340      * had created it, otherwise reverts to the original text.
    341      */
    342     private final void cancelNote() {
    343         if (mCursor != null) {
    344             if (mState == STATE_EDIT) {
    345                 // Put the original note text back into the database
    346                 mCursor.close();
    347                 mCursor = null;
    348                 ContentValues values = new ContentValues();
    349                 values.put(NoteColumns.NOTE, mOriginalContent);
    350                 getContentResolver().update(mUri, values, null, null);
    351             } else if (mState == STATE_INSERT) {
    352                 // We inserted an empty note, make sure to delete it
    353                 deleteNote();
    354             }
    355         }
    356         setResult(RESULT_CANCELED);
    357         finish();
    358     }
    359 
    360     /**
    361      * Take care of deleting a note.  Simply deletes the entry.
    362      */
    363     private final void deleteNote() {
    364         if (mCursor != null) {
    365             mCursor.close();
    366             mCursor = null;
    367             getContentResolver().delete(mUri, null, null);
    368             mText.setText("");
    369         }
    370     }
    371 }
    372