MarkdownEditor.vala 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. using Gtk;
  2. using Adw;
  3. using GtkCommonMark;
  4. namespace Publicate.Editors {
  5. public class MarkdownEditor : Box, EditorWidget, Savable {
  6. private Box leaflet; // For now, a box
  7. private GtkCommonMark.MarkdownView markdown_view;
  8. private Ppub.Publication publication;
  9. private GtkSource.View text_view;
  10. private GtkSource.Buffer source_buffer;
  11. private GtkSource.LanguageManager language_manager;
  12. private ScrolledWindow source_scroller;
  13. private ScrolledWindow markdown_scroller;
  14. private ViewerWindow window;
  15. private TabPage page;
  16. public Adw.TabPage tab_page { get {
  17. return page;
  18. } }
  19. public string asset_name { get{
  20. return asset.name;
  21. } }
  22. private bool unsaved = false;
  23. public bool has_unsaved_changes { get{
  24. return unsaved;
  25. } }
  26. private Ppub.Asset asset;
  27. public MarkdownEditor(ViewerWindow win, TabView tab_view) {
  28. window = win;
  29. orientation = Orientation.VERTICAL;
  30. source_scroller = new ScrolledWindow ();
  31. markdown_scroller = new ScrolledWindow ();
  32. leaflet = new Box(Orientation.HORIZONTAL, 0);
  33. text_view = new GtkSource.View ();
  34. language_manager = new GtkSource.LanguageManager ();
  35. text_view.monospace = true;
  36. text_view.show_line_numbers = true;
  37. text_view.auto_indent = true;
  38. source_buffer = (GtkSource.Buffer) text_view.buffer;
  39. source_buffer.language = language_manager.guess_language ("file.md", "text/markdown");
  40. text_view.hexpand = true;
  41. text_view.set_wrap_mode (WrapMode.WORD_CHAR);
  42. text_view.top_margin = 18;
  43. text_view.bottom_margin = 18;
  44. text_view.left_margin = 18;
  45. text_view.right_margin = 18;
  46. text_view.buffer.changed.connect (update_preview);
  47. source_scroller.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
  48. source_scroller.vexpand = true;
  49. markdown_scroller.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
  50. markdown_scroller.vexpand = true;
  51. source_scroller.vadjustment.value_changed.connect(sync_scroll);
  52. markdown_view = new GtkCommonMark.MarkdownView ();
  53. markdown_view.hexpand = true;
  54. markdown_view.set_wrap_mode (WrapMode.WORD_CHAR);
  55. markdown_view.widget_embedded.connect(widget_embedded);
  56. source_scroller.child = text_view;
  57. markdown_scroller.child = markdown_view;
  58. leaflet.append(source_scroller);
  59. leaflet.append(new Separator(Orientation.VERTICAL));
  60. leaflet.append (markdown_scroller);
  61. Gtk.Settings.get_default().notify["gtk-application-prefer-dark-theme"].connect(() => configure_tags());
  62. configure_tags();
  63. append(leaflet);
  64. page = tab_view.add_page (this, null);
  65. }
  66. public void set_zoom_percentage (int percent) {
  67. var scale = (float)percent / 100f;
  68. markdown_view.tag_manager.font_scale = scale;
  69. }
  70. public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
  71. this.publication = publication;
  72. markdown_view.buffer.set_text("", 0);
  73. page.title = asset.name;
  74. this.asset = asset;
  75. text_view.buffer.set_text("", 0);
  76. MemoryOutputStream os = new MemoryOutputStream (null, GLib.realloc, GLib.free);
  77. yield os.splice_async (publication.read_asset (asset.name), OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET);
  78. var text = os.steal_data ();
  79. text.length = (int) os.get_data_size ();
  80. text_view.buffer.set_text ((string)text, text.length);
  81. set_tab_name(false);
  82. }
  83. public override async void save_asset(Ppub.Builder builder) {
  84. var article_data = get_text().to_utf8();
  85. var article_stream = new MemoryInputStream.from_data((uint8[])article_data);
  86. var compression_info = new Ppub.CompressionInfo(article_stream);
  87. article_stream.seek(0, SeekType.SET);
  88. builder.add_asset(asset.name, asset.mimetype, article_stream, Invercargill.empty<string>(), compression_info);
  89. set_tab_name(false);
  90. }
  91. protected async void widget_embedded(GtkCommonMark.MarkdownViewEmbeddedWidgetHost widget, string file, string title) {
  92. var image = new Gtk.Picture();
  93. image.content_fit = Gtk.ContentFit.FILL;
  94. widget.append (image);
  95. var pixbuf = yield new Gdk.Pixbuf.from_stream_async (publication.read_asset (file), null);
  96. image.set_pixbuf (pixbuf);
  97. widget.available_width_changed.connect(wid => {
  98. image.width_request = wid;
  99. var ratio = image.get_paintable().get_intrinsic_aspect_ratio ();
  100. image.height_request = (int)(wid / ratio);
  101. });
  102. }
  103. private void configure_tags() {
  104. var link = new LinkButton("");
  105. markdown_view.tag_manager.update_link_colour(link.get_color());
  106. }
  107. private void update_preview() {
  108. var text = get_text();
  109. markdown_view.buffer.set_text("", 0);
  110. markdown_view.load_from_string((string)text);
  111. set_tab_name(true);
  112. }
  113. public string get_text() {
  114. TextIter start_iter;
  115. TextIter end_iter;
  116. text_view.buffer.get_start_iter (out start_iter);
  117. text_view.buffer.get_end_iter (out end_iter);
  118. var text = text_view.buffer.get_text (start_iter, end_iter, true);
  119. return text;
  120. }
  121. private void sync_scroll() {
  122. set_scroll_from_frac(markdown_scroller, get_scroll_frac(source_scroller));
  123. }
  124. private static double get_scroll_frac(ScrolledWindow scroller) {
  125. return (scroller.vadjustment.value/(scroller.vadjustment.upper - scroller.vadjustment.page_size));
  126. }
  127. private void set_scroll_from_frac(ScrolledWindow scroller, double frac) {
  128. scroller.vadjustment.value = (scroller.vadjustment.upper - scroller.vadjustment.page_size)*frac;
  129. }
  130. private void set_tab_name(bool unsaved) {
  131. this.unsaved = unsaved;
  132. if(unsaved) {
  133. tab_page.title = @"⏺ $(asset.name)";
  134. }
  135. else {
  136. tab_page.title = asset.name;
  137. }
  138. }
  139. }
  140. }