diff --git a/data/jorts.gschema.xml.in b/data/jorts.gschema.xml.in
index 08409008..7026d1f0 100644
--- a/data/jorts.gschema.xml.in
+++ b/data/jorts.gschema.xml.in
@@ -12,9 +12,9 @@
Whether to hide the actionbar and its buttons
- " • "
- Hide actionbar
- Whether to hide the actionbar and its buttons
+ "• "
+ List item start
+ The string to use at the start of a list item
diff --git a/src/Services/ZoomController.vala b/src/Services/ZoomController.vala
index 753b2ff9..78c9c3b1 100644
--- a/src/Services/ZoomController.vala
+++ b/src/Services/ZoomController.vala
@@ -105,6 +105,7 @@ public class Jorts.ZoomController : Object {
window.remove_css_class (Jorts.Zoom.from_int ( _old_zoom).to_css_class ());
_old_zoom = new_zoom;
window.add_css_class (Jorts.Zoom.from_int ( new_zoom).to_css_class ());
+ window.textview.refresh_list_item_indentation ();
// Adapt headerbar size to avoid weird flickering
window.view.headerbar.height_request = Jorts.Zoom.from_int (new_zoom).to_ui_size ();
diff --git a/src/Views/PreferencesView.vala b/src/Views/PreferencesView.vala
index 1bd2a638..466e7763 100644
--- a/src/Views/PreferencesView.vala
+++ b/src/Views/PreferencesView.vala
@@ -10,6 +10,16 @@
private Granite.Toast toast;
public Gtk.Button close_button;
+ private void commit_list_prefix (Gtk.Entry entry) {
+ var current_prefix = Application.gsettings.get_string (KEY_LIST);
+
+ if (entry.text == current_prefix) {
+ return;
+ }
+
+ Application.gsettings.set_string (KEY_LIST, entry.text);
+ }
+
construct {
var overlay = new Gtk.Overlay ();
child = overlay;
@@ -50,6 +60,15 @@
list_entry.secondary_icon_name = "view-refresh-symbolic";
list_entry.secondary_icon_tooltip_text = _("Reset to default");
list_entry.icon_press.connect (on_reset_prefix);
+ list_entry.activate.connect (() => {
+ commit_list_prefix (list_entry);
+ });
+
+ var list_entry_focus = new Gtk.EventControllerFocus ();
+ list_entry_focus.leave.connect (() => {
+ commit_list_prefix (list_entry);
+ });
+ list_entry.add_controller (list_entry_focus);
var list_label = new Granite.HeaderLabel (_("List item prefix")) {
mnemonic_widget = list_entry,
@@ -63,7 +82,7 @@
Application.gsettings.bind (KEY_LIST,
list_entry, "text",
- SettingsBindFlags.DEFAULT);
+ SettingsBindFlags.GET);
settingsbox.append (lists_box);
diff --git a/src/Widgets/TextView.vala b/src/Widgets/TextView.vala
index 06a9bcf5..3a12e49c 100644
--- a/src/Widgets/TextView.vala
+++ b/src/Widgets/TextView.vala
@@ -12,7 +12,22 @@
public class Jorts.TextView : Granite.HyperTextView {
private Gtk.EventControllerKey keyboard;
- public string list_item_start {get; set;}
+ private Gtk.TextBuffer? observed_buffer;
+ private ulong buffer_changed_handler_id = 0;
+ private bool list_item_restore_queued = false;
+ private string _list_item_start = "";
+ public string list_item_start {
+ get { return _list_item_start; }
+ set {
+ if (_list_item_start == value) {
+ return;
+ }
+
+ var old_prefix = _list_item_start;
+ _list_item_start = value;
+ migrate_list_prefixes (old_prefix, value);
+ }
+ }
public bool on_list_item {public get; private set;}
public string text {
@@ -75,12 +90,118 @@ public class Jorts.TextView : Granite.HyperTextView {
/* CONNECTS AND BINDS */
/***************************************************/
+ notify["buffer"].connect (() => {
+ attach_buffer_observers ();
+ queue_restore_list_item_indentation ();
+ });
+ attach_buffer_observers ();
+
Application.gsettings.bind (KEY_LIST,
this, "list-item-start",
GLib.SettingsBindFlags.DEFAULT);
}
+ private void attach_buffer_observers () {
+ if (observed_buffer == buffer) {
+ return;
+ }
+
+ detach_buffer_observers ();
+ observed_buffer = buffer;
+
+ buffer_changed_handler_id = observed_buffer.changed.connect_after (queue_restore_list_item_indentation);
+ }
+
+ private void detach_buffer_observers () {
+ if (observed_buffer == null) {
+ return;
+ }
+
+ if (buffer_changed_handler_id != 0) {
+ SignalHandler.disconnect (observed_buffer, buffer_changed_handler_id);
+ buffer_changed_handler_id = 0;
+ }
+
+ observed_buffer = null;
+ }
+
+ private void queue_restore_list_item_indentation () {
+ if (list_item_restore_queued) {
+ return;
+ }
+
+ list_item_restore_queued = true;
+
+ Idle.add (() => {
+ list_item_restore_queued = false;
+ restore_list_item_indentation ();
+ return false;
+ });
+ }
+
+ private void ensure_tags () {
+ if (list_item_start == "") {
+ return;
+ }
+
+ var measured_prefix = list_item_start.strip ();
+
+ if (measured_prefix == "") {
+ measured_prefix = list_item_start;
+ }
+
+ var layout = this.create_pango_layout (measured_prefix);
+ int width, height;
+ layout.get_pixel_size (out width, out height);
+
+ var indent_width = int.max (width + 4, 8);
+
+ var list_item_tag = buffer.tag_table.lookup ("list_item");
+
+ if (list_item_tag == null) {
+ buffer.create_tag ("list_item",
+ "indent", -indent_width,
+ "left-margin", SPACING_DOUBLE + indent_width
+ );
+ return;
+ }
+
+ list_item_tag.indent = -indent_width;
+ list_item_tag.left_margin = SPACING_DOUBLE + indent_width;
+ }
+
+ public void refresh_list_item_indentation () {
+ ensure_tags ();
+ }
+
+ public void restore_list_item_indentation () {
+ Gtk.TextIter start, end;
+ buffer.get_bounds (out start, out end);
+ buffer.remove_tag_by_name ("list_item", start, end);
+
+ if (list_item_start == "") {
+ return;
+ }
+
+ ensure_tags ();
+
+ var line_count = buffer.get_line_count ();
+
+ for (int line_number = 0; line_number < line_count; line_number++) {
+ if (!this.has_prefix (line_number)) {
+ continue;
+ }
+
+ Gtk.TextIter line_start, line_end;
+ buffer.get_iter_at_line_offset (out line_start, line_number, 0);
+ line_end = line_start.copy ();
+ line_end.forward_to_line_end ();
+ buffer.apply_tag_by_name ("list_item", line_start, line_end);
+ }
+ }
+
public void toggle_list () {
+ ensure_tags ();
Gtk.TextIter start, end;
buffer.get_selection_bounds (out start, out end);
@@ -105,8 +226,8 @@ public class Jorts.TextView : Granite.HyperTextView {
/**
* Add the list prefix only to lines who hasnt it already
*/
- private bool has_prefix (int line_number) {
- if (list_item_start == "") {return false;}
+ private bool has_specific_prefix (int line_number, string prefix) {
+ if (prefix == "") {return false;}
Gtk.TextIter start, end;
buffer.get_iter_at_line_offset (out start, line_number, 0);
@@ -116,7 +237,54 @@ public class Jorts.TextView : Granite.HyperTextView {
var text_in_line = buffer.get_slice (start, end, false);
- return text_in_line.has_prefix (list_item_start);
+ return text_in_line.has_prefix (prefix);
+ }
+
+ private bool has_prefix (int line_number) {
+ return has_specific_prefix (line_number, list_item_start);
+ }
+
+ private void replace_prefix (int line_number, string old_prefix, string new_prefix) {
+ Gtk.TextIter line_start, prefix_end;
+
+ buffer.get_iter_at_line_offset (out line_start, line_number, 0);
+ buffer.get_iter_at_line_offset (out prefix_end, line_number, old_prefix.char_count ());
+ buffer.delete (ref line_start, ref prefix_end);
+
+ buffer.get_iter_at_line_offset (out line_start, line_number, 0);
+ buffer.insert (ref line_start, new_prefix, -1);
+ }
+
+ private void migrate_list_prefixes (string old_prefix, string new_prefix) {
+ if (old_prefix == "") {
+ if (new_prefix == "") {
+ Gtk.TextIter start, end;
+ buffer.get_bounds (out start, out end);
+ buffer.remove_tag_by_name ("list_item", start, end);
+ }
+
+ return;
+ }
+
+ var line_count = buffer.get_line_count ();
+ var did_change = false;
+
+ buffer.begin_user_action ();
+
+ for (int line_number = 0; line_number < line_count; line_number++) {
+ if (!has_specific_prefix (line_number, old_prefix)) {
+ continue;
+ }
+
+ replace_prefix (line_number, old_prefix, new_prefix);
+ did_change = true;
+ }
+
+ buffer.end_user_action ();
+
+ if (did_change || new_prefix == "") {
+ restore_list_item_indentation ();
+ }
}
/**
@@ -147,6 +315,13 @@ public class Jorts.TextView : Granite.HyperTextView {
buffer.get_iter_at_line_offset (out line_start, line_number, 0);
buffer.insert (ref line_start, list_item_start, -1);
}
+
+ // Apply hanging indent tag to the line
+ Gtk.TextIter ls, le;
+ buffer.get_iter_at_line_offset (out ls, line_number, 0);
+ le = ls.copy ();
+ le.forward_to_line_end ();
+ buffer.apply_tag_by_name ("list_item", ls, le);
}
}
@@ -163,13 +338,19 @@ public class Jorts.TextView : Granite.HyperTextView {
* Remove list prefix from line x to line y. Presuppose it is there
*/
private void remove_prefix (int line_number) {
- Gtk.TextIter line_start, prefix_end;
+ Gtk.TextIter line_start, prefix_end, line_end;
var remove_range = list_item_start.char_count ();
debug ("doing line " + line_number.to_string ());
buffer.get_iter_at_line_offset (out line_start, line_number, 0);
buffer.get_iter_at_line_offset (out prefix_end, line_number, remove_range);
buffer.delete (ref line_start, ref prefix_end);
+
+ // Remove hanging indent tag from the line
+ buffer.get_iter_at_line_offset (out line_start, line_number, 0);
+ line_end = line_start.copy ();
+ line_end.forward_to_line_end ();
+ buffer.remove_tag_by_name ("list_item", line_start, line_end);
}
/**
@@ -177,10 +358,10 @@ public class Jorts.TextView : Granite.HyperTextView {
* Some local stuff is deduplicated in the Ifs, because i do not like the idea of getting computation done not needed 98% of the time
*/
private bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) {
+ ensure_tags ();
// If backspace on a prefix: Delete the prefix.
if (keyval == Gdk.Key.BackSpace) {
- print ("backspace");
Gtk.TextIter start, end;
buffer.get_selection_bounds (out start, out end);
@@ -197,6 +378,13 @@ public class Jorts.TextView : Granite.HyperTextView {
buffer.begin_user_action ();
buffer.delete (ref start, ref end);
buffer.insert_at_cursor ("\n", -1);
+
+ // The line is now an empty normal line, so remove the hanging indent
+ buffer.get_iter_at_line_offset (out start, line_number, 0);
+ end = start.copy ();
+ end.forward_to_line_end ();
+ buffer.remove_tag_by_name ("list_item", start, end);
+
buffer.end_user_action ();
}
}
@@ -212,6 +400,13 @@ public class Jorts.TextView : Granite.HyperTextView {
buffer.begin_user_action ();
buffer.insert_at_cursor ("\n" + list_item_start, -1);
+
+ // Ensure new line has tag applied since it was just inserted
+ buffer.get_iter_at_line_offset (out start, line_number + 1, 0);
+ end = start.copy ();
+ end.forward_to_line_end ();
+ buffer.apply_tag_by_name ("list_item", start, end);
+
buffer.end_user_action ();
return true;
diff --git a/src/Windows/StickyNoteWindow.vala b/src/Windows/StickyNoteWindow.vala
index 27305c02..441bd37c 100644
--- a/src/Windows/StickyNoteWindow.vala
+++ b/src/Windows/StickyNoteWindow.vala
@@ -199,6 +199,7 @@ public class Jorts.StickyNoteWindow : Gtk.ApplicationWindow {
color_controller.theme = data.theme;
zoom_controller.zoom = data.zoom;
view.monospace = data.monospace;
+ textview.restore_list_item_indentation ();
}
public void has_changed () {