From 217e5679263bb1e0c52f743e6fea6618d7625dab Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sun, 4 Sep 2016 20:58:22 -0700 Subject: [PATCH] Double-wide and double-high line support. --- CMakeLists.txt | 1 + src/TerminalOutput.vala | 49 ++++++++++++++++ src/TerminalView.vala | 9 +-- src/TextView.vala | 127 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 src/TextView.vala diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c95d98..bd6bee1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ vala_precompile(VALA_C src/TerminalOutput.vala src/CharacterAttributes.vala src/TerminalView.vala + src/TextView.vala src/TerminalWidget.vala src/NestingContainer.vala src/Autocompletion.vala diff --git a/src/TerminalOutput.vala b/src/TerminalOutput.vala index bbd53dc..2c0d930 100644 --- a/src/TerminalOutput.vala +++ b/src/TerminalOutput.vala @@ -170,6 +170,36 @@ public class TerminalOutput : Gtk.TextBuffer { line_updated(cursor_position.line); break; + case TerminalStream.StreamElement.ControlSequenceType.DEC_DOUBLE_WIDTH_LINE: + Gtk.TextIter start, end; + get_iter_at_line(out start, cursor_position.line); + get_iter_at_line(out end, cursor_position.line); + end.forward_to_line_end(); + + // Add tag to entire line. Text drawn in TextView.draw_layer + apply_tag(tag_table.lookup("double-wide") ?? create_tag("double-wide", "invisible", true), start, end); + break; + + case TerminalStream.StreamElement.ControlSequenceType.DEC_DOUBLE_HEIGHT_LINE_TOP_HALF: + Gtk.TextIter start, end; + get_iter_at_line(out start, cursor_position.line); + get_iter_at_line(out end, cursor_position.line); + end.forward_to_line_end(); + + // Add tag to entire line. Text drawn in TextView.draw_layer + apply_tag(tag_table.lookup("double-top") ?? create_tag("double-top", "invisible", true), start, end); + break; + + case TerminalStream.StreamElement.ControlSequenceType.DEC_DOUBLE_HEIGHT_LINE_BOTTOM_HALF: + Gtk.TextIter start, end; + get_iter_at_line(out start, cursor_position.line); + get_iter_at_line(out end, cursor_position.line); + end.forward_to_line_end(); + + // Add tag to entire line. Text drawn in TextView.draw_layer + apply_tag(tag_table.lookup("double-bottom") ?? create_tag("double-bottom", "invisible", true), start, end); + break; + case TerminalStream.StreamElement.ControlSequenceType.BELL: // TODO: Beep on the terminal window rather than the default display Gdk.beep(); @@ -912,6 +942,25 @@ public class TerminalOutput : Gtk.TextBuffer { foreach (var tag in tags) apply_tag(tag, start, iter); + // If this is a double-wide or double-high line expand tag to the new line end + if (!start.ends_line()) + { + var end = start; + end.forward_to_line_end(); + + var tag = tag_table.lookup("double-wide"); + if (tag != null && start.ends_tag(tag)) + apply_tag(tag, start, end); + + tag = tag_table.lookup("double-top"); + if (tag != null && start.ends_tag(tag)) + apply_tag(tag, start, end); + + tag = tag_table.lookup("double-bottom"); + if (tag != null && start.ends_tag(tag)) + apply_tag(tag, start, end); + } + start = iter; // Printed text should overwrite previous content on the line diff --git a/src/TerminalView.vala b/src/TerminalView.vala index 099db41..6cfc219 100644 --- a/src/TerminalView.vala +++ b/src/TerminalView.vala @@ -39,7 +39,7 @@ public class TerminalView : Fixed { var box = new Box (Orientation.VERTICAL, 5); put (box, 0, 0); - + size_allocate.connect((alloc) => { var child = Gtk.Allocation (); child.x = 0; @@ -103,9 +103,6 @@ public class TerminalOutputView : ScrolledWindow { this.terminal = terminal; view = new TextView.with_buffer (terminal.terminal_output); - view.editable = false; - view.cursor_visible = false; - view.wrap_mode = WrapMode.CHAR; view.motion_notify_event.connect(on_motion_notify_event); view.button_press_event.connect(on_button_press_event); view.has_tooltip = true; @@ -377,7 +374,7 @@ public class TerminalOutputView : ScrolledWindow { } public void scroll_to_position(TerminalOutput.CursorPosition position = {-1, -1}) { - if (position.line == -1 && position.column == -1) + if (position.line == -1 && position.column == -1) // Default: Scroll to end vadjustment.value = vadjustment.upper; else { @@ -431,4 +428,4 @@ public class TerminalOutputView : ScrolledWindow { // TODO: Use Utilities.schedule_execution here? terminal.update_size(); } -} \ No newline at end of file +} diff --git a/src/TextView.vala b/src/TextView.vala new file mode 100644 index 0000000..03d3841 --- /dev/null +++ b/src/TextView.vala @@ -0,0 +1,127 @@ +/* + * Copyright © 2013–2014 Philipp Emanuel Weidmann + * Copyright © 2015-2016 RedHatter + * + * Nemo vir est qui mundum non reddat meliorem. + * + * + * This file is part of Final Term. + * + * Final Term is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Final Term is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Final Term. If not, see . + */ + +using Gtk; +using Pango; + +/** + * Custom TextView to draw double-wide and double-high text. + */ +public class TextView : Gtk.TextView { + public TextView.with_buffer (TextBuffer buffer) { + set_buffer(buffer); + editable = false; + cursor_visible = false; + wrap_mode = Gtk.WrapMode.CHAR; + } + + public override void draw_layer (TextViewLayer layer, Cairo.Context cr) { + // Draw after text has been rendered + if (layer != TextViewLayer.BELOW) + return; + + //TODO: Cache layout + var line = create_pango_layout(null); + TextIter iter; + + // For each of the three types obtain the text using + // forward_to_tag_toggle, apply line styles, set clip, and draw with + // Cairo transforms + + // Double-wide: scale x by a factor of 2 + buffer.get_iter_at_line(out iter, 0); + var tag = buffer.tag_table.lookup("double-wide"); + if (tag != null) { + while (next_segment(cr, tag, ref iter, line)) { + cr.scale(2.0, 1.0); + Pango.cairo_show_layout(cr, line); + cr.scale(0.5, 1.0); + } + } + + // Double-high top half: scale both x and y by a factor of 2 + buffer.get_iter_at_line(out iter, 0); + tag = buffer.tag_table.lookup("double-top"); + if (tag != null) { + while (next_segment(cr, tag, ref iter, line)) { + cr.scale(2.0, 2.0); + Pango.cairo_show_layout(cr, line); + cr.scale(0.5, 0.5); + } + } + + // Double-high bottom half: scale both x and y by a factor of 2 and + // shift y up one line + buffer.get_iter_at_line(out iter, 0); + tag = buffer.tag_table.lookup("double-bottom"); + if (tag != null) { + while (next_segment(cr, tag, ref iter, line)) { + int line_height; + get_line_yrange (iter, null, out line_height); + cr.rel_move_to(0, -line_height); + cr.scale(2.0, 2.0); + Pango.cairo_show_layout(cr, line); + cr.scale(0.5, 0.5); + } + } + } + + private bool next_segment (Cairo.Context cr, TextTag tag, ref TextIter iter, Pango.Layout line) { + if (!iter.forward_to_tag_toggle(tag)) + return false; // No more segments + + var start = iter; + iter.forward_to_tag_toggle(tag); + + // Extract text + var text = buffer.get_text(start, iter, true); + line.set_text(text, -1); + + // TODO: Set all attribues from tags + + // Set color attribute + var color = get_default_attributes().appearance.fg_color; + var attr = Pango.attr_foreground_new(color.red, color.green, color.blue); + attr.start_index = 0; + attr.end_index = text.length; + var attr_list = new AttrList(); + attr_list.change((owned) attr); + line.set_attributes(attr_list); + + // Get position shifted by scroll + Gdk.Rectangle pos_rect; + get_cursor_locations(iter, out pos_rect, null); + pos_rect.y -= (int) get_vadjustment().value; + pos_rect.x -= (int) get_hadjustment().value; + + // clip and move Cairo + int height; + line.get_pixel_size(null, out height); + cr.reset_clip(); + cr.rectangle(pos_rect.x, pos_rect.y, get_allocated_width(), height); + cr.clip(); + cr.move_to(pos_rect.x, pos_rect.y); + + return true; + } +}