Billy Barrow 2 年 前
コミット
3b23c4d1a8

+ 42 - 2
src/Editors/MarkdownEditor.vala

@@ -4,7 +4,7 @@ using GtkCommonMark;
 
 namespace Publicate.Editors {
 
-    public class MarkdownEditor : Box, EditorWidget {
+    public class MarkdownEditor : Box, EditorWidget, Savable {
 
         private Box leaflet; // For now, a box
 
@@ -20,6 +20,23 @@ namespace Publicate.Editors {
         private ViewerWindow window;
         private TabPage page;
 
+        public Adw.TabPage tab_page { get {
+            return page;
+        } }
+
+        public string asset_name { get{
+            return asset.name;
+        } }
+
+
+        private bool unsaved = false;
+        public bool has_unsaved_changes { get{
+            return unsaved;
+        } }
+
+
+        private Ppub.Asset asset;
+
         public MarkdownEditor(ViewerWindow win, TabView tab_view) {
             window = win;
 
@@ -76,10 +93,11 @@ namespace Publicate.Editors {
             markdown_view.tag_manager.font_scale = scale;
         }
         
-        public virtual async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+        public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
             this.publication = publication;
             markdown_view.buffer.set_text("", 0);
             page.title = asset.name;
+            this.asset = asset;
 
             text_view.buffer.set_text("", 0);
             MemoryOutputStream os = new MemoryOutputStream (null, GLib.realloc, GLib.free);
@@ -88,6 +106,17 @@ namespace Publicate.Editors {
             text.length = (int) os.get_data_size ();
 
             text_view.buffer.set_text ((string)text, text.length);
+            set_tab_name(false);
+        }
+
+        public override async void save_asset(Ppub.Builder builder) {
+            var article_data = get_text().to_utf8();
+            var article_stream = new MemoryInputStream.from_data((uint8[])article_data);
+            var compression_info = new Ppub.CompressionInfo(article_stream);
+            article_stream.seek(0, SeekType.SET);
+
+            builder.add_asset(asset.name, asset.mimetype, article_stream, Invercargill.empty<string>(), compression_info);
+            set_tab_name(false);
         }
 
         protected async void widget_embedded(GtkCommonMark.MarkdownViewEmbeddedWidgetHost widget, string file, string title) {
@@ -115,6 +144,7 @@ namespace Publicate.Editors {
             var text = get_text();
             markdown_view.buffer.set_text("", 0);
             markdown_view.load_from_string((string)text);
+            set_tab_name(true);
         }
 
         public string get_text() {
@@ -139,6 +169,16 @@ namespace Publicate.Editors {
             scroller.vadjustment.value = (scroller.vadjustment.upper - scroller.vadjustment.page_size)*frac;
         }
 
+        private void set_tab_name(bool unsaved) {
+            this.unsaved = unsaved;
+            if(unsaved) {
+                tab_page.title = @"⏺  $(asset.name)";
+            }
+            else {
+                tab_page.title = asset.name;
+            }
+        }
+
         
     }
 }

+ 139 - 0
src/Editors/MetadataEditor.vala

@@ -0,0 +1,139 @@
+using Gtk;
+using Adw;
+using GtkCommonMark;
+using Invercargill;
+
+namespace Publicate.Editors {
+
+    public class MetadataEditor : Box, EditorWidget, Savable {
+
+        private Ppub.Publication publication;
+        private Ppub.Metadata metadata;
+        private Ppub.Asset asset;
+        private TabPage page;
+
+        private EntryRow title;
+        private EntryRow author;
+        private EntryRow author_email;
+        private FileChooserRow poster;
+        private EntryRow description;
+        private EntryRow tags;
+
+        private FileChooserRow licence;
+        private EntryRow copyright;
+
+        private ViewerWindow window;
+
+        private bool unsaved = false;
+        public bool has_unsaved_changes { get{
+            return unsaved;
+        } }
+
+
+        public MetadataEditor(ViewerWindow win, TabView tab_view) {
+            window = win;
+
+            var scroll_window = new ScrolledWindow ();
+            scroll_window.hexpand = true;
+            scroll_window.vexpand = true;
+            var box = new Box(Orientation.VERTICAL, 0);
+            scroll_window.child = box;
+            append(scroll_window);
+
+
+            var group = new PreferencesGroup();
+            group.margin_bottom = 18;
+            group.margin_end = 18;
+            group.margin_start = 18;
+            group.margin_top = 18;
+            group.title = "Publication Details";
+            group.description = "";
+
+            title = new EntryRow ();
+            title.title = "Title";
+            title.changed.connect (() => metadata.title = title.text);
+            group.add(title);
+
+            author = new EntryRow ();
+            author.title = "Author";
+            group.add(author);
+
+            author_email = new EntryRow ();
+            author_email.title = "Author Email";
+            group.add(author_email);
+
+            poster = new FileChooserRow (window);
+            poster.title = "Poster Image";
+            group.add(poster);
+
+            description = new EntryRow ();
+            description.enable_emoji_completion = true;
+            description.title = "Description";
+            group.add(description);
+
+            tags = new EntryRow ();
+            tags.title = "Tags (space separated)";
+            group.add(tags);
+
+            box.append(group);
+
+            var group2 = new PreferencesGroup();
+            group2.margin_bottom = 18;
+            group2.margin_end = 18;
+            group2.margin_start = 18;
+            group2.margin_top = 18;
+            group2.title = "Licence and Copyright";
+            group2.description = "";
+
+            copyright = new EntryRow ();
+            copyright.title = "Copyright Notice";
+            group2.add(copyright);
+
+            licence = new FileChooserRow (window);
+            licence.title = "Licence";
+            group2.add(licence);
+
+            box.append(group2);
+
+            this.page = tab_view.add_page (this, null);
+            this.page.title = "Publication Metadata";
+        }
+
+        public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+            this.asset = asset;
+            this.publication = publication;
+
+            var stream = publication.read_asset (asset.name);
+            metadata = new Ppub.Metadata.from_stream (stream);
+
+            poster.set_assets(publication.assets.where(a => a.mimetype.has_prefix ("image/")));
+            licence.set_assets(publication.assets.where(a => a.mimetype.has_prefix ("text/")));
+
+            title.text = metadata.title ?? "";
+            author.text = metadata.author_name ?? "";
+            author_email.text = metadata.author_email ?? "";
+            poster.set_selected_item (metadata.poster ?? "");
+            description.text = metadata.description ?? "";
+            tags.text = ate(metadata.tags).to_string (a => a, " ");
+
+            copyright.text = metadata.copyright ?? "";
+            licence.set_selected_item (metadata.licence ?? "");
+
+
+        }
+
+        public string asset_name { get {
+            return asset.name;
+        } }
+
+        public Adw.TabPage tab_page { get {
+            return page;
+        }}
+
+        public async void save_asset (Ppub.Builder builder) {
+                assert_not_reached ();
+        }
+    }
+
+
+}

+ 106 - 52
src/Editors/PlainTextEditor.vala

@@ -1,56 +1,110 @@
-//  using Gtk;
-//  using Adw;
-//  using GtkCommonMark;
-
-//  namespace Publicate.Editors {
-
-//      public class PlainTextEditor : Box, EditorWidget {
-
-//          private ScrolledWindow scrolled_window;
-//          private ClampScrollable clamp;
-//          private TextView text_view;
-
-//          private TextTag tag;
-
-//          public PlainTextEditor() {
-//              orientation = Orientation.VERTICAL;
-//              scrolled_window = new ScrolledWindow ();
-//              clamp = new ClampScrollable ();
-//              clamp.maximum_size = 800;
-//              scrolled_window.child = clamp;
-//              scrolled_window.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
-//              scrolled_window.vexpand = true;
-
-//              text_view = new TextView ();
-//              tag = text_view.buffer.create_tag (null);
-//              text_view.monospace = true;
-//              text_view.set_wrap_mode (WrapMode.WORD_CHAR);
-//              text_view.top_margin = 18;
-//              text_view.bottom_margin = 18;
-//              text_view.left_margin = 18;
-//              text_view.right_margin = 18;
-//              clamp.child = text_view;
-
-//              append(scrolled_window);
-//          }
-
-//          public void set_zoom_percentage (int percent) {
-//              var scale = (float)percent / 100f;
-//              tag.size_points = scale * 12;
-//          }
+using Gtk;
+using Adw;
+using GtkCommonMark;
+
+namespace Publicate.Editors {
+
+    public class PlainTextEditor : Box, EditorWidget, Savable {
+
+        private Ppub.Publication publication;
+        private TextView text_view;
+        
+        private ScrolledWindow scroller;
+
+        private ViewerWindow window;
+        private TabPage page;
+
+        public Adw.TabPage tab_page { get {
+            return page;
+        } }
+
+        public string asset_name { get{
+            return asset.name;
+        } }
+
+        private bool unsaved = false;
+        public bool has_unsaved_changes { get{
+            return unsaved;
+        } }
+
+
+        private Ppub.Asset asset;
+
+        public PlainTextEditor(ViewerWindow win, TabView tab_view) {
+            window = win;
+
+            orientation = Orientation.VERTICAL;
+            scroller = new ScrolledWindow ();
+
+            text_view = new TextView();
+            text_view.monospace = true;
+            text_view.hexpand = true;
+            text_view.set_wrap_mode (WrapMode.WORD_CHAR);
+            text_view.top_margin = 18;
+            text_view.bottom_margin = 18;
+            text_view.left_margin = 18;
+            text_view.right_margin = 18;
+            text_view.buffer.changed.connect (text_changed);
+            
+
+            scroller.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
+            scroller.vexpand = true;
+
+            scroller.child = text_view;
+
+            append(scroller);
+
+            page = tab_view.add_page (this, null);
+        }
         
-//          public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
-//              text_view.buffer.set_text("", 0);
-//              MemoryOutputStream os = new MemoryOutputStream (null, GLib.realloc, GLib.free);
-//              yield os.splice_async (publication.read_asset (asset.name), OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET);
-//              var text = os.steal_data ();
-//              text.length = (int) os.get_data_size ();
+        public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+            this.publication = publication;
+            this.asset = asset;
+
+            text_view.buffer.set_text("", 0);
+            MemoryOutputStream os = new MemoryOutputStream (null, GLib.realloc, GLib.free);
+            yield os.splice_async (publication.read_asset (asset.name), OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET);
+            var text = os.steal_data ();
+            text.length = (int) os.get_data_size ();
 
-//              Gtk.TextIter iter;
-//              text_view.buffer.get_start_iter (out iter);
-//              text_view.buffer.insert_with_tags (ref iter, (string)text, text.length, tag);
-//          }
+            text_view.buffer.set_text ((string)text, text.length);
+            set_tab_name(false);
+        }
 
-//      }
+        public override async void save_asset(Ppub.Builder builder) {
+            var article_data = get_text().to_utf8();
+            var article_stream = new MemoryInputStream.from_data((uint8[])article_data);
+            var compression_info = new Ppub.CompressionInfo(article_stream);
+            article_stream.seek(0, SeekType.SET);
 
-//  }
+            builder.add_asset(asset.name, asset.mimetype, article_stream, Invercargill.empty<string>(), compression_info);
+            set_tab_name(false);
+        }
+
+        private void text_changed() {
+            set_tab_name(true);
+        }
+
+        public string get_text() {
+            TextIter start_iter;
+            TextIter end_iter;
+            text_view.buffer.get_start_iter (out start_iter);
+            text_view.buffer.get_end_iter (out end_iter);
+
+            var text = text_view.buffer.get_text (start_iter, end_iter, true);
+            return text;
+        }
+
+        private void set_tab_name(bool unsaved) {
+            if(unsaved) {
+                tab_page.title = @"⏺  $(asset.name)";
+            }
+            else {
+                tab_page.title = asset.name;
+            }
+            this.unsaved = unsaved;
+        }
+
+        
+    }
+}

+ 82 - 0
src/FileChooser.vala

@@ -0,0 +1,82 @@
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class FileChooserPopover : Popover {
+
+        private ViewerWindow toplevel;
+        private FileExplorer explorer;
+
+        public signal void asset_selected(Ppub.Asset asset);
+
+        public FileChooserPopover(ViewerWindow window) {
+
+            toplevel = window;
+            var box = new Box(Orientation.VERTICAL, 8);
+            explorer = new FileExplorer();
+            explorer.height_request = 200;
+            explorer.asset_selected.connect(selected);
+
+            box.append(explorer);
+            child = box;
+        }
+
+        public void set_assets(Invercargill.Enumerable<Ppub.Asset> files) {
+            explorer.set_assets(files);
+        }
+
+        public void set_selected_item(string name) {
+            explorer.set_selected_item(name);
+        }
+
+        private void selected(Ppub.Asset asset) {
+            popdown();
+            asset_selected(asset);
+        }
+
+    }
+
+    public class FileChooserRow : ActionRow {
+
+        public signal void asset_selected(Ppub.Asset asset);
+
+        private ViewerWindow toplevel;
+        private FileChooserPopover popover;
+
+        public FileChooserRow(ViewerWindow window) {
+            toplevel = window;
+            subtitle = "Not selected";
+            popover = new FileChooserPopover(window);
+            popover.asset_selected.connect(selected);
+
+            add_suffix(popover);
+            var button = new Button.from_icon_name("document-open-symbolic");
+            button.clicked.connect(() => popover.popup());
+            button.margin_bottom = 8;
+            button.margin_top = 8;
+            button.add_css_class("flat");
+            add_suffix(button);
+
+            activatable_widget = button;
+            activatable = true;
+        }
+
+        public void set_assets(Invercargill.Enumerable<Ppub.Asset> files) {
+            popover.set_assets(files);
+        }
+
+        private void selected(Ppub.Asset asset) {
+            subtitle = asset.name;
+            asset_selected(asset);
+        }
+
+        public void set_selected_item(string name) {
+            subtitle = name;
+            popover.set_selected_item(name);
+        }
+
+
+    }
+
+}

+ 16 - 2
src/FileExplorer.vala

@@ -10,6 +10,7 @@ namespace Publicate {
         private ScrolledWindow scroll_window;
 
         private Invercargill.Sequence<Ppub.Asset> assets;
+        private Invercargill.Sequence<FileItem> file_items;
 
         public signal void asset_selected(Ppub.Asset asset);
 
@@ -17,12 +18,16 @@ namespace Publicate {
 
         public FileExplorer() {
             scroll_window = new ScrolledWindow();
-            file_list = new ListBox();
-            scroll_window.child = file_list;
             scroll_window.hscrollbar_policy = PolicyType.NEVER;
             scroll_window.propagate_natural_width = true;
             append(scroll_window);
 
+            create_list();
+        }
+
+        private void create_list() {
+            file_list = new ListBox();
+            scroll_window.child = file_list;
             file_list.row_activated.connect((row) => {
                 var item = (FileItem)row;
                 selected_asset = item.asset;
@@ -32,15 +37,24 @@ namespace Publicate {
         
         public void set_assets(Invercargill.Enumerable<Ppub.Asset> files) {
 
+            create_list();
+
             assets = files.to_sequence();
+            file_items = new Invercargill.Sequence<FileItem>();
             
             foreach(var asset in assets) {
                 var item = new FileItem(asset);
                 file_list.append(item);
+                file_items.add(item);
             }
             
         }
 
+        public void set_selected_item(string name) {
+            var item = file_items.where(i => i.asset.name == name).first_or_default();
+            file_list.select_row(item);
+        }
+
     }
 
     public class FileItem : ActionRow {

+ 44 - 0
src/Savable.vala

@@ -0,0 +1,44 @@
+
+namespace Publicate {
+    public interface Savable : Object {
+        public abstract async void save_asset(Ppub.Builder builder) throws Error;
+    }
+
+    public class SavableAsset : Object, Savable {
+
+        private Ppub.Publication publication;
+        private Ppub.Asset asset;
+
+        public SavableAsset(Ppub.Publication origin, Ppub.Asset asset) {
+            publication = origin;
+            this.asset = asset;
+        }
+
+        public async void save_asset(Ppub.Builder builder) throws Error {
+            var stream = publication.read_asset(asset.name);
+            var compression = new Ppub.CompressionInfo(stream);
+            stream = publication.read_asset(asset.name);
+
+            builder.add_asset(asset.name, asset.mimetype, stream, Invercargill.empty<string>(), compression);
+        }
+    }
+
+    public class SavableNewAsset : Object, Savable {
+
+        private string name;
+        private string mimetype;
+        private InputStream stream;
+        private Ppub.CompressionInfo compression;
+
+        public SavableNewAsset(string name, string mimetype, InputStream stream, Ppub.CompressionInfo compression) {
+            this.name = name;
+            this.mimetype = mimetype;
+            this.stream = stream;
+            this.compression = compression;
+        }
+
+        public async void save_asset(Ppub.Builder builder) throws Error {
+            builder.add_asset(name, mimetype, stream, Invercargill.empty<string>(), compression);
+        }
+    }
+}

+ 1 - 1
src/Window.vala

@@ -8,7 +8,7 @@ namespace Publicate {
 
         private Stack stack;
         private StartupMenu startup_menu;
-        private PpubEditor editor;
+        public PpubEditor editor {get; set;}
 
         private SimpleAction extract_action;
 

+ 3 - 0
src/meson.build

@@ -8,9 +8,12 @@ sources += files('ZoomSpinButton.vala')
 sources += files('StartupMenu.vala')
 sources += files('FileExplorer.vala')
 sources += files('Editor.vala')
+sources += files('Savable.vala')
+sources += files('FileChooser.vala')
 sources += files('Editors/EditorWidget.vala')
 sources += files('Editors/MarkdownEditor.vala')
 sources += files('Editors/PlainTextEditor.vala')
+sources += files('Editors/MetadataEditor.vala')
 sources += files('Editors/UnsupportedEditor.vala')
 sources += files('Wizards/Wizard.vala')
 sources += files('Wizards/Standard.vala')