123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- using Adw;
- using Gtk;
- namespace Publicate {
- public class PpubEditor : Box {
- private Adw.HeaderBar header;
- private WindowTitle header_title;
- private Leaflet leaflet;
- private File publication_file;
- private ViewerWindow window;
- private FileExplorer file_explorer;
- private Box tab_box;
- private TabView tab_view;
- private TabBar tab_bar;
- private Button save_tab_button;
- private Button remove_file_button;
- private SaveProgress progress_window;
- private Menu document_menu;
- private Gee.HashMap<string, Editors.EditorWidget> open_editors = new Gee.HashMap<string, Editors.EditorWidget>();
- public PpubEditor(ViewerWindow win) {
- window = win;
- progress_window = new SaveProgress(window);
- orientation = Orientation.VERTICAL;
- header = new Adw.HeaderBar();
- append(header);
- header_title = new WindowTitle("Untitled Publication", "");
- header.title_widget = header_title;
- var menu_button = new MenuButton();
- menu_button.icon_name = "open-menu-symbolic";
- header.pack_end(menu_button);
- var menu = new Menu();
- menu_button.menu_model = menu;
- menu.append_section(null, win.window_menu);
- document_menu = new Menu();
- document_menu.append("Save", "win.save");
- document_menu.append("Save as…", "win.save-as");
- document_menu.append("Save all", "win.save-all");
- menu.append_section(null, document_menu);
- var file_box = new Box(Orientation.VERTICAL, 0);
- file_box.vexpand = true;
- file_explorer = new FileExplorer();
- file_explorer.asset_selected.connect(open_asset);
- file_explorer.vexpand = true;
- file_box.append(file_explorer);
-
- var file_buttons = new Box(Orientation.HORIZONTAL, 0);
- file_buttons.add_css_class("linked");
- file_buttons.halign = Align.END;
- file_buttons.margin_end = 8;
- file_buttons.margin_bottom = 8;
- file_buttons.margin_start = 8;
- file_buttons.margin_top = 8;
- file_box.append(file_buttons);
-
- var new_file_button = new Button.from_icon_name("document-new-symbolic");
- file_buttons.append(new_file_button);
- new_file_button.sensitive = false; // Not yet implemented...
- var add_file_button = new Button.from_icon_name("list-add-symbolic");
- file_buttons.append(add_file_button);
- add_file_button.clicked.connect(import_file);
- remove_file_button = new Button.from_icon_name("user-trash-symbolic");
- file_buttons.append(remove_file_button);
- remove_file_button.clicked.connect(delete_file_clicked);
- remove_file_button.sensitive = false;
-
- leaflet = new Leaflet();
- leaflet.vexpand = true;
- leaflet.append(file_box);
-
- tab_view = new TabView();
- tab_bar = new TabBar();
- tab_box = new Box(Orientation.VERTICAL, 0);
- tab_bar.autohide = false;
- tab_bar.set_view(tab_view);
- tab_box.append(tab_bar);
- tab_box.append(tab_view);
- leaflet.append(tab_box);
- tab_view.close_page.connect(editor_closing);
- save_tab_button = new Button.from_icon_name("document-save-symbolic");
- save_tab_button.clicked.connect(save_tab);
- header.pack_end(save_tab_button);
- tab_view.notify["selected-page"].connect(() => select_file_from_tab());
- window.close_request.connect(app_close_request);
- append(leaflet);
- }
- public async void load_ppub(File file, bool initial = false) throws Error {
- window.publication = new Ppub.Publication(file.get_path());
- publication_file = file;
- header_title.title = window.publication.metadata.title ?? "Untitled PPUB";
- header_title.subtitle = window.publication.metadata.author_name ?? "";
- window.title = header_title.title + " - Publicate!";
- file_explorer.set_assets(window.publication.assets);
- if(initial) {
- window.maximize();
- yield open_asset(window.publication.get_default_asset());
- yield add_publish_menu();
- }
- }
- public async void add_publish_menu() throws Error {
- var collections = yield window.collection_service.get_collection_config();
- if(collections.any(() => true)) {
- var publish_menu = new Menu();
- foreach (var collection in collections) {
- var action = new SimpleAction("publish-to-" + collection.collection_id, null);
- action.activate.connect(() => publish.begin(collection));
- window.add_action(action);
- publish_menu.append(collection.name, "win.publish-to-" + collection.collection_id);
- }
- document_menu.append_submenu("Publish to", publish_menu);
- }
- }
- public async void open_asset(Ppub.Asset? asset) {
- update_deletable();
- if(asset == null) {
- return;
- }
- if(open_editors.has_key(asset.name)) {
- tab_view.selected_page = open_editors[asset.name].tab_page;
- return;
- }
-
- if(asset.mimetype == "text/markdown") {
- var editor = new Editors.MarkdownEditor(window, tab_view);
- add_editor(editor, asset);
- }
- else if(asset.mimetype.has_prefix("text/")) {
- var editor = new Editors.PlainTextEditor(window, tab_view);
- add_editor(editor, asset);
- }
- else if(asset.mimetype == "application/x-ppub-metadata") {
- var editor = new Editors.MetadataEditor(window, tab_view);
- add_editor(editor, asset);
- }
- if(asset.mimetype == "application/x-ppvm") {
- var editor = new Editors.VideoManifestEditor(window, tab_view);
- add_editor(editor, asset);
- }
- }
- private bool editor_closing(TabPage page) {
- var editor = (Editors.EditorWidget)page.child;
- if(editor.has_unsaved_changes && open_editors.has_key(editor.asset_name)) {
- var prompt = new Adw.MessageDialog(window, "Save Changes?", "This editor has unsaved changes, would you like to save them before closing?");
- prompt.add_response("cancel", "Cancel");
- prompt.add_response("discard", "Discard");
- prompt.add_response("save", "Save");
- prompt.response.connect(r => {
- if(r == "save") {
- save_and_close_tab_page.begin(page);
- }
- else if(r == "discard") {
- open_editors.unset(editor.asset_name);
- tab_view.close_page(page);
- tab_view.close_page_finish(page, true);
- }
- else {
- tab_view.close_page_finish(page, false);
- }
- });
- prompt.present();
- return Gdk.EVENT_STOP;
- }
- open_editors.unset(editor.asset_name);
- tab_view.close_page_finish(page, true);
- return Gdk.EVENT_STOP;
- }
- private void add_editor(Editors.EditorWidget editor, Ppub.Asset asset) {
- editor.load_asset(window.publication, asset);
- open_editors.set(asset.name, editor);
- tab_view.selected_page = editor.tab_page;
- }
- private async void save(Invercargill.Enumerable<Savable> to_save) {
- progress_window.display_progress("Saving…", 0.0);
- yield do_save(to_save);
- progress_window.complete();
- }
- private async void do_save(Invercargill.Enumerable<Savable> to_save) {
- SourceFunc callback = do_save.callback;
-
- ThreadFunc<bool> run = () => {
- var items = to_save.count();
- var count = 0;
- var builder = new Ppub.Builder();
- foreach (var item in to_save) {
- count++;
- item.save_asset(builder);
- Idle.add(() => {
- progress_window.display_progress("Processing Assets…", (double)count / (double)items);
- return false;
- });
- }
-
- Idle.add(() => {
- progress_window.display_progress("Writing PPUB…", 1.0);
- return false;
- });
- var temp_file = File.new_for_path(publication_file.get_path() + ".tmp");
- var stream = temp_file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION);
-
- builder.write(stream);
- stream.close();
-
- temp_file.move(publication_file, FileCopyFlags.OVERWRITE);
- Idle.add(() => {
- load_ppub.begin(publication_file, false, () => {
- select_file_from_tab();
- callback();
- });
- return false;
- });
- return true;
- };
- new Thread<bool>("saver thread", run);
- yield;
- }
- public async void save_tab() {
- var current_editor = (Editors.EditorWidget)tab_view.selected_page.child;
- var to_save = window.publication.assets.select<Savable>(a => a.name == current_editor.asset_name ? (Savable)current_editor : new SavableAsset(window.publication, a));
- yield save(to_save);
- }
- public async void save_as() {
- var dialog = new FileDialog();
- dialog.initial_name = publication_file.get_basename();
- 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;
- }
- progress_window.display_progress("Saving…", 0.0);
- yield publication_file.copy_async(file, FileCopyFlags.OVERWRITE, 1, null);
- publication_file = file;
- yield save_tab();
- }
- public async void save_all() {
- var to_save = window.publication.assets.select<Savable>(get_savable_editor_or_asset);
- yield save(to_save);
- }
- private Savable get_savable_editor_or_asset(Ppub.Asset asset) {
- if(open_editors.has_key(asset.name)) {
- var editor = open_editors[asset.name];
- if(editor.has_unsaved_changes) {
- return (Savable)editor;
- }
- }
- return new SavableAsset(window.publication, asset);
- }
- private async void save_and_close_tab_page(TabPage page) {
- var editor = (Editors.EditorWidget)page.child;
- var to_save = window.publication.assets.select<Savable>(a => a.name == editor.asset_name ? (Savable)editor : new SavableAsset(window.publication, a));
- yield save(to_save);
- open_editors.unset(editor.asset_name);
- tab_view.close_page_finish(page, true);
- }
- private void select_file_from_tab() {
- var editor = (Editors.EditorWidget)tab_view.selected_page.child;
- file_explorer.set_selected_item(editor.asset_name);
- }
- public async void add_asset (string name, string mimetype, GLib.InputStream stream, Ppub.CompressionInfo compression) {
- var to_save = window.publication.assets.select<Savable>(a => new SavableAsset(window.publication, a)).to_series();
- to_save.add(new SavableNewAsset(name, mimetype, stream, compression));
- yield save(to_save);
-
- }
- public async void import_file() {
- var dialog = new FileDialog();
- var file = yield dialog.open(window, null);
- if(file == null) {
- return;
- }
-
- var stream = yield file.read_async(Priority.DEFAULT, null);
- var sample = new uint8[2048];
- size_t sample_size;
- yield stream.read_all_async(sample, Priority.DEFAULT, null, out sample_size);
- stream.seek(0, SeekType.SET, null);
- var mimetype = Ppub.guess_mimetype(file.get_basename(), sample);
- var compression_policy = Ppub.CompressionPolicy.AUTO;
- if(mimetype.has_prefix("video/")) {
- compression_policy = Ppub.CompressionPolicy.NEVER_COMPRESS;
- }
- var compression = new Ppub.CompressionInfo(stream, compression_policy);
- stream.seek(0, SeekType.SET, null);
- yield add_asset(file.get_basename(), mimetype, stream, compression);
- }
- private bool app_close_request() {
- if(Invercargill.Convert.gte(open_editors.values).any(e => e.has_unsaved_changes)) {
- var prompt = new Adw.MessageDialog(window, "Save Changes?", "There are editors with unsaved changes, would you like to save them before closing?");
- prompt.add_response("cancel", "Cancel");
- prompt.add_response("discard", "Discard");
- prompt.add_response("save", "Save All");
- prompt.response.connect(r => {
- if(r == "save") {
- save_all_and_close.begin();
- }
- else if(r == "discard") {
- open_editors.clear();
- window.close();
- }
- });
- prompt.present();
- return Gdk.EVENT_STOP;
- }
- return Gdk.EVENT_PROPAGATE;
- }
- private async void save_all_and_close() {
- yield save_all();
- window.close();
- }
- private void update_deletable() {
- // Inhibit deletion of default document or metadata.
- var asset = file_explorer.selected_asset;
- remove_file_button.sensitive = (asset != null && asset.name != "metadata" && window.publication.get_default_asset().name != asset.name);
- }
- private void delete_file_clicked() {
- var asset = file_explorer.selected_asset;
- if(asset == null) {
- return;
- }
- var prompt = new Adw.MessageDialog(window, "Delete File?", @"Are you sure you want to delete the file\"<i>$(asset.name)</i>\"? This cannot be undone.");
- prompt.body_use_markup = true;
- prompt.add_response("cancel", "Cancel");
- prompt.add_response("delete", "Delete");
- prompt.response.connect(r => {
- if(r == "delete") {
- delete_file.begin(asset);
- }
- });
- prompt.present();
- }
- private async void delete_file(Ppub.Asset asset) {
- var to_save = window.publication.assets
- .where(a => a.name != asset.name)
- .select<Savable>(a => new SavableAsset(window.publication, a)).to_series();
- if(open_editors.has_key(asset.name)) {
- Editors.EditorWidget editor;
- open_editors.unset(asset.name, out editor);
- tab_view.close_page(editor.tab_page);
- }
- yield save(to_save);
- }
- private async void publish(CollectionConfig collection) {
- yield save_all();
- var publish_window = new PublishWindow(window);
- publish_window.present();
- yield publish_window.publish_to(collection, publication_file);
- }
-
- }
- }
|