package com.applang.tagesberichte; import static com.applang.Util1.*; import com.applang.berichtsheft.R; import java.util.LinkedList; import android.app.Activity; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.text.Editable; import android.text.Selection; import android.text.TextWatcher; import android.text.style.UnderlineSpan; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.EditText; import android.widget.Filter; import android.widget.ImageButton; import android.widget.TextView; public class Helper { /* * THIS CLASS IS PROVIDED TO THE PUBLIC DOMAIN FOR FREE WITHOUT ANY * RESTRICTIONS OR ANY WARRANTY. * * A generic undo/redo implementation for TextViews. */ public static class TextViewUndoRedo { /** * Is undo/redo being performed? This member signals if an undo/redo * operation is currently being performed. Changes in the text during * undo/redo are not recorded because it would mess up the undo history. */ private boolean mIsUndoOrRedo = false; /** * The edit history. */ private EditHistory mEditHistory; /** * The change listener. */ private EditTextChangeListener mChangeListener; /** * The edit text. */ private TextView mTextView; // =================================================================== // /** * Create a new TextViewUndoRedo and attach it to the specified TextView. * * @param textView * The text view for which the undo/redo is implemented. */ public TextViewUndoRedo(TextView textView) { mTextView = textView; mEditHistory = new EditHistory(); mChangeListener = new EditTextChangeListener(); mTextView.addTextChangedListener(mChangeListener); } // =================================================================== // /** * Disconnect this undo/redo from the text view. */ public void disconnect() { mTextView.removeTextChangedListener(mChangeListener); } /** * Set the maximum history size. If size is negative, then history size is * only limited by the device memory. */ public void setMaxHistorySize(int maxHistorySize) { mEditHistory.setMaxHistorySize(maxHistorySize); } /** * Clear history. */ public void clearHistory() { mEditHistory.clear(); } /** * Can undo be performed? */ public boolean canUndo() { return (mEditHistory.mmPosition > 0); } /** * Perform undo. */ public void undo() { EditItem edit = mEditHistory.getPrevious(); if (edit == null) { return; } Editable text = (Editable) mTextView.getText(); int start = edit.mmStart; int end = start + (edit.mmAfter != null ? edit.mmAfter.length() : 0); mIsUndoOrRedo = true; text.replace(start, end, edit.mmBefore); mIsUndoOrRedo = false; // This will get rid of underlines inserted when editor tries to come // up with a suggestion. for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) { text.removeSpan(o); } Selection.setSelection(text, edit.mmBefore == null ? start : (start + edit.mmBefore.length())); } /** * Can redo be performed? */ public boolean canRedo() { return (mEditHistory.mmPosition < mEditHistory.mmHistory.size()); } /** * Perform redo. */ public void redo() { EditItem edit = mEditHistory.getNext(); if (edit == null) { return; } Editable text = (Editable) mTextView.getText(); int start = edit.mmStart; int end = start + (edit.mmBefore != null ? edit.mmBefore.length() : 0); mIsUndoOrRedo = true; text.replace(start, end, edit.mmAfter); mIsUndoOrRedo = false; // This will get rid of underlines inserted when editor tries to come // up with a suggestion. for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) { text.removeSpan(o); } Selection.setSelection(text, edit.mmAfter == null ? start : (start + edit.mmAfter.length())); } /** * Store preferences. */ public void storePersistentState(Editor editor, String prefix) { // Store hash code of text in the editor so that we can check if the // editor contents has changed. editor.putString(prefix + ".hash", String.valueOf(mTextView.getText().toString().hashCode())); editor.putInt(prefix + ".maxSize", mEditHistory.mmMaxHistorySize); editor.putInt(prefix + ".position", mEditHistory.mmPosition); editor.putInt(prefix + ".size", mEditHistory.mmHistory.size()); int i = 0; for (EditItem ei : mEditHistory.mmHistory) { String pre = prefix + "." + i; editor.putInt(pre + ".start", ei.mmStart); editor.putString(pre + ".before", ei.mmBefore.toString()); editor.putString(pre + ".after", ei.mmAfter.toString()); i++; } } /** * Restore preferences. * * @param prefix * The preference key prefix used when state was stored. * @return did restore succeed? If this is false, the undo history will be * empty. */ public boolean restorePersistentState(SharedPreferences sp, String prefix) throws IllegalStateException { boolean ok = doRestorePersistentState(sp, prefix); if (!ok) { mEditHistory.clear(); } return ok; } private boolean doRestorePersistentState(SharedPreferences sp, String prefix) { String hash = sp.getString(prefix + ".hash", null); if (hash == null) { // No state to be restored. return true; } if (Integer.valueOf(hash) != mTextView.getText().toString().hashCode()) { return false; } mEditHistory.clear(); mEditHistory.mmMaxHistorySize = sp.getInt(prefix + ".maxSize", -1); int count = sp.getInt(prefix + ".size", -1); if (count == -1) { return false; } for (int i = 0; i < count; i++) { String pre = prefix + "." + i; int start = sp.getInt(pre + ".start", -1); String before = sp.getString(pre + ".before", null); String after = sp.getString(pre + ".after", null); if (start == -1 || before == null || after == null) { return false; } mEditHistory.add(new EditItem(start, before, after)); } mEditHistory.mmPosition = sp.getInt(prefix + ".position", -1); if (mEditHistory.mmPosition == -1) { return false; } return true; } // =================================================================== // /** * Keeps track of all the edit history of a text. */ private final class EditHistory { /** * The position from which an EditItem will be retrieved when getNext() * is called. If getPrevious() has not been called, this has the same * value as mmHistory.size(). */ private int mmPosition = 0; /** * Maximum undo history size. */ private int mmMaxHistorySize = -1; /** * The list of edits in chronological order. */ private final LinkedList<EditItem> mmHistory = new LinkedList<EditItem>(); /** * Clear history. */ private void clear() { mmPosition = 0; mmHistory.clear(); } /** * Adds a new edit operation to the history at the current position. If * executed after a call to getPrevious() removes all the future history * (elements with positions >= current history position). */ private void add(EditItem item) { while (mmHistory.size() > mmPosition) { mmHistory.removeLast(); } mmHistory.add(item); mmPosition++; if (mmMaxHistorySize >= 0) { trimHistory(); } } /** * Set the maximum history size. If size is negative, then history size * is only limited by the device memory. */ private void setMaxHistorySize(int maxHistorySize) { mmMaxHistorySize = maxHistorySize; if (mmMaxHistorySize >= 0) { trimHistory(); } } /** * Trim history when it exceeds max history size. */ private void trimHistory() { while (mmHistory.size() > mmMaxHistorySize) { mmHistory.removeFirst(); mmPosition--; } if (mmPosition < 0) { mmPosition = 0; } } /** * Traverses the history backward by one position, returns and item at * that position. */ private EditItem getPrevious() { if (mmPosition == 0) { return null; } mmPosition--; return mmHistory.get(mmPosition); } /** * Traverses the history forward by one position, returns and item at * that position. */ private EditItem getNext() { if (mmPosition >= mmHistory.size()) { return null; } EditItem item = mmHistory.get(mmPosition); mmPosition++; return item; } } /** * Represents the changes performed by a single edit operation. */ private final class EditItem { private final int mmStart; private final CharSequence mmBefore; private final CharSequence mmAfter; /** * Constructs EditItem of a modification that was applied at position * start and replaced CharSequence before with CharSequence after. */ public EditItem(int start, CharSequence before, CharSequence after) { mmStart = start; mmBefore = before; mmAfter = after; } } /** * Class that listens to changes in the text. */ private final class EditTextChangeListener implements TextWatcher { /** * The text that will be removed by the change event. */ private CharSequence mBeforeChange; /** * The text that was inserted by the change event. */ private CharSequence mAfterChange; public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (mIsUndoOrRedo) { return; } mBeforeChange = s.subSequence(start, start + count); } public void onTextChanged(CharSequence s, int start, int before, int count) { if (mIsUndoOrRedo) { return; } mAfterChange = s.subSequence(start, start + count); mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange)); } public void afterTextChanged(Editable s) { } } } public static void listViewSearch(boolean init, Activity activity, final Filter filter) { if (init) { LayoutInflater inflater = LayoutInflater.from(activity); View view = inflater.inflate(R.layout.search, null); ViewGroup contentView = (ViewGroup) getContentView(activity); contentView.addView(view, 0); final EditText searchEdit = (EditText) activity.findViewById(android.R.id.edit); searchEdit.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { filter.filter(s); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { } }); final ViewGroup searchGroup = (ViewGroup) activity.findViewById(R.id.search); searchGroup.setVisibility(View.GONE); ImageButton btn = (ImageButton) activity.findViewById(R.id.button1); if (btn != null) btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { searchEdit.setText(""); searchGroup.setVisibility(View.GONE); } }); // alert(this, viewHierarchy(this)); } else { ViewGroup searchGroup = (ViewGroup) activity.findViewById(R.id.search); if (searchGroup.getVisibility() == View.VISIBLE) searchGroup.setVisibility(View.GONE); else { searchGroup.setVisibility(View.VISIBLE); activity.findViewById(android.R.id.edit).requestFocus(); } } } }