Explorar el Código

Initial commit

Billy Barrow hace 2 años
commit
bf890af9f3

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+meson-*
+publicate
+publicate.p/
+.ninja*
+*.ninja
+compile_commands.json

+ 34 - 0
src/App.vala

@@ -0,0 +1,34 @@
+using Gtk;
+
+
+namespace Publicate {
+
+    public class PpubViewerApplication : Adw.Application {
+
+        public PpubViewerApplication() {
+            Object(application_id: "nz.barrow.billy.publicate", flags: GLib.ApplicationFlags.HANDLES_OPEN);
+        }
+
+        protected override void activate () {
+            var window = new ViewerWindow(this);
+            window.present ();
+        }
+
+        protected override void open (File[] files, string hint) {
+            foreach (var file in files) {
+                var window = new ViewerWindow(this);
+                window.present ();
+                window.load_ppub.begin(file);
+            }
+        }
+
+    }
+
+    int main (string[] argv) {
+        // Create a new application
+        Gst.init (ref argv);
+        var app = new PpubViewerApplication();
+        return app.run (argv);
+    }
+    
+}

+ 71 - 0
src/Editor.vala

@@ -0,0 +1,71 @@
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class PpubEditor : Box {
+
+        private Adw.HeaderBar header;
+        private WindowTitle header_title;
+
+        private Leaflet leaflet;
+
+        private Ppub.Publication publication;
+        private ViewerWindow window;
+
+        private FileExplorer file_explorer;
+        private Box tab_box;
+        private TabView tab_view;
+        private TabBar tab_bar;
+
+        public PpubEditor(ViewerWindow win) {
+            window = win;
+
+            orientation = Orientation.VERTICAL;
+            header = new Adw.HeaderBar();
+            append(header);
+
+            header_title = new WindowTitle("Untitled Publication", "");
+            header.title_widget = header_title;
+
+            file_explorer = new FileExplorer();
+            file_explorer.asset_selected.connect(open_asset);
+
+            leaflet = new Leaflet();
+            leaflet.vexpand = true;
+            leaflet.append(file_explorer);
+            
+            tab_view = new TabView();
+            tab_bar = new TabBar();
+            tab_box = new Box(Orientation.VERTICAL, 0);
+            tab_bar.set_view(tab_view);
+            tab_box.append(tab_bar);
+            tab_box.append(tab_view);
+            leaflet.append(tab_box);
+
+            append(leaflet);
+        }
+
+        public async void load_ppub(File file) throws Error {
+
+            publication = new Ppub.Publication(file.get_path());
+
+            header_title.title = publication.metadata.title ?? "Untitled PPUB";
+            header_title.subtitle = publication.metadata.author_name ?? "";
+            window.title = header_title.title + " - Publicate!";
+
+            file_explorer.set_assets(publication.assets);
+
+        }
+
+        public async void open_asset(Ppub.Asset asset) {
+            
+            if(asset.mimetype == "text/markdown") {
+                var editor = new Editors.MarkdownEditor(window, tab_view);
+                editor.load_asset(publication, asset);
+            }
+
+        }
+
+    }
+}

+ 7 - 0
src/Editors/EditorWidget.vala

@@ -0,0 +1,7 @@
+
+namespace Publicate.Editors {
+    public interface EditorWidget : Gtk.Widget {
+        public abstract void set_zoom_percentage(int percent);
+        public abstract async void load_asset(Ppub.Publication publication, Ppub.Asset asset);
+    }
+}

+ 85 - 0
src/Editors/MarkdownEditor.vala

@@ -0,0 +1,85 @@
+using Gtk;
+using Adw;
+using GtkCommonMark;
+
+namespace Publicate.Editors {
+
+    public class MarkdownEditor : Box, EditorWidget {
+
+        protected ScrolledWindow scrolled_window;
+        protected ClampScrollable clamp;
+        protected GtkCommonMark.MarkdownView markdown_view;
+        private Ppub.Publication publication;
+
+        private ViewerWindow window;
+        private TabPage page;
+
+        public MarkdownEditor(ViewerWindow win, TabView tab_view) {
+            window = win;
+
+            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;
+
+            markdown_view = new GtkCommonMark.MarkdownView ();
+            markdown_view.set_wrap_mode (WrapMode.WORD_CHAR);
+            markdown_view.widget_embedded.connect(widget_embedded);
+            clamp.child = markdown_view;
+
+            Gtk.Settings.get_default().notify["gtk-application-prefer-dark-theme"].connect(() => configure_tags());
+            configure_tags();
+
+            append(scrolled_window);
+
+            page = tab_view.add_page (this, null);
+        }
+
+        public double get_scroll_position () {
+            return scrolled_window.vadjustment.get_value ();
+        }
+
+        public void set_zoom_percentage (int percent) {
+            var scale = (float)percent / 100f;
+            markdown_view.tag_manager.font_scale = scale;
+        }
+
+        public void set_scroll_position (double position) {
+            scrolled_window.vadjustment.set_value (position);
+        }
+        
+        public virtual async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+            this.publication = publication;
+            markdown_view.buffer.set_text("", 0);
+            page.title = asset.name;
+            yield markdown_view.load_from_stream_async (publication.read_asset (asset.name));
+        }
+
+        protected async void widget_embedded(GtkCommonMark.MarkdownViewEmbeddedWidgetHost widget, string file, string title) {
+            var image = new Gtk.Picture();
+            image.content_fit = Gtk.ContentFit.FILL;
+            widget.append (image);
+
+            var pixbuf = yield new Gdk.Pixbuf.from_stream_async (publication.read_asset (file), null);
+            image.set_pixbuf (pixbuf);
+
+            widget.available_width_changed.connect(wid => {
+                image.width_request = wid;
+                var ratio = image.get_paintable().get_intrinsic_aspect_ratio ();
+                image.height_request = (int)(wid / ratio);
+            });
+        }
+
+
+        private void configure_tags() {
+            var link = new LinkButton("");
+            markdown_view.tag_manager.update_link_colour(link.get_color());
+        }
+
+        
+    }
+
+}

+ 56 - 0
src/Editors/PlainTextEditor.vala

@@ -0,0 +1,56 @@
+//  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;
+//          }
+        
+//          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 ();
+
+//              Gtk.TextIter iter;
+//              text_view.buffer.get_start_iter (out iter);
+//              text_view.buffer.insert_with_tags (ref iter, (string)text, text.length, tag);
+//          }
+
+//      }
+
+//  }

+ 31 - 0
src/Editors/UnsupportedEditor.vala

@@ -0,0 +1,31 @@
+//  using Adw;
+//  using Gtk;
+
+//  namespace Publicate.Editors {
+
+//      public class UnsupportedEditor : Box, EditorWidget {
+
+//          private StatusPage status_page;
+
+//          public void set_zoom_percentage (int percent) {
+//              return;
+//          }
+
+//          public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+//              status_page.description = @"Files of type '$(asset.mimetype)' cannot be displayed by this application.";
+//          }
+
+//          public UnsupportedEditor() {
+//              orientation = Orientation.VERTICAL;
+
+//              status_page = new StatusPage();
+//              status_page.vexpand = true;
+//              status_page.title = "Unsupported Content Type";
+//              status_page.icon_name = "face-uncertain-symbolic";
+
+//              append(status_page);
+
+//          }
+
+//      }
+//  }

+ 63 - 0
src/FileExplorer.vala

@@ -0,0 +1,63 @@
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class FileExplorer : Box {
+
+        
+        private ListBox file_list;
+        private ScrolledWindow scroll_window;
+
+        private Invercargill.Sequence<Ppub.Asset> assets;
+
+        public signal void asset_selected(Ppub.Asset asset);
+
+        public Ppub.Asset? selected_asset {get; private set;}
+
+        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);
+
+            file_list.row_activated.connect((row) => {
+                var item = (FileItem)row;
+                selected_asset = item.asset;
+                asset_selected(item.asset);
+            });
+        }
+        
+        public void set_assets(Invercargill.Enumerable<Ppub.Asset> files) {
+
+            assets = files.to_sequence();
+            
+            foreach(var asset in assets) {
+                var item = new FileItem(asset);
+                file_list.append(item);
+            }
+            
+        }
+
+    }
+
+    public class FileItem : ActionRow {
+
+        public Ppub.Asset asset {get; private set;}
+
+        public FileItem(Ppub.Asset asset) {
+            this.asset = asset;
+
+            activatable = true;
+            title = asset.name;
+            subtitle = ContentType.get_description (asset.mimetype);
+            
+            var icon = new Image.from_gicon (ContentType.get_icon (asset.mimetype));
+            add_prefix (icon);
+        }
+        
+
+    }
+}

+ 84 - 0
src/StartupMenu.vala

@@ -0,0 +1,84 @@
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class StartupMenu : Box {
+
+        private Adw.HeaderBar header_bar;
+        private Box box;
+        private Stack stack;
+
+        private Wizards.StandardWizard standard_wizard;
+        private ViewerWindow window;
+
+        public StartupMenu(ViewerWindow win) {
+            window = win;
+            orientation = Orientation.VERTICAL;
+            vexpand = true;
+
+            header_bar = new Adw.HeaderBar ();
+            header_bar.add_css_class ("flat");
+            header_bar.show_end_title_buttons = true;
+            header_bar.title_widget = new Adw.WindowTitle ("", "");
+
+            stack = new Stack();
+            stack.transition_type = StackTransitionType.SLIDE_LEFT_RIGHT;
+
+            append(header_bar);
+            append(stack);
+
+            box = new Box(Orientation.VERTICAL, 18);
+            box.valign = Align.CENTER;
+            box.halign = Align.CENTER;
+            box.vexpand = true;
+            box.margin_bottom = 40;
+            stack.add_child (box);
+
+
+            var title = new Label("Publicate!");
+            title.add_css_class("title-1");
+            box.append(title);
+
+            var action_list = new ListBox ();
+            action_list.add_css_class ("boxed-list");
+            box.append(action_list);
+
+            var new_action_row = new ActionRow();
+            new_action_row.title = "New Publication";
+            new_action_row.subtitle = "Create a new publication using Markdown for text formatting.";
+            new_action_row.activatable = true;
+            new_action_row.activated.connect(() => stack.visible_child = standard_wizard);
+            action_list.append (new_action_row);
+
+            var new_video_action_row = new ActionRow();
+            new_video_action_row.title = "New Video Publication";
+            new_video_action_row.subtitle = "Create a video publication from an existing video.";
+            action_list.append (new_video_action_row);
+
+            var edit_action_row = new ActionRow();
+            edit_action_row.title = "Open Existing Publication";
+            edit_action_row.subtitle = "Edit a publication that has already been created.";
+            edit_action_row.activatable = true;
+            action_list.append (edit_action_row);
+            edit_action_row.activated.connect(() => window.open_ppub.begin());
+
+            standard_wizard = new Wizards.StandardWizard(window);
+            standard_wizard.cancelled.connect(wizard_cancelled);
+            standard_wizard.open.connect(open_file);
+            stack.add_child(standard_wizard);
+
+        }
+
+
+        private void wizard_cancelled() {
+            stack.visible_child = box;
+        }
+
+        private void open_file(File file) {
+            window.load_ppub(file);
+        }
+
+
+    }
+}

+ 57 - 0
src/Window.vala

@@ -0,0 +1,57 @@
+
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class ViewerWindow : Adw.ApplicationWindow {
+
+        private Stack stack;
+        private StartupMenu startup_menu;
+        private PpubEditor editor;
+
+        private SimpleAction extract_action;
+
+        public ViewerWindow(Adw.Application app) {
+            application = app;
+            stack = new Stack();
+            startup_menu = new StartupMenu(this);
+            editor = new PpubEditor(this);
+
+            default_height = 600;
+            default_width = 800;
+
+            content = stack;
+            stack.add_child(startup_menu);
+            stack.add_child(editor);
+            stack.transition_type = StackTransitionType.CROSSFADE;
+        }
+
+        public async void open_ppub() throws Error {
+
+            var dialog = new FileDialog();
+            var filter = new FileFilter();
+            var filters = new GLib.ListStore(Type.OBJECT);
+            filters.append(filter);
+            filter.add_pattern("*.ppub");
+            filter.name = "Portable Publications";
+            dialog.filters = filters;
+            var file = yield dialog.open(this, null);
+
+            if(file == null) {
+                return;
+            }
+            
+            yield load_ppub(file);
+
+        }
+
+        public async void load_ppub(File file) {
+            stack.visible_child = editor;
+            yield editor.load_ppub(file);
+        }
+
+
+    }
+
+}

+ 116 - 0
src/Wizards/Standard.vala

@@ -0,0 +1,116 @@
+using Gtk;
+using Adw;
+
+namespace Publicate.Wizards {
+
+    public class StandardWizard : Box, Wizard {
+
+        private EntryRow title;
+        private EntryRow author;
+        private EntryRow author_email;
+
+        private ViewerWindow window;
+
+        public StandardWizard(ViewerWindow win) {
+            window = win;
+            orientation = Orientation.VERTICAL;
+            spacing = 18;
+            valign = Align.CENTER;
+            halign = Align.CENTER;
+            vexpand = true;
+            margin_bottom = 40;
+
+            
+            var group = new PreferencesGroup();
+            group.width_request = 300;
+            group.title = "Publication Details";
+            group.description = "These can be changed later";
+
+            title = new EntryRow ();
+            title.title = "Title";
+            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);
+
+            append(group);
+
+
+            var back_button = new Button.with_label ("Cancel");
+            back_button.hexpand = true;
+            back_button.clicked.connect (() => cancelled());
+
+            var next_button = new Button.with_label ("Create");
+            next_button.hexpand = true;
+            next_button.add_css_class ("suggested-action");
+            next_button.clicked.connect (() => create_ppub());
+
+            var button_box = new Box(Orientation.HORIZONTAL, 0);
+            button_box.add_css_class ("linked");
+
+            button_box.append (back_button);
+            button_box.append (next_button);
+            button_box.vexpand = true;
+            append(button_box);
+        }
+
+        private async void create_ppub() {
+
+            var dialog = new FileDialog();
+            var filter = new FileFilter();
+            var filters = new GLib.ListStore(Type.OBJECT);
+            filters.append(filter);
+            filter.add_pattern("*.ppub");
+            filter.name = "Portable Publications";
+            dialog.filters = filters;
+            var file = yield dialog.save(window, null);
+
+            if(file == null) {
+                return;
+            }
+
+            var metadata = new Ppub.Metadata();
+            var email = author_email.text.chomp().chug();
+            if(email != "") {
+                metadata.author = @"$(author.text) <$email>";
+            }
+            else {
+                metadata.author = author.text;
+            }
+
+            metadata.title = title.text;
+            
+            print(metadata.to_string());
+
+            var builder = new Ppub.Builder();
+            builder.add_metadata(metadata);
+
+            var article_data = @"# $(title.text)\n\n".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("article.md", "text/markdown", article_stream, Invercargill.empty<string>(), compression_info);
+
+            var output_stream = file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION);
+            builder.write(output_stream);
+
+            output_stream.close();
+
+            open(file);
+        }
+
+        public void reset() {
+            title.text = "";
+            author.text = "";
+            author_email.text = "";
+        }
+
+    }
+
+}

+ 0 - 0
src/Wizards/Video.vala


+ 11 - 0
src/Wizards/Wizard.vala

@@ -0,0 +1,11 @@
+namespace Publicate.Wizards {
+
+    public interface Wizard : Gtk.Widget {
+
+        public signal void cancelled();
+        public signal void open(File file);
+
+        public abstract void reset();
+
+    }
+}

+ 65 - 0
src/ZoomSpinButton.vala

@@ -0,0 +1,65 @@
+using Adw;
+using Gtk;
+
+namespace Publicate {
+
+    public class ZoomSpinButton : Gtk.Box {
+
+        private Button zoom_out_button;
+        private Button reset_zoom_button;
+        private Button zoom_in_button;
+
+        public Adjustment adjustment {get; set;}
+
+        public int default_value {get; set;}
+
+        public ZoomSpinButton(bool compact) {
+            orientation = Orientation.HORIZONTAL;
+            halign = Align.END;
+
+            
+            zoom_out_button = new Button.from_icon_name ("zoom-out-symbolic");
+            zoom_in_button = new Button.from_icon_name ("zoom-in-symbolic");
+            adjustment = new Adjustment(100, 80, 300, 10, 50, 0);
+            default_value = 100;
+            
+            
+            adjustment.value_changed.connect(() => zoom_level_changed((int)adjustment.value));
+            
+            append(zoom_out_button);
+            if(!compact) {
+                reset_zoom_button = new Button();
+                append(reset_zoom_button);
+                add_css_class("linked");
+                zoom_level_changed.connect(update_label);
+                update_label(default_value);
+            }
+            append(zoom_in_button);
+            
+            zoom_in_button.clicked.connect(zoom_in);
+            zoom_out_button.clicked.connect(zoom_out);
+            reset_zoom_button.clicked.connect(reset_zoom);
+
+        }
+
+        public signal void zoom_level_changed(int percentage);
+
+        private void update_label(int percentage) {
+            reset_zoom_button.label = @"$percentage%";
+            zoom_out_button.sensitive = percentage != adjustment.lower;
+            zoom_in_button.sensitive = percentage != adjustment.upper;
+        }
+
+        public void zoom_in() {
+            adjustment.value += adjustment.step_increment;
+        }
+
+        public void zoom_out() {
+            adjustment.value -= adjustment.step_increment;
+        }
+
+        public void reset_zoom() {
+            adjustment.value = default_value;
+        }
+    }
+}

+ 32 - 0
src/meson.build

@@ -0,0 +1,32 @@
+project('publicate', 'vala', 'c')
+
+add_project_arguments(['--disable-warnings', '--enable-checking'], language: 'vala')
+
+sources = files('App.vala')
+sources += files('Window.vala')
+sources += files('ZoomSpinButton.vala')
+sources += files('StartupMenu.vala')
+sources += files('FileExplorer.vala')
+sources += files('Editor.vala')
+sources += files('Editors/EditorWidget.vala')
+sources += files('Editors/MarkdownEditor.vala')
+sources += files('Editors/PlainTextEditor.vala')
+sources += files('Editors/UnsupportedEditor.vala')
+sources += files('Wizards/Wizard.vala')
+sources += files('Wizards/Standard.vala')
+sources += files('Wizards/Video.vala')
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gee-0.8'),
+    dependency('libadwaita-1'),
+    dependency('invercargill'),
+    dependency('gtk4'),
+    dependency('gtkcommonmark'),
+    dependency('libppub'),
+    dependency('gstreamer-1.0')
+]
+
+executable('publicate', sources, dependencies: dependencies)