From 0e9405711142f3a02cf53333bdc100ca20cd23e6 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Fri, 6 Dec 2024 07:05:17 -0800 Subject: [PATCH] Select lines (PR #2443 by @harshad1) --- .../markor/format/ActionButtonBase.java | 7 +- .../markor/frontend/NewFileDialog.java | 6 ++ .../frontend/textview/HighlightingEditor.java | 45 ++++++++++ .../filebrowser/GsFileBrowserDialog.java | 1 - .../filebrowser/GsFileBrowserFragment.java | 1 - .../filebrowser/GsFileBrowserListAdapter.java | 89 +++++++++---------- app/src/main/res/layout/new_file_dialog.xml | 2 +- .../res/values/string-not_translatable.xml | 1 + 8 files changed, 97 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java index d6927d9e29..48c22775ba 100644 --- a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java +++ b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java @@ -251,11 +251,6 @@ private List loadActionPreference(final String suffix) { public List getActionOrder() { final Set order = new LinkedHashSet<>(loadActionPreference(ORDER_SUFFIX)); - // Handle the case where order was stored without suffix. i.e. before this release. - if (order.isEmpty()) { - order.addAll(loadActionPreference("")); - } - final Set defined = new LinkedHashSet<>(getActiveActionKeys()); final Set disabled = new LinkedHashSet<>(getDisabledActions()); @@ -302,7 +297,7 @@ public void recreateActionButtons(final ViewGroup barLayout, final ActionItem.Di @SuppressLint("ClickableViewAccessibility") private void setupRepeat(final View btn) { // Velocity and acceleration parameters - final int INITIAL_DELAY = 400, DELTA_DELAY = 50, MIN_DELAY = 100; + final int INITIAL_DELAY = 300, DELTA_DELAY = 100, MIN_DELAY = 100; final Handler handler = new Handler(); final Runnable repeater = new Runnable() { diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index 2821db277d..f5da1cc7cb 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -162,6 +162,12 @@ private AlertDialog makeDialog(final File basedir, final boolean allowCreateDir, templateAdapter.addAll(GsCollectionUtils.map(templates, p -> p.first)); templateSpinner.setAdapter(templateAdapter); + templateSpinner.setOnItemSelectedListener(new GsAndroidSpinnerOnItemSelectedAdapter(pos -> { + final String template = templateAdapter.getItem(pos); + final String fmt = appSettings.getTemplateTitleFormat(template); + formatEdit.setText(fmt); + })); + // Setup type / format spinner and action // ----------------------------------------------------------------------------------------- final ArrayAdapter typeAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_dropdown_item); diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java index 4406270109..1202fc5e1a 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java +++ b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java @@ -18,8 +18,11 @@ import android.text.Layout; import android.text.TextWatcher; import android.util.AttributeSet; +import android.view.ActionMode; import android.util.Log; import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; @@ -30,6 +33,7 @@ import androidx.appcompat.widget.AppCompatEditText; import net.gsantner.markor.ApplicationObject; +import net.gsantner.markor.R; import net.gsantner.markor.activity.MainActivity; import net.gsantner.markor.model.AppSettings; import net.gsantner.opoc.format.GsTextUtils; @@ -114,6 +118,9 @@ public void afterTextChanged(final Editable s) { // Fix for Android 12 perf issues - https://github.com/gsantner/markor/discussions/1794 setEmojiCompatEnabled(false); + + // Custom options + setupCustomOptions(); } @Override @@ -522,6 +529,11 @@ public void withAutoFormatDisabled(final GsCallback.a0 callback) { // Utility functions for interaction // --------------------------------------------------------------------------------------------- + public void selectLines() { + final int[] sel = TextViewUtils.getLineSelection(this); + setSelection(sel[0], sel[1]); + } + public void simulateKeyPress(int keyEvent_KEYCODE_SOMETHING) { dispatchKeyEvent(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyEvent_KEYCODE_SOMETHING, 0)); dispatchKeyEvent(new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyEvent_KEYCODE_SOMETHING, 0)); @@ -742,4 +754,37 @@ public void reset() { _maxNumberDigits = 0; } } + + private void setupCustomOptions() { + setCustomSelectionActionModeCallback(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Add custom items programmatically + menu.add(0, R.string.option_select_lines, 0, "☰"); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Modify menu items here if necessary + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.string.option_select_lines: + HighlightingEditor.this.selectLines(); + return true; + default: + return false; + } + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Cleanup if needed + } + }); + } } \ No newline at end of file diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserDialog.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserDialog.java index b0609cbd72..6ba6c1cd1e 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserDialog.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserDialog.java @@ -173,7 +173,6 @@ public void onViewCreated(final View root, final @Nullable Bundle savedInstanceS _filesystemViewerAdapter = new GsFileBrowserListAdapter(_dopt, activity); _recyclerList.setAdapter(_filesystemViewerAdapter); - _filesystemViewerAdapter.getFilter().filter(""); onFsViewerDoUiUpdate(_filesystemViewerAdapter); // Setup callbacks diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java index 58ef93e0d7..f0bb0a4f8c 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java @@ -124,7 +124,6 @@ public void onViewCreated(@NonNull View root, @Nullable Bundle savedInstanceStat _filesystemViewerAdapter = new GsFileBrowserListAdapter(_dopt, context); _recyclerList.setAdapter(_filesystemViewerAdapter); - _filesystemViewerAdapter.getFilter().filter(""); onFsViewerDoUiUpdate(_filesystemViewerAdapter); _swipe.setOnRefreshListener(() -> { diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java index 061548a7be..1dee2e5ae8 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java @@ -89,7 +89,7 @@ public class GsFileBrowserListAdapter extends RecyclerView.Adapter _virtualMapping; @@ -139,6 +139,7 @@ public GsFileBrowserListAdapter(GsFileBrowserOptions.Options options, Context co _virtualMapping = Collections.unmodifiableMap(getVirtualFolders()); _reverseVirtualMapping = Collections.unmodifiableMap(GsCollectionUtils.reverse(_virtualMapping)); loadFolder(_dopt.startFolder != null ? _dopt.startFolder : _dopt.rootFolder, null); + _filter = new StringFilter(this); } public Map getVirtualFolders() { @@ -367,9 +368,6 @@ public int getItemCount() { @Override public Filter getFilter() { - if (_filter == null) { - _filter = new StringFilter(this, _adapterData); - } return _filter; } @@ -600,7 +598,7 @@ public void showFile(final File file) { return; } - if (getFilePosition(file) < 0) { + if (!_adapterDataFiltered.contains(file)) { final File dir = file.getParentFile(); if (dir != null) { loadFolder(dir, file); @@ -620,13 +618,19 @@ public void onLayoutChange(View v, int l, int t, int r, int b, int ol, int ot, i }); } + private void postScrollToAndFlash(final File file) { + if (_recyclerView != null && file != null) { + _recyclerView.post(() -> scrollToAndFlash(file)); + } + } + /** * Scroll to a file in current folder and flash * * @param file File to blink */ public boolean scrollToAndFlash(final File file) { - final int pos = getFilePosition(file); + final int pos = _adapterDataFiltered.indexOf(file); if (pos >= 0 && _layoutManager != null) { _layoutManager.scrollToPosition(pos); _recyclerView.post(() -> @@ -641,19 +645,6 @@ public boolean scrollToAndFlash(final File file) { return false; } - // Get the position of a file in the current view - // -1 if file is not a child of the current directory - public int getFilePosition(final File file) { - if (file != null) { - for (int i = 0; i < _adapterDataFiltered.size(); i++) { - if (_adapterDataFiltered.get(i).equals(file)) { - return i; - } - } - } - return -1; - } - private static final ExecutorService executorService = new ThreadPoolExecutor(0, 3, 60, TimeUnit.SECONDS, new SynchronousQueue<>()); private void loadFolder(final File folder, final File show) { @@ -696,6 +687,11 @@ private void loadFolder(final File folder, final File show) { // This function is not called on the main thread, so post to the UI thread private synchronized void _loadFolder(final @NonNull File folder, final @Nullable File toShow) { + + if (_recyclerView == null) { + return; + } + final boolean folderChanged = !folder.equals(_currentFolder); final List newData = new ArrayList<>(); @@ -717,7 +713,6 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl newData.add(new File(folder, "0")); } - if (folder.equals(VIRTUAL_STORAGE_RECENTS)) { newData.addAll(_dopt.recentFiles); } else if (folder.equals(VIRTUAL_STORAGE_POPULAR)) { @@ -756,16 +751,17 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl } } - if (_recyclerView == null) { - //noinspection UnnecessaryReturnStatement - return; - } else if (folderChanged || modSumChanged || !newData.equals(_adapterData)) { + if (folderChanged || modSumChanged || !newData.equals(_adapterData)) { + final ArrayList filteredData = new ArrayList<>(); + _filter._filter(newData, filteredData); + _recyclerView.post(() -> { // Modify all these values in the UI thread _adapterData.clear(); _adapterData.addAll(newData); + _adapterDataFiltered.clear(); + _adapterDataFiltered.addAll(filteredData); _currentSelection.retainAll(_adapterData); - _filter.filter(_filter._lastFilter); _currentFolder = folder; _prevModSum = modSum; @@ -782,18 +778,18 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl _layoutManager.onRestoreInstanceState(_folderScrollMap.remove(_currentFolder)); } - _recyclerView.post(() -> scrollToAndFlash(toShow)); + postScrollToAndFlash(toShow); }); - } else if (toShow != null && _adapterDataFiltered.contains(toShow)) { - _recyclerView.post(() -> scrollToAndFlash(toShow)); + } else { + postScrollToAndFlash(toShow); } if (_dopt.listener != null) { _dopt.listener.onFsViewerDoUiUpdate(GsFileBrowserListAdapter.this); } }); - } else if (toShow != null && _adapterDataFiltered.contains(toShow)) { - _recyclerView.post(() -> scrollToAndFlash(toShow)); + } else { + postScrollToAndFlash(toShow); } } @@ -835,39 +831,40 @@ public boolean isCurrentFolderHome() { //######################## private static class StringFilter extends Filter { private final GsFileBrowserListAdapter _adapter; - private final List _originalList; private final List _filteredList; - public CharSequence _lastFilter = ""; + public String _lastFilter = ""; - private StringFilter(GsFileBrowserListAdapter adapter, List adapterData) { + private StringFilter(final GsFileBrowserListAdapter adapter) { super(); _adapter = adapter; - _originalList = adapterData; _filteredList = new ArrayList<>(); } @Override protected FilterResults performFiltering(CharSequence constraint) { final FilterResults results = new FilterResults(); - constraint = constraint.toString().toLowerCase(Locale.getDefault()).trim(); - _filteredList.clear(); - if (constraint.length() == 0) { - _filteredList.addAll(_originalList); - } else { - for (File file : _originalList) { - if (file.getName().toLowerCase(Locale.getDefault()).contains(constraint)) { - _filteredList.add(file); - } - } - } + _lastFilter = constraint.toString().toLowerCase().trim(); + _filter(_adapter._adapterData, _filteredList); - _lastFilter = constraint; results.values = _filteredList; results.count = _filteredList.size(); return results; } + public void _filter(final List all, final List filtered) { + filtered.clear(); + if (_lastFilter.isEmpty()) { + filtered.addAll(all); + } else { + for (final File file : all) { + if (file.getName().toLowerCase().contains(_lastFilter)) { + filtered.add(file); + } + } + } + } + @Override @SuppressWarnings("unchecked") protected void publishResults(CharSequence constraint, FilterResults results) { diff --git a/app/src/main/res/layout/new_file_dialog.xml b/app/src/main/res/layout/new_file_dialog.xml index a6cc48837a..bb9cbfa1ec 100644 --- a/app/src/main/res/layout/new_file_dialog.xml +++ b/app/src/main/res/layout/new_file_dialog.xml @@ -150,7 +150,7 @@ android:text="@string/template" android:textAppearance="@style/TextAppearance.AppCompat.Caption" /> - . Square Brackets CSV OrgMode + option_select_lines