Skip to content

Commit

Permalink
Format detection fixes, headline dialog improvements (PR #2300 closes
Browse files Browse the repository at this point in the history
…#2303 closes #2296 closes #2297)

* Headline Dialog State
* Fixes with format order and type
* Switching to list for ease of use

---------

Co-authored-by: Gregor Santner <gsantner@mailbox.org>
  • Loading branch information
harshad1 and gsantner committed May 25, 2024
1 parent cef4068 commit 8bc15d7
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -548,7 +547,7 @@ private static String extractShareText(final Intent intent) {
link = link != null ? link.trim() : "";

if (Patterns.WEB_URL.matcher(link).matches()) {
link = (title != null? title : "") + sanitize(link);
link = (title != null ? title : "") + sanitize(link);
}

return link;
Expand Down
46 changes: 18 additions & 28 deletions app/src/main/java/net/gsantner/markor/format/FormatRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import net.gsantner.markor.model.Document;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

public class FormatRegistry {
Expand Down Expand Up @@ -77,52 +79,40 @@ public class FormatRegistry {

public static class Format {
public final @StringRes int format, name;
public final String ext;
public final String defaultExtensionWithDot;
public final TextConverterBase converter;

Format(int format, int name, String ext, final TextConverterBase converter) {
this.format = format;
this.name = name;
this.ext = ext;
this.converter = converter;
public Format(@StringRes final int a_format, @StringRes final int a_name, final String a_defaultFileExtension, final TextConverterBase a_converter) {
format = a_format;
name = a_name;
defaultExtensionWithDot = a_defaultFileExtension;
converter = a_converter;
}
}

public static final Format[] FORMATS = new Format[]{
// Order here is used to **determine** format by it's file extension and/or content heading
public static final List<Format> FORMATS = Arrays.asList(
new Format(FormatRegistry.FORMAT_MARKDOWN, R.string.markdown, ".md", CONVERTER_MARKDOWN),
new Format(FormatRegistry.FORMAT_PLAIN, R.string.plaintext, ".txt", CONVERTER_PLAINTEXT),
new Format(FormatRegistry.FORMAT_TODOTXT, R.string.todo_txt, ".todo.txt", CONVERTER_TODOTXT),
new Format(FormatRegistry.FORMAT_CSV, R.string.csv, ".csv", CONVERTER_CSV),
new Format(FormatRegistry.FORMAT_WIKITEXT, R.string.wikitext, ".txt", CONVERTER_WIKITEXT),
new Format(FormatRegistry.FORMAT_KEYVALUE, R.string.key_value, ".json", CONVERTER_KEYVALUE),
new Format(FormatRegistry.FORMAT_ASCIIDOC, R.string.asciidoc, ".adoc", CONVERTER_ASCIIDOC),
new Format(FormatRegistry.FORMAT_CSV, R.string.csv, ".csv", CONVERTER_CSV),
new Format(FormatRegistry.FORMAT_ORGMODE, R.string.orgmode, ".org", CONVERTER_ORGMODE),
new Format(FormatRegistry.FORMAT_EMBEDBINARY, R.string.embed_binary, ".jpg", CONVERTER_EMBEDBINARY),
new Format(FormatRegistry.FORMAT_UNKNOWN, R.string.none, "", null),
};


// Order here is used to **determine** format by it's file extension and/or content heading
private final static TextConverterBase[] CONVERTERS = new TextConverterBase[]{
CONVERTER_MARKDOWN,
CONVERTER_CSV,
CONVERTER_TODOTXT,
CONVERTER_WIKITEXT,
CONVERTER_KEYVALUE,
CONVERTER_ASCIIDOC,
CONVERTER_PLAINTEXT,
CONVERTER_EMBEDBINARY,
CONVERTER_ORGMODE,
};
new Format(FormatRegistry.FORMAT_PLAIN, R.string.plaintext, ".txt", CONVERTER_PLAINTEXT),
new Format(FormatRegistry.FORMAT_UNKNOWN, R.string.none, "", null)
);

public static boolean isFileSupported(final File file, final boolean... textOnly) {
final boolean textonly = textOnly != null && textOnly.length > 0 && textOnly[0];
if (file != null) {
final String filepath = file.getAbsolutePath().toLowerCase(Locale.ROOT);
for (TextConverterBase converter : CONVERTERS) {
if (textonly && converter instanceof EmbedBinaryTextConverter) {
for (final Format format : FORMATS) {
if (textonly && format.converter instanceof EmbedBinaryTextConverter) {
continue;
}
if (converter.isFileOutOfThisFormat(filepath)) {
if (format.converter != null && format.converter.isFileOutOfThisFormat(filepath)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -41,7 +39,7 @@ public class MarkdownActionButtons extends ActionButtonBase {

private static final Pattern WEB_URL = Pattern.compile("https?://[^\\s/$.?#].[^\\s]*");

private final Set<Integer> _disabledHeadings = new HashSet<>();
private final MarkorDialogFactory.HeadlineDialogState _headlineDialogState = new MarkorDialogFactory.HeadlineDialogState();

public static final String LINE_PREFIX = "^(>\\s|#{1,6}\\s|\\s*[-*+](?:\\s\\[[ xX]\\])?\\s|\\s*\\d+[.)]\\s)?";

Expand Down Expand Up @@ -304,7 +302,7 @@ private void insertTableRow(int cols, boolean isHeaderEnabled) {
@Override
public boolean runTitleClick() {
final Matcher m = MarkdownReplacePatternGenerator.PREFIX_ATX_HEADING.matcher("");
MarkorDialogFactory.showHeadlineDialog(getActivity(), _hlEditor, _webView, _disabledHeadings, (text, start, end) -> {
MarkorDialogFactory.showHeadlineDialog(getActivity(), _hlEditor, _webView, _headlineDialogState, (text, start, end) -> {
if (m.reset(text.subSequence(start, end)).find()) {
return m.end(2) - m.start(2) - 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;

public class WikitextActionButtons extends ActionButtonBase {

private Set<Integer> _disabledHeadings = new HashSet<>();
private MarkorDialogFactory.HeadlineDialogState _headlineDialogState = new MarkorDialogFactory.HeadlineDialogState();

public WikitextActionButtons(@NonNull Context context, Document document) {
super(context, document);
Expand Down Expand Up @@ -263,7 +261,7 @@ public static String createWikitextHeaderAndTitleContents(String fileNameWithout
@Override
public boolean runTitleClick() {
final Matcher m = WikitextSyntaxHighlighter.HEADING.matcher("");
MarkorDialogFactory.showHeadlineDialog(getActivity(), _hlEditor, _webView, _disabledHeadings, (text, start, end) -> {
MarkorDialogFactory.showHeadlineDialog(getActivity(), _hlEditor, _webView, _headlineDialogState, (text, start, end) -> {
if (m.reset(text.subSequence(start, end)).find()) {
return 7 - (m.end(2) - m.start(2));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -788,17 +788,33 @@ private static class Heading {
}
}

public static class HeadlineDialogState {
public Set<Integer> disabledLevels = new HashSet<>();
public String searchQuery = "";
public int listPosition = -1;
}

/**
* Show a dialog to select a heading
*
* @param activity Activity
* @param edit Editable text
* @param webView WebView corresponding to the text
* @param state State of the dialog, so it can be restored.
* @param levelCallback Callback to get the heading level given the text and line start and end
*/
public static void showHeadlineDialog(
final Activity activity,
final EditText edit,
final WebView webView,
final Set<Integer> disabled,
final GsCallback.r3<Integer, CharSequence, Integer, Integer> headingLevel) {
final HeadlineDialogState state,
final GsCallback.r3<Integer, CharSequence, Integer, Integer> levelCallback
) {
// Get all headings and their levels
final CharSequence text = edit.getText();
final List<Heading> headings = new ArrayList<>();
GsTextUtils.forEachline(text, (line, start, end) -> {
final int level = headingLevel.callback(text, start, end);
final int level = levelCallback.callback(text, start, end);
if (level > 0) {
headings.add(new Heading(level, text.subSequence(start, end), line));
}
Expand All @@ -809,7 +825,7 @@ public static void showHeadlineDialog(
final List<Integer> levels = new ArrayList<>(new TreeSet<>(GsCollectionUtils.map(headings, h -> h.level)));

// Currently filtered headings
final List<Integer> filtered = GsCollectionUtils.indices(headings, h -> !disabled.contains(h.level));
final List<Integer> filtered = GsCollectionUtils.indices(headings, h -> !state.disabledLevels.contains(h.level));
final List<String> data = GsCollectionUtils.map(filtered, i -> headings.get(i).str);

final DialogOptions dopt = new DialogOptions();
Expand All @@ -819,34 +835,36 @@ public static void showHeadlineDialog(
dopt.searchHintText = R.string.search;
dopt.isSearchEnabled = true;
dopt.isSoftInputVisible = false;
dopt.isDismissOnItemSelected = false;
dopt.isSaveItemPositionEnabled = true;
dopt.listPosition = state.listPosition;
dopt.defaultText = state.searchQuery;

dopt.positionCallback = result -> {
final int index = filtered.get(result.get(0));
TextViewUtils.selectLines(edit, headings.get(index).line);
String header = headings.get(index).str;
String id = MarkdownTextConverter.generateHeaderId(header.substring(header.lastIndexOf('#') + 1).trim());

final String header = headings.get(index).str;
final String headerText = header.substring(header.lastIndexOf('#') + 1).trim();
final String id = MarkdownTextConverter.generateHeaderId(headerText);
webView.loadUrl("javascript:document.getElementById('" + id + "').scrollIntoView();");
};

dopt.neutralButtonText = R.string.filter;
dopt.neutralButtonCallback = (dialog) -> {
final DialogOptions dopt2 = new DialogOptions();
dopt2.preSelected = GsCollectionUtils.indices(levels, l -> !disabled.contains(l));
dopt2.preSelected = GsCollectionUtils.indices(levels, l -> !state.disabledLevels.contains(l));
dopt2.data = GsCollectionUtils.map(levels, l -> "H" + l);
dopt2.titleText = R.string.filter;
dopt2.isSearchEnabled = false;
dopt2.isMultiSelectEnabled = true;
dopt2.dialogWidthDp = 250;
dopt2.positionCallback = (selected) -> {
// Update levels so the selected ones are true
disabled.clear();
disabled.addAll(GsCollectionUtils.setDiff(levels, GsCollectionUtils.map(selected, levels::get)));
state.disabledLevels.clear();
state.disabledLevels.addAll(GsCollectionUtils.setDiff(levels, GsCollectionUtils.map(selected, levels::get)));

// Update selection and data
filtered.clear();
filtered.addAll(GsCollectionUtils.indices(headings, h -> !disabled.contains(h.level)));
filtered.addAll(GsCollectionUtils.indices(headings, h -> !state.disabledLevels.contains(h.level)));

data.clear();
data.addAll(GsCollectionUtils.map(filtered, (si, i) -> headings.get(si).str));
Expand All @@ -860,6 +878,10 @@ public static void showHeadlineDialog(
dopt.portraitAspectRatio = new float[]{0.95f, 0.8f};
dopt.landscapeAspectRatio = new float[]{0.7f, 0.95f};
dopt.gravity = Gravity.CENTER;
dopt.dismissCallback = (d) -> {
state.listPosition = dopt.listPosition;
state.searchQuery = dopt.defaultText;
};

GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt);
}
Expand Down
37 changes: 26 additions & 11 deletions app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public class NewFileDialog extends DialogFragment {

public static final int MAX_TITLE_FORMATS = 10;

private static final List<Integer> NEW_FILE_FORMATS = Arrays.asList(
FormatRegistry.FORMAT_MARKDOWN,
FormatRegistry.FORMAT_PLAIN,
FormatRegistry.FORMAT_TODOTXT,
FormatRegistry.FORMAT_WIKITEXT,
FormatRegistry.FORMAT_ASCIIDOC,
FormatRegistry.FORMAT_ORGMODE,
FormatRegistry.FORMAT_CSV
);

private GsCallback.a1<File> callback;

public static NewFileDialog newInstance(
Expand Down Expand Up @@ -125,6 +135,11 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr
titleEdit.setFilters(new InputFilter[]{GsContextUtils.instance.makeFilenameInputFilter()});
extEdit.setFilters(titleEdit.getFilters());

// Build a list of available formats
// -----------------------------------------------------------------------------------------
final List<FormatRegistry.Format> formats = GsCollectionUtils.map(
NEW_FILE_FORMATS, t -> GsCollectionUtils.selectFirst(FormatRegistry.FORMATS, f -> f.format == t));

// Setup title format spinner and actions
// -----------------------------------------------------------------------------------------
final ArrayAdapter<String> formatAdapter = new ArrayAdapter<>(
Expand Down Expand Up @@ -156,17 +171,17 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr
// Setup type / format spinner and action
// -----------------------------------------------------------------------------------------
final ArrayAdapter<String> typeAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_dropdown_item);
typeAdapter.addAll(GsCollectionUtils.map(Arrays.asList(FormatRegistry.FORMATS), f -> activity.getString(f.name)));
typeAdapter.addAll(GsCollectionUtils.map(formats, f -> activity.getString(f.name)));
typeSpinner.setAdapter(typeAdapter);

// Load name formats into spinner
final GsCallback.a1<Integer> typeCallback = pos -> {
final FormatRegistry.Format fmt = FormatRegistry.FORMATS[pos];
if (fmt.ext != null) {
final FormatRegistry.Format fmt = formats.get(pos);
if (fmt.defaultExtensionWithDot != null) {
if (encryptCheckbox.isChecked()) {
extEdit.setText(fmt.ext + JavaPasswordbasedCryption.DEFAULT_ENCRYPTION_EXTENSION);
extEdit.setText(fmt.defaultExtensionWithDot + JavaPasswordbasedCryption.DEFAULT_ENCRYPTION_EXTENSION);
} else {
extEdit.setText(fmt.ext);
extEdit.setText(fmt.defaultExtensionWithDot);
}
}

Expand Down Expand Up @@ -245,10 +260,10 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr
// Most of the logic we want is in the document class so we just reuse it
final Document document = new Document(file);

// These are done even if the file doesn
// These are done even if the file isn't created
final String titleFormat = formatEdit.getText().toString().trim();
appSettings.setTemplateTitleFormat(templateAdapter.getItem(ti), titleFormat);
final FormatRegistry.Format fmt = FormatRegistry.FORMATS[typeSpinner.getSelectedItemPosition()];
final FormatRegistry.Format fmt = formats.get(typeSpinner.getSelectedItemPosition());
appSettings.setTypeTemplate(fmt.format, (String) templateSpinner.getSelectedItem());
appSettings.setNewFileDialogLastUsedType(fmt.format);

Expand All @@ -259,8 +274,8 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr
if (!file.exists() || file.length() <= GsContextUtils.TEXTFILE_OVERWRITE_MIN_TEXT_LENGTH) {
document.saveContent(activity, content.first, cu, true);

// We only make these changes if the file did not exist
document.setFormat(FormatRegistry.FORMATS[typeSpinner.getSelectedItemPosition()].format);
// We only make these changes if the file did not already exist
appSettings.setDocumentFormat(document.getPath(), fmt.format);
appSettings.setLastEditPosition(document.getPath(), content.second);
appSettings.setNewFileDialogLastUsedExtension(extEdit.getText().toString().trim());

Expand Down Expand Up @@ -304,8 +319,8 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr

// Initial creation - loop through and set type
final int lastUsedType = appSettings.getNewFileDialogLastUsedType();
for (int i = 0; i < FormatRegistry.FORMATS.length; i++) {
final FormatRegistry.Format fmt = FormatRegistry.FORMATS[i];
for (int i = 0; i < formats.size(); i++) {
final FormatRegistry.Format fmt = formats.get(i);
if (fmt.format == lastUsedType) {
typeSpinner.setSelection(i);
typeCallback.callback(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static class DialogOptions {
public boolean isSearchEnabled = true;
public boolean isSoftInputVisible = true;
public boolean isDismissOnItemSelected = true;
public boolean isSaveItemPositionEnabled = false;
public int listPosition = -1;
public int gravity = Gravity.NO_GRAVITY;
public int dialogWidthDp = WindowManager.LayoutParams.MATCH_PARENT;
public int dialogHeightDp = WindowManager.LayoutParams.WRAP_CONTENT;
Expand Down Expand Up @@ -271,27 +271,32 @@ public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activi
final EditText searchEditText = searchView.findViewWithTag("EDIT");
searchEditText.addTextChangedListener(GsTextWatcherAdapter.after(listAdapter::filter));


if (dopt.isSearchEnabled) {
mainLayout.addView(searchView);
}

final ListView listView = new ListView(activity);
listView.setId(LIST_VIEW_ID);
listView.setAdapter(listAdapter);
if (dopt.isSaveItemPositionEnabled) {
listView.setSelection(activity.getIntent().getIntExtra("lastHeadingPosition", 0));

if (dopt.listPosition >= 0) {
listView.setSelection(dopt.listPosition);
}

listView.setVisibility(dopt.data != null && !dopt.data.isEmpty() ? View.VISIBLE : View.GONE);
final LinearLayout.LayoutParams listLayout = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
listLayout.weight = 1;
mainLayout.addView(listView, listLayout);

if (dopt.dismissCallback != null) {
dialogBuilder.setOnDismissListener(dopt.dismissCallback::callback);
} else {
dialogBuilder.setOnDismissListener(dialogInterface -> activity.getIntent().putExtra("lastHeadingPosition", listView.getFirstVisiblePosition()));
}
dialogBuilder.setOnDismissListener((dialogInterface) -> {
// Update state
dopt.listPosition = listView.getFirstVisiblePosition();
dopt.defaultText = searchEditText.getText().toString();

if (dopt.dismissCallback != null) {
dopt.dismissCallback.callback(dialogInterface);
}
});

dialogBuilder.setView(mainLayout)
.setOnCancelListener(null)
Expand Down
Loading

0 comments on commit 8bc15d7

Please sign in to comment.