Billy Barrow 3 жил өмнө
commit
97f43d99e6

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+*.ppub
+*.ppix
+*~
+*.pyc
+PyPPUB

+ 8 - 0
README.md

@@ -0,0 +1,8 @@
+# PPublisher
+
+PPublisher is a GTK3 application for "easily" creating PPUB (Portable PUBlication) files, as well as PPIX (Ppub IndeX) files. It was written in a few hours as quickly as possible and I really need to tidy up this application as well as the PyPPUB libary that this uses.
+
+At some point I'll package this and the dependencies so you don't need to remotely read my mind to get it working...
+
+## Useful links
+ *coming soon* when I publish everything

+ 205 - 0
ppublisher/__main__.py

@@ -0,0 +1,205 @@
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import GLib, Gio, Gtk
+
+import sys
+from ppublisher.file_item import FileItem
+from ppublisher.metadata import MetadataEditor
+from ppublisher.file_editor import FileEditor
+from PyPPUB import ppub_builder
+
+
+#
+# 1. our .glade file (may contain paths)
+#
+@Gtk.Template.from_file("ppublisher/ppublisher.ui")
+class AppWindow(Gtk.ApplicationWindow):
+    #
+    # 2. the GtkApplicationWindow class
+    #
+    __gtype_name__ = "ppublisher"
+
+    #
+    # 3. the Button id
+    #
+    editor: Gtk.Box = Gtk.Template.Child()
+    file_list: Gtk.ListBox = Gtk.Template.Child()
+
+    def __init__(self, **kwargs):
+        super(Gtk.ApplicationWindow, self).__init__(**kwargs)
+        self.current_editor = None
+
+    def set_editor(self, child):
+        if(self.current_editor != None):
+            self.editor.remove(self.current_editor)
+        self.current_editor = child
+        self.editor.add(child)
+
+    def get_all_items(self):
+        return [x.get_child() for x in self.file_list.get_children()]
+
+    def set_default_document(self, item):
+        items = self.get_all_items()
+        for i in items:
+            i.set_main(False)
+        if(item != None):
+            item.set_main(True)
+
+    def get_default_document(self):
+        items = self.get_all_items()
+        for item in items:
+            if(item.is_main):
+                return item
+        return None
+
+    def get_metadata(self):
+        items = self.get_all_items()
+        for item in items:
+            if(item.is_metadata):
+                return item
+        return None
+
+    def build_ppub(self, path):
+        metadata = self.get_metadata()
+        default = self.get_default_document()
+        if(metadata == None or default == None):
+            raise Exception("No default document or metadata present!")
+
+        builder = ppub_builder.PpubBuilder()
+        builder.metadata = metadata.build_metadata()
+        self.add_asset(builder, default)
+        for item in self.get_all_items():
+            if(item == metadata or item == default):
+                continue
+            self.add_asset(builder, item)
+
+        f = open(path, 'wb')
+        builder.write_to_stream(f)
+        f.close()
+
+    def add_asset(self, builder, item):
+        f = open(item.path, 'rb')
+        builder.add_asset(item.filename, item.mimetype, f.read())
+
+    def build_ppix(self, folder_path):
+        from PyPPUB.ppix_builder import PpixBuilder
+        from PyPPUB import ppub
+        builder = PpixBuilder()
+        import glob
+        paths = glob.glob(folder_path + "/*.ppub")
+        for path in paths:
+            builder.add_publication(path.split("/")[-1], ppub.Ppub.from_stream(open(path, 'rb')))
+
+        f = open(folder_path + "/lib.ppix", 'wb')
+        builder.write_out(f)
+
+    @Gtk.Template.Callback()
+    def file_selected(self, widget, list_item):
+        item = list_item.get_child()
+        if(item.is_metadata):
+            self.set_editor(MetadataEditor(item))
+        else:
+            self.set_editor(FileEditor(item, self))
+
+    @Gtk.Template.Callback()
+    def add_file_click(self, widget):
+        dialog = Gtk.FileChooserDialog(title = "Add File", parent = self, action = Gtk.FileChooserAction.OPEN)
+        dialog.add_buttons(
+            Gtk.STOCK_CANCEL,
+            Gtk.ResponseType.CANCEL,
+            Gtk.STOCK_OPEN,
+            Gtk.ResponseType.OK,
+        )
+        dialog.set_select_multiple(True)
+
+        response = dialog.run()
+        if response == Gtk.ResponseType.OK:
+            files = dialog.get_filenames()
+            for file in files:
+                print(file)
+                self.file_list.add(FileItem(file))
+
+        dialog.destroy()
+
+    @Gtk.Template.Callback()
+    def save_click(self, widget):
+        dialog = Gtk.FileChooserDialog(title = "Save PPUB", parent = self, action = Gtk.FileChooserAction.SAVE)
+        dialog.add_buttons(
+            Gtk.STOCK_CANCEL,
+            Gtk.ResponseType.CANCEL,
+            Gtk.STOCK_SAVE,
+            Gtk.ResponseType.OK,
+        )
+
+        response = dialog.run()
+        if response == Gtk.ResponseType.OK:
+            try:
+                self.build_ppub(dialog.get_filename())
+            except Exception as ex:
+                error = Gtk.MessageDialog(
+                    transient_for=dialog,
+                    flags=0,
+                    message_type=Gtk.MessageType.ERROR,
+                    buttons=Gtk.ButtonsType.CANCEL,
+                    text="Error exporting PPUB!",
+                )
+                error.format_secondary_text(str(ex))
+                error.run()
+
+                error.destroy()
+            
+        dialog.destroy()
+
+    @Gtk.Template.Callback()
+    def create_index_click(self, widget):
+        dialog = Gtk.FileChooserDialog(title = "Choose a file to build a PPIX index in", parent = self, action = Gtk.FileChooserAction.SELECT_FOLDER)
+        dialog.add_buttons(
+            Gtk.STOCK_CANCEL,
+            Gtk.ResponseType.CANCEL,
+            "Build Index",
+            Gtk.ResponseType.OK,
+        )
+
+        response = dialog.run()
+        if response == Gtk.ResponseType.OK:
+            try:
+                self.build_ppix(dialog.get_filename())
+            except Exception as ex:
+                error = Gtk.MessageDialog(
+                    transient_for=dialog,
+                    flags=0,
+                    message_type=Gtk.MessageType.ERROR,
+                    buttons=Gtk.ButtonsType.CANCEL,
+                    text="Error creating PPIX!",
+                )
+                error.format_secondary_text(str(ex))
+                error.run()
+
+                error.destroy()
+            
+        dialog.destroy()
+
+    @Gtk.Template.Callback()
+    def remove_file_click(self, widget):
+        item_container = self.file_list.get_selected_row()
+        item = item_container.get_child()
+        if(item.is_metadata):
+            return
+
+        self.file_list.remove(item_container)
+
+
+class Application(Gtk.Application):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, application_id="com.pcthingz.ppublisher",
+                         flags=Gio.ApplicationFlags.FLAGS_NONE, **kwargs)
+        self.window = None
+
+    def do_activate(self):
+        self.window = self.window or AppWindow(application=self)
+        self.window.present()
+        self.window.file_list.add(FileItem(None, True))
+
+
+if __name__ == '__main__':
+    Application().run(sys.argv)

+ 57 - 0
ppublisher/file.ui

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="file_item" parent="GtkBox">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="margin-left">8</property>
+    <property name="margin-right">8</property>
+    <property name="margin-top">8</property>
+    <property name="margin-bottom">8</property>
+    <property name="spacing">8</property>
+    <child>
+      <object class="GtkLabel" id="list_filename">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="label" translatable="yes">File Name</property>
+        <property name="ellipsize">end</property>
+        <attributes>
+          <attribute name="weight" value="bold"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkImage" id="star">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="icon-name">non-starred-symbolic</property>
+        <property name="icon_size">2</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="pack-type">end</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="list_mimetype">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="label" translatable="yes">Mimetype</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="pack-type">end</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </template>
+</interface>

+ 35 - 0
ppublisher/file_editor.py

@@ -0,0 +1,35 @@
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import GLib, Gio, Gtk
+
+import os
+import mimetypes
+
+@Gtk.Template.from_file("ppublisher/file_editor.ui")
+class FileEditor(Gtk.Box):
+
+    __gtype_name__ = "file_editor"
+
+    editor_filename: Gtk.Label = Gtk.Template.Child()
+    editor_mimetype: Gtk.Label = Gtk.Template.Child()
+    editor_default_document: Gtk.CheckButton = Gtk.Template.Child()
+
+    def __init__(self, file, window):
+        super(Gtk.Box, self).__init__()
+
+        self.file = file
+        self.window = window
+
+        self.editor_filename.set_text(file.list_filename.get_text())
+        self.editor_mimetype.set_text(file.mimetype)
+        self.editor_default_document.set_active(file.is_main)
+
+    @Gtk.Template.Callback()
+    def default_toggled(self, widget):
+        enabled = widget.get_active()
+        if(enabled):
+            self.window.set_default_document(self.file)
+        else:
+            self.window.set_default_document(None)
+
+

+ 74 - 0
ppublisher/file_editor.ui

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="file_editor" parent="GtkBox">
+    <property name="width-request">450</property>
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="halign">center</property>
+    <property name="valign">center</property>
+    <property name="margin-left">18</property>
+    <property name="margin-right">18</property>
+    <property name="margin-top">18</property>
+    <property name="margin-bottom">18</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">8</property>
+    <child>
+      <object class="GtkLabel" id="editor_filename">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="halign">start</property>
+        <property name="label" translatable="yes">File Name</property>
+        <attributes>
+          <attribute name="scale" value="1.5"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="editor_mimetype">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="halign">start</property>
+        <property name="label" translatable="yes">Mimetype</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkSeparator">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkCheckButton" id="editor_default_document">
+        <property name="label" translatable="yes">Default Document</property>
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">False</property>
+        <property name="halign">start</property>
+        <property name="draw-indicator">True</property>
+        <signal name="toggled" handler="default_toggled" swapped="no"/>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">3</property>
+      </packing>
+    </child>
+  </template>
+</interface>

+ 69 - 0
ppublisher/file_item.py

@@ -0,0 +1,69 @@
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import GLib, Gio, Gtk
+
+import os
+import mimetypes
+from PyPPUB import ppub_builder
+import datetime
+
+@Gtk.Template.from_file("ppublisher/file.ui")
+class FileItem(Gtk.Box):
+
+    __gtype_name__ = "file_item"
+
+    list_filename: Gtk.Label = Gtk.Template.Child()
+    star: Gtk.Image = Gtk.Template.Child()
+    list_mimetype: Gtk.Label = Gtk.Template.Child()
+
+    def __init__(self, path, is_metadata = False):
+        super(Gtk.Box, self).__init__()
+        self.path = path
+        self.filename = "metadata"
+        self.is_main = False
+        self.is_metadata = is_metadata
+
+        if(not is_metadata):
+            self.mimetype = mimetypes.guess_type(path)[0] or "application/octet-stream"
+            self.filename = os.path.split(path)[1]
+            self.list_filename.set_text(self.filename)
+            self.list_mimetype.set_text(self.mimetype)
+
+        else:
+            self.list_filename.set_text("Metadata")
+            self.list_mimetype.set_text("application/x-ppub-metadata")
+            self.mimetype = "application/x-ppub-metadata"
+            self.star.set_from_icon_name("document-properties-symbolic", Gtk.IconSize.MENU)
+
+            self.title = "My book"
+            self.author = ""
+            self.description = ""
+            self.tags = ""
+            self.licence = ""
+            self.copyright = ""
+        
+        self.set_main(False)
+
+    def set_main(self, main):
+        self.is_main = main
+        if(not self.is_metadata):
+            if(main):
+                self.star.set_from_icon_name("starred-symbolic", Gtk.IconSize.MENU)
+            else:
+                self.star.set_from_icon_name("non-starred-symbolic", Gtk.IconSize.MENU)
+
+    def build_metadata(self):
+        metadata = ppub_builder.Metadata()
+        
+        def add_if_present(field, value):
+            if(value != None or value != ""):
+                metadata.set_value(field, value)
+
+        add_if_present("title", self.title)
+        add_if_present("author", self.author)
+        add_if_present("description", self.description)
+        add_if_present("tags", self.tags)
+        add_if_present("licence", self.licence)
+        add_if_present("copyright", self.copyright)
+        metadata.set_value("date", datetime.datetime.now().astimezone().isoformat())
+        return metadata

+ 41 - 0
ppublisher/metadata.py

@@ -0,0 +1,41 @@
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import GLib, Gio, Gtk
+
+import os
+import mimetypes
+
+@Gtk.Template.from_file("ppublisher/metadata.ui")
+class MetadataEditor(Gtk.Box):
+
+    __gtype_name__ = "metadata_editor"
+
+    title_edit: Gtk.Entry = Gtk.Template.Child()
+    author_edit: Gtk.Entry = Gtk.Template.Child()
+    description_edit: Gtk.Entry = Gtk.Template.Child()
+    tags_edit: Gtk.Entry = Gtk.Template.Child()
+    licence_edit: Gtk.Entry = Gtk.Template.Child()
+    copyright_edit: Gtk.Entry = Gtk.Template.Child()
+
+    def __init__(self, metadata):
+        super(Gtk.Box, self).__init__()
+
+        self.metadata = metadata
+
+        self.title_edit.set_text(self.metadata.title)
+        self.author_edit.set_text(self.metadata.author)
+        self.description_edit.set_text(self.metadata.description)
+        self.tags_edit.set_text(self.metadata.tags)
+        self.licence_edit.set_text(self.metadata.licence)
+        self.copyright_edit.set_text(self.metadata.copyright)
+
+    @Gtk.Template.Callback()
+    def data_changed(self, widget):
+        self.metadata.title = self.title_edit.get_text()
+        self.metadata.author = self.author_edit.get_text()
+        self.metadata.description = self.description_edit.get_text()
+        self.metadata.tags = self.tags_edit.get_text()
+        self.metadata.licence = self.licence_edit.get_text()
+        self.metadata.copyright = self.copyright_edit.get_text()
+
+

+ 220 - 0
ppublisher/metadata.ui

@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="metadata_editor" parent="GtkBox">
+    <property name="width-request">450</property>
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="halign">center</property>
+    <property name="valign">center</property>
+    <property name="margin-left">18</property>
+    <property name="margin-right">18</property>
+    <property name="margin-top">18</property>
+    <property name="margin-bottom">18</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">8</property>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="halign">start</property>
+        <property name="label" translatable="yes">Metadata</property>
+        <attributes>
+          <attribute name="scale" value="1.5"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="halign">start</property>
+        <property name="label" translatable="yes">PPUB Metadata</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkSeparator">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <!-- n-columns=2 n-rows=6 -->
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="row-spacing">6</property>
+        <property name="column-spacing">8</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Title</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Author</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Description</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Tags</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Licence</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Copyright</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="author_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="title_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="show-emoji-icon">True</property>
+            <property name="enable-emoji-completion">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="tags_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="licence_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="copyright_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="description_edit">
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="show-emoji-icon">True</property>
+            <property name="enable-emoji-completion">True</property>
+            <signal name="changed" handler="data_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">3</property>
+      </packing>
+    </child>
+  </template>
+</interface>

+ 152 - 0
ppublisher/ppublisher.ui

@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="ppublisher" parent="GtkApplicationWindow">
+    <property name="width-request">1000</property>
+    <property name="height-request">500</property>
+    <property name="can-focus">False</property>
+    <property name="show-menubar">False</property>
+    <child>
+      <object class="GtkPaned">
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <child>
+          <object class="GtkBox">
+            <property name="width-request">400</property>
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkListBox" id="file_list">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <signal name="row-selected" handler="file_selected" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkActionBar">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <child>
+                  <object class="GtkButtonBox">
+                    <property name="visible">True</property>
+                    <property name="can-focus">False</property>
+                    <property name="spacing">8</property>
+                    <property name="layout-style">start</property>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="width-request">-1</property>
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="receives-default">True</property>
+                        <signal name="clicked" handler="add_file_click" swapped="no"/>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="icon-name">list-add-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                        <property name="non-homogeneous">True</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="receives-default">True</property>
+                        <signal name="clicked" handler="remove_file_click" swapped="no"/>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="icon-name">list-remove-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                        <property name="non-homogeneous">True</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="resize">False</property>
+            <property name="shrink">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="editor">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="resize">True</property>
+            <property name="shrink">True</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="title" translatable="yes">PPublisher</property>
+        <property name="show-close-button">True</property>
+        <child>
+          <object class="GtkButton">
+            <property name="label">gtk-save</property>
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="receives-default">True</property>
+            <property name="use-stock">True</property>
+            <property name="always-show-image">True</property>
+            <signal name="clicked" handler="save_click" swapped="no"/>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton">
+            <property name="label">Create Index</property>
+            <property name="visible">True</property>
+            <property name="can-focus">True</property>
+            <property name="receives-default">True</property>
+            <property name="always-show-image">True</property>
+            <signal name="clicked" handler="create_index_click" swapped="no"/>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>