/* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * 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 net.micode.notes.ui; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Paint; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.model.WorkingNote; import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; // NoteEditActivity 是一个用于编辑便签的 Activity。 public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { // 内部类,用于持有头部视图的控件引用。 private class HeadViewHolder { public TextView tvModified; // 修改日期的 TextView。 public ImageView ivAlertIcon; // 提醒图标的 ImageView。 public TextView tvAlertDate; // 提醒日期的 TextView。 public ImageView ibSetBgColor; // 设置背景颜色的 ImageView。 } // 背景颜色选择按钮与颜色 ID 的映射。 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } // 背景颜色选择结果与按钮 ID 的映射。 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } // 字体大小选择按钮与大小 ID 的映射。 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } // 字体大小选择结果与按钮 ID 的映射。 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } // 日志标签。 private static final String TAG = "NoteEditActivity"; // 头部视图的 Holder。 private HeadViewHolder mNoteHeaderHolder; // 头部视图面板、背景颜色选择器和字体大小选择器。 private View mHeadViewPanel; private View mNoteBgColorSelector; private View mFontSizeSelector; // 编辑器和编辑器面板。 private EditText mNoteEditor; private View mNoteEditorPanel; // 正在编辑的便签。 private WorkingNote mWorkingNote; // SharedPreferences 和字体大小 ID。 private SharedPreferences mSharedPrefs; private int mFontSizeId; // SharedPreferences 中字体大小的键。 private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 快捷方式图标标题的最大长度。 private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 多选框标记。 public static final String TAG_CHECKED = String.valueOf('\u221A'); public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 编辑文本列表。 private LinearLayout mEditTextList; // 用户查询字符串和正则表达式模式。 private String mUserQuery; private Pattern mPattern; // onCreate 方法,初始化 Activity。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } initResources(); } // onRestoreInstanceState 方法,恢复 Activity 状态。 @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); return; } Log.d(TAG, "Restoring from killed activity"); } } // initActivityState 方法,初始化 Activity 状态。 private boolean initActivityState(Intent intent) { // 省略部分代码... } // onResume 方法,恢复 Activity 时调用。 @Override protected void onResume() { super.onResume(); initNoteScreen(); } // initNoteScreen 方法,初始化便签屏幕。 private void initNoteScreen() { // 省略部分代码... } // onNewIntent 方法,处理新的 Intent。 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } // onSaveInstanceState 方法,保存 Activity 状态。 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 省略部分代码... } // dispatchTouchEvent 方法,分发触摸事件。 @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 省略部分代码... } // inRangeOfView 方法,判断触摸事件是否在视图范围内。 private boolean inRangeOfView(View view, MotionEvent ev) { // 省略部分代码... } // initResources 方法,初始化资源。 private void initResources() { // 省略部分代码... } // onPause 方法,暂停 Activity 时调用。 @Override protected void onPause() { super.onPause(); if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); } // updateWidget 方法,更新小部件。 private void updateWidget() { // 省略部分代码... } } /** * 当点击某个视图时调用此方法。 * @param v 被点击的视图。 */ public void onClick(View v) { int id = v.getId(); // 获取被点击视图的ID。 // 如果被点击的视图是设置背景颜色的按钮,则显示背景颜色选择器。 if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(-View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果被点击的是背景颜色按钮。 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.GONE); mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 更改背景颜色ID。 mNoteBgColorSelector.setVisibility(View.GONE); } else if (sFontSizeBtnsMap.containsKey(id)) { // 如果被点击的是字体大小按钮。 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); mFontSizeId = sFontSizeBtnsMap.get(id); // 更改字体大小ID。 mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存新的字体大小偏好。 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); // 如果便签处于清单模式,更新清单视图;否则,更新文本外观。 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { getWorkingText(); switchToListMode(mWorkingNote.getContent()); } else { mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } mFontSizeSelector.setVisibility(View.GONE); } } /** * 当按下返回键时调用。 */ @Override public void onBackPressed() { if(clearSettingState()) { return; } saveNote(); super.onBackPressed(); } /** * 清除设置状态,例如隐藏背景颜色选择器和字体大小选择器。 * @return 如果设置状态被清除,则返回true,否则返回false。 */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { mFontSizeSelector.setVisibility(View.GONE); return true; } return false; } /** * 当背景颜色改变时调用。 */ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } /** * 准备选项菜单。 * @param menu 选项菜单。 * @return 如果菜单已准备,则返回true,否则返回false。 */ @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { return true; } clearSettingState(); menu.clear(); // 根据便签的文件夹ID决定加载哪个菜单。 if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } // 根据便签的属性更新菜单项。 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { menu.findItem(R.id.menu_delete_remind).setVisible(false); } return true; } /** * 处理选项菜单项的选择。 * @param item 被选择的菜单项。 * @return 如果项被处理,则返回true,否则返回false。 */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: createNewNote(); break; case R.id.menu_delete: // 显示删除便签的确认对话框。 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(getString(R.string.alert_message_delete_note)); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { deleteCurrentNote(); finish(); } }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.menu_font_size: mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: sendToDesktop(); break; case R.id.menu_alert: setReminder(); break; case R.id.menu_delete_remind: mWorkingNote.setAlertDate(0, false); break; default: break; } return true; } /** * 设置便签的提醒。 */ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { mWorkingNote.setAlertDate(date, true); } }); d.show(); } /** * 分享便签到支持{@link Intent#ACTION_SEND}动作和{text/plain}类型的应用。 * @param context 上下文。 * @param info 要分享的便签内容。 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); context.startActivity(intent); } /** * 创建一个新的便签。 */ private void createNewNote() { // 在创建新便签之前保存当前正在编辑的便签。 saveNote(); finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); startActivity(intent); } /** * 删除当前便签。 */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); if (id != Notes.ID_ROOT_FOLDER) { ids.add(id); } else { Log.d(TAG, "错误的便签ID,不应该发生"); } if (!isSyncMode()) { if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { Log.e(TAG, "删除便签错误"); } } else { if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { Log.e(TAG, "将便签移动到废纸篓文件夹错误,不应该发生"); } } } mWorkingNote.markDeleted(true); } /** * 检查应用是否处于同步模式。 * @return 如果处于同步模式,则返回true,否则返回false。 */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } /** * 当时钟提醒改变时调用。 * @param date 新的提醒日期。 * @param set 如果设置了提醒,则为true;如果取消了提醒,则为false。 */ public void onClockAlertChanged(long date, boolean set) { if (!mWorkingNote.existInDatabase()) { saveNote(); } if (mWorkingNote.getNoteId() > 0) { Intent intent = new Intent(this, AlarmReceiver.class); intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); if(!set) { alarmManager.cancel(pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { Log.e(TAG, "时钟提醒设置错误"); showToast(R.string.error_note_empty_for_clock); } } /** * 当小部件改变时调用。 */ public void onWidgetChanged() { updateWidget(); } /** * 处理在清单中删除文本项。 * @param index 要删除项的索引。 * @param text 项的文本。 */