diff options
Diffstat (limited to 'srcs/juloo.keyboard2')
| -rw-r--r-- | srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java | 22 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/ClipboardHistoryService.java | 180 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/ClipboardHistoryView.java | 125 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/ClipboardPinView.java | 139 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/Config.java | 2 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/KeyEventHandler.java | 10 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/KeyValue.java | 4 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/Keyboard2.java | 10 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/NonScrollListView.java | 38 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java | 3 |
10 files changed, 532 insertions, 1 deletions
diff --git a/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java b/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java new file mode 100644 index 0000000..9842058 --- /dev/null +++ b/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java @@ -0,0 +1,22 @@ +package juloo.keyboard2; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +final class ClipboardHistoryCheckBox extends CheckBox + implements CompoundButton.OnCheckedChangeListener +{ + public ClipboardHistoryCheckBox(Context ctx, AttributeSet attrs) + { + super(ctx, attrs); + setOnCheckedChangeListener(this); + } + + @Override + public void onCheckedChanged(CompoundButton _v, boolean isChecked) + { + ClipboardHistoryService.set_history_enabled(isChecked); + } +} diff --git a/srcs/juloo.keyboard2/ClipboardHistoryService.java b/srcs/juloo.keyboard2/ClipboardHistoryService.java new file mode 100644 index 0000000..e3f01ba --- /dev/null +++ b/srcs/juloo.keyboard2/ClipboardHistoryService.java @@ -0,0 +1,180 @@ +package juloo.keyboard2; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build.VERSION; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public final class ClipboardHistoryService +{ + /** Start the service on startup and start listening to clipboard changes. */ + public static void on_startup(Context ctx, ClipboardPasteCallback cb) + { + get_service(ctx); + _paste_callback = cb; + } + + /** Start the service if it hasn't been started before. Returns [null] if the + feature is unsupported. */ + public static ClipboardHistoryService get_service(Context ctx) + { + if (VERSION.SDK_INT <= 11) + return null; + if (_service == null) + _service = new ClipboardHistoryService(ctx); + return _service; + } + + public static void set_history_enabled(boolean e) + { + if (_service == null) + return; + Config.globalPrefs().edit() + .putBoolean("clipboard_history_enabled", e) + .commit(); + if (e) + _service.add_current_clip(); + else + _service.clear_history(); + } + + /** Send the given string to the editor. */ + public static void paste(String clip) + { + if (_paste_callback != null) + _paste_callback.paste_from_clipboard_pane(clip); + } + + /** The maximum size limits the amount of user data stored in memory but also + gives a sense to the user that the history is not persisted and can be + forgotten as soon as the app stops. */ + public static final int MAX_HISTORY_SIZE = 3; + /** Time in ms until history entries expire. */ + public static final long HISTORY_TTL_MS = 5 * 60 * 1000; + + static ClipboardHistoryService _service = null; + static ClipboardPasteCallback _paste_callback = null; + + ClipboardManager _cm; + List<HistoryEntry> _history; + OnClipboardHistoryChange _listener = null; + + ClipboardHistoryService(Context ctx) + { + _history = new ArrayList<HistoryEntry>(); + _cm = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE); + _cm.addPrimaryClipChangedListener(this.new SystemListener()); + } + + public List<String> clear_expired_and_get_history() + { + long now_ms = System.currentTimeMillis(); + List<String> dst = new ArrayList<String>(); + Iterator<HistoryEntry> it = _history.iterator(); + while (it.hasNext()) + { + HistoryEntry ent = it.next(); + if (ent.expiry_timestamp <= now_ms) + it.remove(); + else + dst.add(ent.content); + } + return dst; + } + + /** This will call [on_clipboard_history_change]. */ + public void remove_history_entry(String clip) + { + int last_pos = _history.size() - 1; + for (int pos = last_pos; pos >= 0; pos--) + { + if (!_history.get(pos).content.equals(clip)) + continue; + // Removing the current clipboard, clear the system clipboard. + if (pos == last_pos) + { + if (VERSION.SDK_INT >= 28) + _cm.clearPrimaryClip(); + else + _cm.setText(""); + } + _history.remove(pos); + if (_listener != null) + _listener.on_clipboard_history_change(); + } + } + + /** Add clipboard entries to the history, skipping consecutive duplicates and + empty strings. */ + public void add_clip(String clip) + { + if (!Config.globalConfig().clipboard_history_enabled) + return; + int size = _history.size(); + if (clip.equals("") || (size > 0 && _history.get(size - 1).content.equals(clip))) + return; + if (size >= MAX_HISTORY_SIZE) + _history.remove(0); + _history.add(new HistoryEntry(clip)); + if (_listener != null) + _listener.on_clipboard_history_change(); + } + + public void clear_history() + { + _history.clear(); + if (_listener != null) + _listener.on_clipboard_history_change(); + } + + public void set_on_clipboard_history_change(OnClipboardHistoryChange l) { _listener = l; } + + public static interface OnClipboardHistoryChange + { + public void on_clipboard_history_change(); + } + + /** Add what is currently in the system clipboard into the history. */ + void add_current_clip() + { + ClipData clip = _cm.getPrimaryClip(); + if (clip == null) + return; + int count = clip.getItemCount(); + for (int i = 0; i < count; i++) + add_clip(clip.getItemAt(i).getText().toString()); + } + + final class SystemListener implements ClipboardManager.OnPrimaryClipChangedListener + { + public SystemListener() {} + + @Override + public void onPrimaryClipChanged() + { + add_current_clip(); + } + } + + static final class HistoryEntry + { + public final String content; + + /** Time at which the entry expires. */ + public final long expiry_timestamp; + + public HistoryEntry(String c) + { + content = c; + expiry_timestamp = System.currentTimeMillis() + HISTORY_TTL_MS; + } + } + + public interface ClipboardPasteCallback + { + public void paste_from_clipboard_pane(String content); + } +} diff --git a/srcs/juloo.keyboard2/ClipboardHistoryView.java b/srcs/juloo.keyboard2/ClipboardHistoryView.java new file mode 100644 index 0000000..b4eb6fe --- /dev/null +++ b/srcs/juloo.keyboard2/ClipboardHistoryView.java @@ -0,0 +1,125 @@ +package juloo.keyboard2; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class ClipboardHistoryView extends NonScrollListView + implements ClipboardHistoryService.OnClipboardHistoryChange +{ + List<String> _history; + ClipboardHistoryService _service; + ClipboardEntriesAdapter _adapter; + + public ClipboardHistoryView(Context ctx, AttributeSet attrs) + { + super(ctx, attrs); + _history = Collections.EMPTY_LIST; + _adapter = this.new ClipboardEntriesAdapter(); + _service = ClipboardHistoryService.get_service(ctx); + if (_service != null) + { + _service.set_on_clipboard_history_change(this); + _history = _service.clear_expired_and_get_history(); + } + setAdapter(_adapter); + } + + /** The history entry at index [pos] is removed from the history and added to + the list of pinned clipboards. */ + public void pin_entry(int pos) + { + ClipboardPinView v = (ClipboardPinView)((ViewGroup)getParent().getParent()).findViewById(R.id.clipboard_pin_view); + String clip = _history.get(pos); + v.add_entry(clip); + _service.remove_history_entry(clip); + } + + /** Send the specified entry to the editor. */ + public void paste_entry(int pos) + { + ClipboardHistoryService.paste(_history.get(pos)); + } + + @Override + public void on_clipboard_history_change() + { + update_data(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) + { + if (visibility == View.VISIBLE) + update_data(); + } + + void update_data() + { + _history = _service.clear_expired_and_get_history(); + _adapter.notifyDataSetChanged(); + invalidate(); + } + + class ClipboardEntriesAdapter extends BaseAdapter + { + public ClipboardEntriesAdapter() {} + + @Override + public int getCount() { return _history.size(); } + @Override + public Object getItem(int pos) { return _history.get(pos); } + @Override + public long getItemId(int pos) { return _history.get(pos).hashCode(); } + + @Override + public View getView(final int pos, View v, ViewGroup _parent) + { + if (v == null) + v = View.inflate(getContext(), R.layout.clipboard_history_entry, null); + ((TextView)v.findViewById(R.id.clipboard_entry_text)) + .setText(_history.get(pos)); + v.findViewById(R.id.clipboard_entry_addpin).setOnClickListener( + new View.OnClickListener() + { + @Override + public void onClick(View v) { pin_entry(pos); } + }); + v.findViewById(R.id.clipboard_entry_paste).setOnClickListener( + new View.OnClickListener() + { + @Override + public void onClick(View v) { paste_entry(pos); } + }); + // v.findViewById(R.id.clipboard_entry_removehist).setOnClickListener( + // new View.OnClickListener() + // { + // @Override + // public void onClick(View v) + // { + // AlertDialog d = new AlertDialog.Builder(getContext()) + // .setTitle(R.string.clipboard_remove_confirm) + // .setPositiveButton(R.string.clipboard_remove_confirmed, + // new DialogInterface.OnClickListener(){ + // public void onClick(DialogInterface _dialog, int _which) + // { + // _service.remove_history_entry(_history.get(pos)); + // } + // }) + // .setNegativeButton(android.R.string.cancel, null) + // .create(); + // Utils.show_dialog_on_ime(d, v.getWindowToken()); + // } + // }); + return v; + } + } +} diff --git a/srcs/juloo.keyboard2/ClipboardPinView.java b/srcs/juloo.keyboard2/ClipboardPinView.java new file mode 100644 index 0000000..26833d6 --- /dev/null +++ b/srcs/juloo.keyboard2/ClipboardPinView.java @@ -0,0 +1,139 @@ +package juloo.keyboard2; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.json.JSONArray; +import org.json.JSONException; + +public final class ClipboardPinView extends NonScrollListView +{ + /** Preference file name that store pinned clipboards. */ + static final String PERSIST_FILE_NAME = "clipboards"; + /** Preference name for pinned clipboards. */ + static final String PERSIST_PREF = "pinned"; + + List<String> _entries; + ClipboardPinEntriesAdapter _adapter; + SharedPreferences _persist_store; + + public ClipboardPinView(Context ctx, AttributeSet attrs) + { + super(ctx, attrs); + _entries = new ArrayList<String>(); + _persist_store = + ctx.getSharedPreferences("pinned_clipboards", Context.MODE_PRIVATE); + load_from_prefs(_persist_store, _entries); + _adapter = this.new ClipboardPinEntriesAdapter(); + setAdapter(_adapter); + } + + /** Pin a clipboard and persist the change. */ + public void add_entry(String text) + { + _entries.add(text); + _adapter.notifyDataSetChanged(); + persist(); + invalidate(); + } + + /** Remove the entry at index [pos] and persist the change. */ + public void remove_entry(int pos) + { + if (pos < 0 || pos >= _entries.size()) + return; + _entries.remove(pos); + _adapter.notifyDataSetChanged(); + persist(); + invalidate(); + } + + /** Send the specified entry to the editor. */ + public void paste_entry(int pos) + { + ClipboardHistoryService.paste(_entries.get(pos)); + } + + void persist() { save_to_prefs(_persist_store, _entries); } + + static void load_from_prefs(SharedPreferences store, List<String> dst) + { + String arr_s = store.getString(PERSIST_PREF, null); + if (arr_s == null) + return; + try + { + JSONArray arr = new JSONArray(arr_s); + for (int i = 0; i < arr.length(); i++) + dst.add(arr.getString(i)); + } + catch (JSONException _e) {} + } + + static void save_to_prefs(SharedPreferences store, List<String> entries) + { + JSONArray arr = new JSONArray(); + for (int i = 0; i < entries.size(); i++) + arr.put(entries.get(i)); + store.edit() + .putString(PERSIST_PREF, arr.toString()) + .commit(); + } + + class ClipboardPinEntriesAdapter extends BaseAdapter + { + public ClipboardPinEntriesAdapter() {} + + @Override + public int getCount() { return _entries.size(); } + @Override + public Object getItem(int pos) { return _entries.get(pos); } + @Override + public long getItemId(int pos) { return _entries.get(pos).hashCode(); } + + @Override + public View getView(final int pos, View v, ViewGroup _parent) + { + if (v == null) + v = View.inflate(getContext(), R.layout.clipboard_pin_entry, null); + ((TextView)v.findViewById(R.id.clipboard_pin_text)) + .setText(_entries.get(pos)); + v.findViewById(R.id.clipboard_pin_paste).setOnClickListener( + new View.OnClickListener() + { + @Override + public void onClick(View v) { paste_entry(pos); } + }); + v.findViewById(R.id.clipboard_pin_remove).setOnClickListener( + new View.OnClickListener() + { + @Override + public void onClick(View v) + { + AlertDialog d = new AlertDialog.Builder(getContext()) + .setTitle(R.string.clipboard_remove_confirm) + .setPositiveButton(R.string.clipboard_remove_confirmed, + new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface _dialog, int _which) + { + remove_entry(pos); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + Utils.show_dialog_on_ime(d, v.getWindowToken()); + } + }); + return v; + } + } +} diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java index 7570728..061183c 100644 --- a/srcs/juloo.keyboard2/Config.java +++ b/srcs/juloo.keyboard2/Config.java @@ -67,6 +67,7 @@ public final class Config public boolean pin_entry_enabled; public boolean borderConfig; public int circle_sensitivity; + public boolean clipboard_history_enabled; // Dynamically set public boolean shouldOfferVoiceTyping; @@ -185,6 +186,7 @@ public final class Config current_layout_portrait = _prefs.getInt("current_layout_portrait", 0); current_layout_landscape = _prefs.getInt("current_layout_landscape", 0); circle_sensitivity = Integer.valueOf(_prefs.getString("circle_sensitivity", "2")); + clipboard_history_enabled = _prefs.getBoolean("clipboard_history_enabled", false); } public int get_current_layout() diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index b6225f1..087ac5b 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -10,7 +10,9 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import java.util.Iterator; -public final class KeyEventHandler implements Config.IKeyEventHandler +public final class KeyEventHandler + implements Config.IKeyEventHandler, + ClipboardHistoryService.ClipboardPasteCallback { IReceiver _recv; Autocapitalisation _autocap; @@ -105,6 +107,12 @@ public final class KeyEventHandler implements Config.IKeyEventHandler update_meta_state(mods); } + @Override + public void paste_from_clipboard_pane(String content) + { + send_text(content); + } + /** Update [_mods] to be consistent with the [mods], sending key events if needed. */ void update_meta_state(Pointers.Modifiers mods) diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java index 1635bab..31a92f2 100644 --- a/srcs/juloo.keyboard2/KeyValue.java +++ b/srcs/juloo.keyboard2/KeyValue.java @@ -12,6 +12,8 @@ public final class KeyValue implements Comparable<KeyValue> SWITCH_NUMERIC, SWITCH_EMOJI, SWITCH_BACK_EMOJI, + SWITCH_CLIPBOARD, + SWITCH_BACK_CLIPBOARD, CHANGE_METHOD_PICKER, CHANGE_METHOD_AUTO, ACTION, @@ -460,6 +462,8 @@ public final class KeyValue implements Comparable<KeyValue> case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT); case "switch_emoji": return eventKey(0xE001, Event.SWITCH_EMOJI, FLAG_SMALLER_FONT); case "switch_back_emoji": return eventKey("ABC", Event.SWITCH_BACK_EMOJI, 0); + case "switch_clipboard": return eventKey(0xE017, Event.SWITCH_CLIPBOARD, 0); + case "switch_back_clipboard": return eventKey("ABC", Event.SWITCH_BACK_CLIPBOARD, 0); case "switch_forward": return eventKey(0xE013, Event.SWITCH_FORWARD, FLAG_SMALLER_FONT); case "switch_backward": return eventKey(0xE014, Event.SWITCH_BACKWARD, FLAG_SMALLER_FONT); case "switch_greekmath": return eventKey("πλ∇¬", Event.SWITCH_GREEKMATH, FLAG_SMALLER_FONT); diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index c332375..0c82aaf 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -36,6 +36,7 @@ public class Keyboard2 extends InputMethodService /** Layout associated with the currently selected locale. Not 'null'. */ private KeyboardData _localeTextLayout; private ViewGroup _emojiPane = null; + private ViewGroup _clipboard_pane = null; public int actionId; // Action performed by the Action key. private Config _config; @@ -113,6 +114,7 @@ public class Keyboard2 extends InputMethodService _keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard); _keyboardView.reset(); Logs.set_debug_logs(getResources().getBoolean(R.bool.debug_logs)); + ClipboardHistoryService.on_startup(this, _keyeventhandler); } private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm) @@ -223,6 +225,7 @@ public class Keyboard2 extends InputMethodService { _keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard); _emojiPane = null; + _clipboard_pane = null; setInputView(_keyboardView); } _keyboardView.reset(); @@ -384,7 +387,14 @@ public class Keyboard2 extends InputMethodService setInputView(_emojiPane); break; + case SWITCH_CLIPBOARD: + if (_clipboard_pane == null) + _clipboard_pane = (ViewGroup)inflate_view(R.layout.clipboard_pane); + setInputView(_clipboard_pane); + break; + case SWITCH_BACK_EMOJI: + case SWITCH_BACK_CLIPBOARD: setInputView(_keyboardView); break; diff --git a/srcs/juloo.keyboard2/NonScrollListView.java b/srcs/juloo.keyboard2/NonScrollListView.java new file mode 100644 index 0000000..32ef744 --- /dev/null +++ b/srcs/juloo.keyboard2/NonScrollListView.java @@ -0,0 +1,38 @@ +package juloo.keyboard2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.widget.ListView; + +/** A non-scrollable list view that can be embedded in a bigger ScrollView. + Credits to Dedaniya HirenKumar in + https://stackoverflow.com/questions/18813296/non-scrollable-listview-inside-scrollview */ +public class NonScrollListView extends ListView +{ + public NonScrollListView(Context context) + { + super(context); + } + + public NonScrollListView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public NonScrollListView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec( + Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom); + ViewGroup.LayoutParams params = getLayoutParams(); + params.height = getMeasuredHeight(); + } +} diff --git a/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java index adf66ec..22c6bd9 100644 --- a/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java +++ b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java @@ -24,6 +24,7 @@ public class ExtraKeysPreference extends PreferenceCategory "meta", "compose", "voice_typing", + "switch_clipboard", "accent_aigu", "accent_grave", "accent_double_aigu", @@ -79,6 +80,7 @@ public class ExtraKeysPreference extends PreferenceCategory { case "voice_typing": case "change_method": + case "switch_clipboard": case "compose": case "tab": case "esc": @@ -117,6 +119,7 @@ public class ExtraKeysPreference extends PreferenceCategory case "voice_typing": id = R.string.key_descr_voice_typing; break; case "ª": id = R.string.key_descr_ª; break; case "º": id = R.string.key_descr_º; break; + case "switch_clipboard": id = R.string.key_descr_clipboard; break; } if (id == 0) return null; |
