Explorar o código

Add navigation and properties

Billy Barrow %!s(int64=2) %!d(string=hai) anos
pai
achega
02c5ee9af4
Modificáronse 4 ficheiros con 282 adicións e 6 borrados
  1. 24 0
      src/NavigationStack.vala
  2. 76 0
      src/PropertiesPane.vala
  3. 180 6
      src/Window.vala
  4. 2 0
      src/meson.build

+ 24 - 0
src/NavigationStack.vala

@@ -0,0 +1,24 @@
+
+namespace PpubViewer {
+
+    public class NavigationHistoryItem {
+
+        public NavigationHistoryItem? previous {get; set;}
+
+        public NavigationHistoryItem? next {get; set;}
+
+        public string file_name {get; set;}
+        public double scroll_position {get; set;}
+
+        public NavigationHistoryItem add_next(string file, double scroll_pos) {
+            scroll_position = scroll_pos;
+            next = new NavigationHistoryItem();
+            next.scroll_position = 0;
+            next.file_name = file;
+            next.previous = this;
+            return next;
+        }
+
+    }
+
+}

+ 76 - 0
src/PropertiesPane.vala

@@ -0,0 +1,76 @@
+
+
+using Adw;
+using Gtk;
+
+namespace PpubViewer {
+
+    public class PropertiesPane : Box {
+
+        public signal void link_clicked(string file);
+
+        public PropertiesPane(Ppub.Metadata metadata, bool small) {
+            orientation = Orientation.VERTICAL;
+            margin_start = 18;
+            margin_end = 18;
+            margin_bottom = 18;
+
+            width_request = 180;
+            halign = Align.START;
+            set_metadata(metadata);
+        }
+
+        private void set_metadata(Ppub.Metadata metadata) {
+
+            if(has_value(metadata.title))
+                add_property("Title", metadata.title);
+
+            if(has_value(metadata.author)){
+                if(metadata.author_email != null)
+                    add_property("Author", @"<a href=\"mailto:$(metadata.author_email)\">$(metadata.author_name)</a>", true);
+                else
+                    add_property("Author", metadata.author);
+            }
+
+            if(metadata.date != null)
+                add_property("Last Modified", metadata.date.to_local().format("%c"));
+
+            if(has_value(metadata.description))
+                add_property("Description", metadata.description);
+
+            if(has_value(metadata.copyright))
+                add_property("Copyright", metadata.copyright);
+
+            if(has_value(metadata.licence))
+                add_property("Licence", @"<a href=\"$(Uri.escape_string(metadata.licence))\">See Licence</a>", true);
+
+        }
+
+        private bool has_value(string? value) {
+            return value != null && value.chomp().length > 0;
+        }
+
+        private void add_property(string name, string body, bool use_markup = false) {
+            var title = new Label(name);
+            var info = new Label(body);
+            info.use_markup = use_markup;
+            title.margin_top = 18;
+
+            title.halign = Align.START;
+            title.add_css_class("heading");
+            info.halign = Align.START;
+            info.wrap = true;
+            info.add_css_class("body");
+
+            info.activate_link.connect(url => {
+                link_clicked(url);
+                return true;
+            });
+
+            append(title);
+            append(info);
+        }
+
+    }
+
+}

+ 180 - 6
src/Window.vala

@@ -9,13 +9,26 @@ namespace PpubViewer {
         private Adw.HeaderBar header_bar;
         private Box box;
         private Flap flap;
+        private ClampScrollable clamp;
         private GtkCommonMark.MarkdownView markdown_view;
+        private ScrolledWindow scrolled_window;
+
+        private Button back_button;
+        private Button forward_button;
+        private Button properties_button;
+
+        private Button menu_button;
+        private PopoverMenu menu;
+        private SpinButton zoom_spin;
 
         private Ppub.Publication publication;
+        private NavigationHistoryItem current_page = null;
+        private WindowTitle window_title;
 
         public ViewerWindow(Adw.Application app) {
             application = app;
             header_bar = new Adw.HeaderBar ();
+            window_title = new WindowTitle("PPUB Viewer", "");
             flap = new Flap();
             box = new Box (Orientation.VERTICAL, 0);
             box.append (header_bar);
@@ -24,25 +37,120 @@ namespace PpubViewer {
             header_bar.show_end_title_buttons = true;
             default_width = 600;
             default_height = 400;
+            header_bar.title_widget = window_title;
+            flap.separator = new Separator(Orientation.VERTICAL);
+
+            properties_button = new Button.from_icon_name("sidebar-show-symbolic");
+            header_bar.pack_start(properties_button);
+
+            header_bar.pack_start(new Separator(Orientation.VERTICAL));
+
+            back_button = new Button.from_icon_name("go-previous-symbolic");
+            back_button.sensitive = false;
+            back_button.clicked.connect(go_back);
+            header_bar.pack_start(back_button);
+            forward_button = new Button.from_icon_name("go-next-symbolic");
+            forward_button.sensitive = false;
+            forward_button.clicked.connect(go_forward);
+            header_bar.pack_start(forward_button);
+
+            menu = new PopoverMenu.from_model(null);
+            menu_button = new Button.from_icon_name("open-menu-symbolic");
+            menu_button.clicked.connect(() => menu.popdown());
+            header_bar.pack_end(menu_button);
+            var zoom_adjustment = new Adjustment(100, 80, 300, 10, 50, 0);
+            zoom_adjustment.value_changed.connect(change_zoom);
+            zoom_spin = new SpinButton(zoom_adjustment, 1, 0);
+            header_bar.pack_end(zoom_spin);
+            
 
-            var scrolled_window = new ScrolledWindow();
+            scrolled_window = new ScrolledWindow();
             scrolled_window.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
             scrolled_window.vexpand = true;
-            var clamp = new ClampScrollable ();
-            markdown_view = new GtkCommonMark.MarkdownView ();
+            clamp = new ClampScrollable ();
             clamp.maximum_size = 800;
+            scrolled_window.child = clamp;
+
+            var status_page = new StatusPage();
+            status_page.vexpand = true;
+            status_page.title = "PPUB Viewer";
+            status_page.description = "Open a PPUB to view it here";
+            status_page.icon_name = "text-x-generic-symbolic";
+            flap.content = status_page;
+
+            markdown_view = new GtkCommonMark.MarkdownView ();
             markdown_view.set_wrap_mode (WrapMode.WORD_CHAR);
             markdown_view.widget_embedded.connect(widget_embedded);
+            markdown_view.link_activated.connect(link_clicked);
             clamp.child = markdown_view;
-            scrolled_window.child = clamp;
-            flap.content = scrolled_window;
+            
+            Gtk.Settings.get_default().notify["gtk-application-prefer-dark-theme"].connect(() => configure_tags());
+
+            properties_button.clicked.connect(() => {
+                flap.reveal_flap = !flap.reveal_flap;
+            });
 
         }
 
         public void load_ppub(File file) throws Error {
             publication = new Ppub.Publication(file.get_path());
             var asset = publication.get_default_asset ();
-            markdown_view.load_from_stream (publication.read_asset (asset.name));
+
+            window_title.title = publication.metadata.title ?? "Untitled PPUB";
+            window_title.subtitle = publication.metadata.author_name ?? "";
+            title = window_title.title + " - PPUB Viewer";
+
+            var scroller = new ScrolledWindow();
+            scroller.add_css_class("background");
+            scroller.hscrollbar_policy = PolicyType.NEVER;
+            var pane = new PropertiesPane(publication.metadata, false);
+            scroller.child = pane;
+            pane.link_clicked.connect(property_link_clicked);
+
+            flap.flap = scroller;
+            navigate(asset.name);   
+        }
+
+        public void navigate(string file) {
+
+            if(current_page == null) {
+                current_page = new NavigationHistoryItem();
+                current_page.file_name = file;
+                current_page.scroll_position = 0;
+            }
+            else {
+                current_page = current_page.add_next(file, scrolled_window.vadjustment.get_value());
+            }
+
+            load_document(file);
+        }
+
+        public void go_back() {
+            current_page.scroll_position = scrolled_window.vadjustment.get_value();
+            current_page = current_page.previous;
+            load_document(current_page.file_name);
+        }
+
+        public void go_forward() {
+            current_page.scroll_position = scrolled_window.vadjustment.get_value();
+            current_page = current_page.next;
+            load_document(current_page.file_name);
+        }
+
+
+        public void load_document(string file) {
+
+            back_button.sensitive = current_page.previous != null;
+            forward_button.sensitive = current_page.next != null;            
+
+            flap.content = scrolled_window;
+            markdown_view.buffer.set_text("", 0);
+            markdown_view.load_from_stream (publication.read_asset (file));
+            Idle.add_once(() => {
+                configure_tags();
+                scrolled_window.vadjustment.set_value(current_page.scroll_position);
+                return false;
+            });
         }
 
         private void widget_embedded(GtkCommonMark.MarkdownViewEmbeddedWidgetHost widget, string file, string title) {
@@ -57,7 +165,73 @@ namespace PpubViewer {
             widget.append (image);
         }
 
+        private void link_clicked(string url) {
+
+            var asset = publication.get_asset(Uri.unescape_string(url));
+            if(asset != null) {
+                navigate(asset.name);
+                return;
+            }
+
+            Uri uri;
+
+            try{
+                uri = Uri.parse(url, UriFlags.PARSE_RELAXED);
+            }
+            catch {
+                var dialog = new Adw.MessageDialog(this, "Unrecognised link",  @"Could not determine how to handle the link <i>$url</i>.");
+                dialog.body_use_markup = true;
+                dialog.add_response("close", "Close");
+                dialog.present();
+                dialog.response.connect(() => dialog.close());
+                return;
+            }
+
+            var app = AppInfo.get_default_for_uri_scheme(uri.get_scheme());
+            if(app == null) {
+                var dialog = new Adw.MessageDialog(this, "No application",  @"Could not find an application to handle the URI <i>$uri</i>.");
+                dialog.body_use_markup = true;
+                dialog.add_response("close", "Close");
+                dialog.present();
+                dialog.response.connect(() => dialog.close());
+                return;
+            }
+
+            var uris = new List<string>();
+            uris.append(uri.to_string());
+
+            var dialog = new Adw.MessageDialog(this, "Open external link?", @"Do you want to open the external link <i>$uri</i>?");
+            dialog.body_use_markup = true;
+            dialog.add_response("cancel", @"Cancel");
+            dialog.add_response("open", @"Open in $(app.get_display_name())");
+            dialog.present();
+
+            dialog.response.connect(id => {
+                if(id == "open") {
+                    app.launch_uris(uris, null);
+                }
+                dialog.close();
+            });
+            
+        }
+
+        private void configure_tags() {
+            var link = new LinkButton("");
+            markdown_view.tag_manager.update_link_colour(link.get_color());
+        }
+
+        private void property_link_clicked(string file) {
+            link_clicked(file);
+            if(flap.folded) {
+                flap.reveal_flap = false;
+            }
+        }
+
+        private void change_zoom() {
+            var scale = zoom_spin.adjustment.value / 100;
+            markdown_view.tag_manager.font_scale = (float)scale;
 
+        }
 
     }
 

+ 2 - 0
src/meson.build

@@ -4,6 +4,8 @@ add_project_arguments(['--disable-warnings', '--enable-checking'], language: 'va
 
 sources = files('App.vala')
 sources += files('Window.vala')
+sources += files('NavigationStack.vala')
+sources += files('PropertiesPane.vala')
 
 dependencies = [
     dependency('glib-2.0'),