Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions data/jorts.gschema.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
<description>Whether to hide the actionbar and its buttons</description>
</key>
<key name="list-item-start" type="s">
<default>" • "</default>
<summary>Hide actionbar</summary>
<description>Whether to hide the actionbar and its buttons</description>
<default>"• "</default>
<summary>List item start</summary>
<description>The string to use at the start of a list item</description>
</key>
</schema>
</schemalist>
1 change: 1 addition & 0 deletions src/Services/ZoomController.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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 ();
Expand Down
21 changes: 20 additions & 1 deletion src/Views/PreferencesView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -63,7 +82,7 @@

Application.gsettings.bind (KEY_LIST,
list_entry, "text",
SettingsBindFlags.DEFAULT);
SettingsBindFlags.GET);

settingsbox.append (lists_box);

Expand Down
207 changes: 201 additions & 6 deletions src/Widgets/TextView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i struggle with the whole observed_buffer thing. It feels very wrong, we end up with twice the amount of text per sticky note in memory (not even counting the gtk cruft), and compare them constantly at every change

i really need to sit down and work on this

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);
Comment on lines +26 to +28
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var old_prefix = _list_item_start;
_list_item_start = value;
migrate_list_prefixes (old_prefix, value);
migrate_list_prefixes (_list_item_start, value);
_list_item_start = value;

Id rather set the sauce to the new sauce after we migrated everything

}
}
public bool on_list_item {public get; private set;}

public string text {
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -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 ();
}
}

/**
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -163,24 +338,30 @@ 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);
}

/**
* Handler whenever a key is pressed, to see if user needs something and get ahead
* 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);
Expand All @@ -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 ();
}
}
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Windows/StickyNoteWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down