瀏覽代碼

UI Changes, added EXIF support

Billy Barrow 7 年之前
父節點
當前提交
96365430cc
共有 8 個文件被更改,包括 835 次插入161 次删除
  1. 64 5
      Export/__init__.py
  2. 290 0
      Export/exiftool.py
  3. 20 0
      PF2/Sounds.py
  4. 16 6
      PF2/__init__.py
  5. 1 1
      README.md
  6. 332 147
      ui/Export.glade
  7. 112 2
      ui/PF2_Activity.glade
  8. 二進制
      ui/complete.png

+ 64 - 5
Export/__init__.py

@@ -4,11 +4,12 @@ import threading
 from gi.repository import GLib
 import cv2
 import subprocess
-
+from Export.exiftool import ExifTool
 
 class ExportDialog:
 
     def __init__(self, root, builder, w, h, get_image_call, done_call, path):
+        self.exif = ExifTool()
         self.path = path
         self.builder = builder
         self.root = root
@@ -38,7 +39,11 @@ class ExportDialog:
             "quality",
             "width_spin",
             "height_spin",
-            "quality_spin"
+            "quality_spin",
+            "metadata",
+            "description",
+            "artist",
+            "copyright"
         ]
 
         for component in components:
@@ -65,9 +70,28 @@ class ExportDialog:
         self.ui["cancel_button"].connect("clicked", self.on_cancel_clicked)
 
 
+        # Get EXIF data for this image ready
+        self.exif.start()  
+        data = self.exif.get_metadata(self.path)
+
+        # Preload editable fields
+        self.ui["description"].set_text(self.safe_exif_get(data, "EXIF:ImageDescription"))
+        self.ui["artist"].set_text(self.safe_exif_get(data, "EXIF:Artist"))
+        self.ui["copyright"].set_text(self.safe_exif_get(data, "EXIF:Copyright"))
+        
+        print(self.safe_exif_get(data, "Flash"))
+
+
         self.ui["window"].show_all()
 
 
+    def safe_exif_get(self, data, key):
+        out = ""
+        if(key in data):
+            out = data[key]
+        return out
+
+
     def on_preset_changed(self, sender):
         if(sender.get_active() == 0):
             self.ui["width_spin"].set_sensitive(True)
@@ -118,6 +142,7 @@ class ExportDialog:
         self.ui["width_spin"].set_value(r*self.width)
 
     def on_cancel_clicked(self, sender):
+        self.exif.terminate()
         self.ui["window"].close()
 
     def on_save_clicked(self, sender):
@@ -128,10 +153,19 @@ class ExportDialog:
         height = self.ui["height"].get_value()
         path = self.ui["file"].get_filename()
         pngcrush = self.ui["pngcrush"].get_active()
+        # Metadata
+        metadata_scheme = self.ui["metadata"].get_active()
+        description = self.ui["description"].get_text()
+        artist = self.ui["artist"].get_text()
+        copyright_info = self.ui["copyright"].get_text()
+        
         threading.Thread(target=self.do_export, args=(format, quality, width,
-                                                      height, path, pngcrush)).start()
+                                                      height, path, pngcrush,
+                                                      metadata_scheme,
+                                                      description, artist,
+                                                      copyright_info)).start()
 
-    def do_export(self, format, quality, width, height, path, pngcrush):
+    def do_export(self, format, quality, width, height, path, pngcrush, metadata_scheme, description, artist, copyright_info):
         self.image = self.get_image_call(width, height)
         newPath = path
         if(format == 0) and (not path.endswith(".png")):
@@ -151,14 +185,39 @@ class ExportDialog:
             tempPath = "/tmp/pngcrush-%i.png" % random.randrange(1000000,9999999)
             cv2.imwrite(tempPath, self.image)
             subprocess.call(["pngcrush", "-rem gAMA", "-rem cHRM", "-rem iCCP", "-rem sRGB" , "-m 0", "-l 9", "-fix", "-v", "-v", tempPath, newPath])
+            os.unlink(tempPath)
 
         else:
             cv2.imwrite(newPath, self.image)
 
-        GLib.idle_add(self.done_call, newPath)
+        # Write metadata
+        # If the scheme is Include Original Metadata
+        if(metadata_scheme == 0):
+            # Copy metadata from original file
+            self.exif.execute(b"-TagsFromFile", self.path.encode("utf-8"), b"-all:all", newPath.encode("utf-8"), b"-overwrite_original")
+
+        # Scheme 1 (Ignore Original Metadata) does not copy any data so is skipped here
+
+        # If the scheme is Strip GPS Information
+        if(metadata_scheme == 2):
+            # Copy all metadata not pertaining to GPS information
+            self.exif.execute(b"-tagsFromFile", self.path.encode("utf-8"), b"-all:all", b"-gps:all=", b"-xmp:geotag=", newPath.encode("utf-8"), b"-overwrite_original")
 
+        # Write dialog editable metadata and application metadata to file
+        self.exif.execute(self.fmt_exif_set(description, b"ImageDescription"),
+                            self.fmt_exif_set(artist, b"Artist"),
+                            self.fmt_exif_set(copyright_info, b"Copyright"),
+                            self.fmt_exif_set("PhotoFiddle2", b"Software"),
+                            newPath.encode("utf-8"), b"-overwrite_original")
+
+        # Clean up
+        self.exif.terminate()
+
+        GLib.idle_add(self.done_call, newPath)
 
 
+    def fmt_exif_set(self, data, key):
+        return b"-%s=%s" % (key, data.encode("utf-8"))
 
 
 

+ 290 - 0
Export/exiftool.py

@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+# PyExifTool <http://github.com/smarnach/pyexiftool>
+# Copyright 2012 Sven Marnach
+
+# This file is part of PyExifTool.
+#
+# PyExifTool is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the licence, or
+# (at your option) any later version, or the BSD licence.
+#
+# PyExifTool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# See COPYING.GPL or COPYING.BSD for more details.
+
+"""
+PyExifTool is a Python library to communicate with an instance of Phil
+Harvey's excellent ExifTool_ command-line application.  The library
+provides the class :py:class:`ExifTool` that runs the command-line
+tool in batch mode and features methods to send commands to that
+program, including methods to extract meta-information from one or
+more image files.  Since ``exiftool`` is run in batch mode, only a
+single instance needs to be launched and can be reused for many
+queries.  This is much more efficient than launching a separate
+process for every single query.
+.. _ExifTool: http://www.sno.phy.queensu.ca/~phil/exiftool/
+The source code can be checked out from the github repository with
+::
+    git clone git://github.com/smarnach/pyexiftool.git
+Alternatively, you can download a tarball_.  There haven't been any
+releases yet.
+.. _tarball: https://github.com/smarnach/pyexiftool/tarball/master
+PyExifTool is licenced under GNU GPL version 3 or later.
+Example usage::
+    import exiftool
+    files = ["a.jpg", "b.png", "c.tif"]
+    with exiftool.ExifTool() as et:
+        metadata = et.get_metadata_batch(files)
+    for d in metadata:
+        print("{:20.20} {:20.20}".format(d["SourceFile"],
+                                         d["EXIF:DateTimeOriginal"]))
+"""
+
+from __future__ import unicode_literals
+
+import sys
+import subprocess
+import os
+import json
+import warnings
+import codecs
+
+try:        # Py3k compatibility
+    basestring
+except NameError:
+    basestring = (bytes, str)
+
+executable = "exiftool"
+"""The name of the executable to run.
+If the executable is not located in one of the paths listed in the
+``PATH`` environment variable, the full path should be given here.
+"""
+
+# Sentinel indicating the end of the output of a sequence of commands.
+# The standard value should be fine.
+sentinel = b"{ready}"
+
+# The block size when reading from exiftool.  The standard value
+# should be fine, though other values might give better performance in
+# some cases.
+block_size = 4096
+
+# This code has been adapted from Lib/os.py in the Python source tree
+# (sha1 265e36e277f3)
+def _fscodec():
+    encoding = sys.getfilesystemencoding()
+    errors = "strict"
+    if encoding != "mbcs":
+        try:
+            codecs.lookup_error("surrogateescape")
+        except LookupError:
+            pass
+        else:
+            errors = "surrogateescape"
+
+    def fsencode(filename):
+        """
+        Encode filename to the filesystem encoding with 'surrogateescape' error
+        handler, return bytes unchanged. On Windows, use 'strict' error handler if
+        the file system encoding is 'mbcs' (which is the default encoding).
+        """
+        if isinstance(filename, bytes):
+            return filename
+        else:
+            return filename.encode(encoding, errors)
+
+    return fsencode
+
+fsencode = _fscodec()
+del _fscodec
+
+class ExifTool(object):
+    """Run the `exiftool` command-line tool and communicate to it.
+    You can pass the file name of the ``exiftool`` executable as an
+    argument to the constructor.  The default value ``exiftool`` will
+    only work if the executable is in your ``PATH``.
+    Most methods of this class are only available after calling
+    :py:meth:`start()`, which will actually launch the subprocess.  To
+    avoid leaving the subprocess running, make sure to call
+    :py:meth:`terminate()` method when finished using the instance.
+    This method will also be implicitly called when the instance is
+    garbage collected, but there are circumstance when this won't ever
+    happen, so you should not rely on the implicit process
+    termination.  Subprocesses won't be automatically terminated if
+    the parent process exits, so a leaked subprocess will stay around
+    until manually killed.
+    A convenient way to make sure that the subprocess is terminated is
+    to use the :py:class:`ExifTool` instance as a context manager::
+        with ExifTool() as et:
+            ...
+    .. warning:: Note that there is no error handling.  Nonsensical
+       options will be silently ignored by exiftool, so there's not
+       much that can be done in that regard.  You should avoid passing
+       non-existent files to any of the methods, since this will lead
+       to undefied behaviour.
+    .. py:attribute:: running
+       A Boolean value indicating whether this instance is currently
+       associated with a running subprocess.
+    """
+
+    def __init__(self, executable_=None):
+        if executable_ is None:
+            self.executable = executable
+        else:
+            self.executable = executable_
+        self.running = False
+
+    def start(self):
+        """Start an ``exiftool`` process in batch mode for this instance.
+        This method will issue a ``UserWarning`` if the subprocess is
+        already running.  The process is started with the ``-G`` and
+        ``-n`` as common arguments, which are automatically included
+        in every command you run with :py:meth:`execute()`.
+        """
+        if self.running:
+            warnings.warn("ExifTool already running; doing nothing.")
+            return
+        with open(os.devnull, "w") as devnull:
+            self._process = subprocess.Popen(
+                [self.executable, "-stay_open", "True",  "-@", "-",
+                 "-common_args", "-G", "-n"],
+                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                stderr=devnull)
+        self.running = True
+
+    def terminate(self):
+        """Terminate the ``exiftool`` process of this instance.
+        If the subprocess isn't running, this method will do nothing.
+        """
+        if not self.running:
+            return
+        self._process.stdin.write(b"-stay_open\nFalse\n")
+        self._process.stdin.flush()
+        self._process.communicate()
+        del self._process
+        self.running = False
+
+    def __enter__(self):
+        self.start()
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.terminate()
+
+    def __del__(self):
+        self.terminate()
+
+    def execute(self, *params):
+        """Execute the given batch of parameters with ``exiftool``.
+        This method accepts any number of parameters and sends them to
+        the attached ``exiftool`` process.  The process must be
+        running, otherwise ``ValueError`` is raised.  The final
+        ``-execute`` necessary to actually run the batch is appended
+        automatically; see the documentation of :py:meth:`start()` for
+        the common options.  The ``exiftool`` output is read up to the
+        end-of-output sentinel and returned as a raw ``bytes`` object,
+        excluding the sentinel.
+        The parameters must also be raw ``bytes``, in whatever
+        encoding exiftool accepts.  For filenames, this should be the
+        system's filesystem encoding.
+        .. note:: This is considered a low-level method, and should
+           rarely be needed by application developers.
+        """
+        if not self.running:
+            raise ValueError("ExifTool instance not running.")
+        self._process.stdin.write(b"\n".join(params + (b"-execute\n",)))
+        self._process.stdin.flush()
+        output = b""
+        fd = self._process.stdout.fileno()
+        while not output[-32:].strip().endswith(sentinel):
+            output += os.read(fd, block_size)
+        return output.strip()[:-len(sentinel)]
+
+    def execute_json(self, *params):
+        """Execute the given batch of parameters and parse the JSON output.
+        This method is similar to :py:meth:`execute()`.  It
+        automatically adds the parameter ``-j`` to request JSON output
+        from ``exiftool`` and parses the output.  The return value is
+        a list of dictionaries, mapping tag names to the corresponding
+        values.  All keys are Unicode strings with the tag names
+        including the ExifTool group name in the format <group>:<tag>.
+        The values can have multiple types.  All strings occurring as
+        values will be Unicode strings.  Each dictionary contains the
+        name of the file it corresponds to in the key ``"SourceFile"``.
+        The parameters to this function must be either raw strings
+        (type ``str`` in Python 2.x, type ``bytes`` in Python 3.x) or
+        Unicode strings (type ``unicode`` in Python 2.x, type ``str``
+        in Python 3.x).  Unicode strings will be encoded using
+        system's filesystem encoding.  This behaviour means you can
+        pass in filenames according to the convention of the
+        respective Python version – as raw strings in Python 2.x and
+        as Unicode strings in Python 3.x.
+        """
+        params = map(fsencode, params)
+        return json.loads(self.execute(b"-j", *params).decode("utf-8"))
+
+    def get_metadata_batch(self, filenames):
+        """Return all meta-data for the given files.
+        The return value will have the format described in the
+        documentation of :py:meth:`execute_json()`.
+        """
+        return self.execute_json(*filenames)
+
+    def get_metadata(self, filename):
+        """Return meta-data for a single file.
+        The returned dictionary has the format described in the
+        documentation of :py:meth:`execute_json()`.
+        """
+        return self.execute_json(filename)[0]
+
+    def get_tags_batch(self, tags, filenames):
+        """Return only specified tags for the given files.
+        The first argument is an iterable of tags.  The tag names may
+        include group names, as usual in the format <group>:<tag>.
+        The second argument is an iterable of file names.
+        The format of the return value is the same as for
+        :py:meth:`execute_json()`.
+        """
+        # Explicitly ruling out strings here because passing in a
+        # string would lead to strange and hard-to-find errors
+        if isinstance(tags, basestring):
+            raise TypeError("The argument 'tags' must be "
+                            "an iterable of strings")
+        if isinstance(filenames, basestring):
+            raise TypeError("The argument 'filenames' must be "
+                            "an iterable of strings")
+        params = ["-" + t for t in tags]
+        params.extend(filenames)
+        return self.execute_json(*params)
+
+    def get_tags(self, tags, filename):
+        """Return only specified tags for a single file.
+        The returned dictionary has the format described in the
+        documentation of :py:meth:`execute_json()`.
+        """
+        return self.get_tags_batch(tags, [filename])[0]
+
+    def get_tag_batch(self, tag, filenames):
+        """Extract a single tag from the given files.
+        The first argument is a single tag name, as usual in the
+        format <group>:<tag>.
+        The second argument is an iterable of file names.
+        The return value is a list of tag values or ``None`` for
+        non-existent tags, in the same order as ``filenames``.
+        """
+        data = self.get_tags_batch([tag], filenames)
+        result = []
+        for d in data:
+            d.pop("SourceFile")
+            result.append(next(iter(d.values()), None))
+        return result
+
+    def get_tag(self, tag, filename):
+        """Extract a single tag from a single file.
+        The return value is the value of the specified tag, or
+        ``None`` if this tag was not found in the file.
+        """
+        return self.get_tag_batch(tag, [filename])[0]

+ 20 - 0
PF2/Sounds.py

@@ -0,0 +1,20 @@
+import subprocess
+import threading
+
+
+class SystemSounds:
+    @staticmethod
+    def play_sound(id):
+        threading.Thread(target=SystemSounds.play_sound_blocking, args=(id,)).start()
+        
+    @staticmethod
+    def play_sound_blocking(id):
+        subprocess.call(['/usr/bin/canberra-gtk-play','--id',id])
+
+    @staticmethod
+    def window_attention():
+        SystemSounds.play_sound("window-attention")
+
+    @staticmethod
+    def complete():
+        SystemSounds.play_sound("complete")

+ 16 - 6
PF2/__init__.py

@@ -11,7 +11,7 @@ import os
 import traceback
 import uuid
 
-from PF2 import Histogram, Layer, Debounce
+from PF2 import Histogram, Layer, Debounce, Sounds
 from PF2.Tools import BlackWhite
 from PF2.Tools import Colours
 from PF2.Tools import Contrast
@@ -87,7 +87,10 @@ class PF2(Activity.Activity):
             "preview_stack",
             "export_progress",
             "export_progress_label",
-            "resize_progress"
+            "resize_progress",
+            "export_complete_back",
+            "export_again",
+            "export_complete_label"
         ]
 
         for component in components:
@@ -194,6 +197,8 @@ class PF2(Activity.Activity):
         self.ui["scroll_window"].connect_after('motion-notify-event', self.mouse_coords_changed)
         self.ui["mask_brush_size"].connect("value-changed", self.brush_size_changed)
         self.ui["layer_blend_mode"].connect("changed", self.layer_blend_mode_changed)
+        self.ui["export_complete_back"].connect("clicked", self.on_export_go_back)
+        self.ui["export_again"].connect("clicked", self.on_export_clicked)
 
 
 
@@ -322,6 +327,7 @@ class PF2(Activity.Activity):
 
     def show_current(self):
         self.ui["preview"].set_from_pixbuf(self.pimage)
+        Sounds.SystemSounds.window_attention()
         if (self.ui["original_toggle"].get_active()):
             self.ui["original_toggle"].set_active(False)
 
@@ -369,7 +375,11 @@ class PF2(Activity.Activity):
     def on_export_complete(self, filename):
         self.is_exporting = False
         self.on_export_state_change()
-        self.show_message("Export Complete!", "Your photo has been exported to '%s'" % filename)
+        Sounds.SystemSounds.complete()
+        self.ui["export_complete_label"].set_text("Saved to '%s'" % filename)
+        self.ui["preview_stack"].set_visible_child_name("export_complete")
+
+    def on_export_go_back(self, sender):
         self.ui["preview_stack"].set_visible_child_name("preview")
 
     def on_export_state_change(self):
@@ -493,8 +503,8 @@ class PF2(Activity.Activity):
             self.render_debounce.call(None, self.update_resize_progress)
 
     def update_resize_progress(self, name, count, current):
-        fraction = current+(self.current_processing_layer_index*count)
-        fraction = fraction / (count*(len(self.layers)-1))
+        fraction = current+((self.current_processing_layer_index+1)*count)
+        fraction = fraction / (count*(len(self.layers)))
 
         GLib.idle_add(self.ui["resize_progress"].set_fraction, fraction)
 
@@ -530,7 +540,7 @@ class PF2(Activity.Activity):
         self.is_working = False
         GLib.idle_add(self.update_undo_state)
         page_name = self.ui["preview_stack"].get_visible_child_name()
-        if(page_name != "render" and page_name != "load"):
+        if(page_name != "render" and page_name != "load" and page_name != "export_complete"):
             self.ui["preview_stack"].set_visible_child_name("preview")
 
         self.change_occurred = False

+ 1 - 1
README.md

@@ -1,2 +1,2 @@
 # PhotoFiddle2
-The second version of PhotoFiddle
+The second version of PhotoFiddle

+ 332 - 147
ui/Export.glade

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
+<!-- Generated with glade 3.20.4 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkHeaderBar" id="Export_headerbar">
@@ -48,7 +48,8 @@
     <property name="page_increment">10</property>
   </object>
   <object class="GtkWindow" id="Export_window">
-    <property name="width_request">750</property>
+    <property name="width_request">1000</property>
+    <property name="height_request">800</property>
     <property name="can_focus">False</property>
     <property name="modal">True</property>
     <property name="type_hint">dialog</property>
@@ -80,190 +81,371 @@
             <property name="margin_bottom">18</property>
             <child>
               <object class="GtkBox">
-                <property name="name">Watermark</property>
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="valign">start</property>
-                <property name="margin_top">6</property>
-                <property name="margin_bottom">18</property>
-                <property name="vexpand">False</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">6</property>
+                <property name="halign">center</property>
+                <property name="spacing">18</property>
                 <child>
-                  <object class="GtkGrid">
+                  <object class="GtkBox">
+                    <property name="name">Watermark</property>
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="hexpand">True</property>
-                    <property name="row_spacing">6</property>
-                    <property name="column_spacing">12</property>
+                    <property name="valign">start</property>
+                    <property name="margin_top">6</property>
+                    <property name="margin_bottom">18</property>
+                    <property name="vexpand">False</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</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">Preset</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">Format</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">Width</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">Height</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkComboBoxText" id="Export_preset">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="active">4</property>
-                        <items>
-                          <item translatable="yes">Custom Settings</item>
-                          <item translatable="yes">Export For Facebook</item>
-                          <item translatable="yes">Export For Flickr</item>
-                          <item translatable="yes">Export For Direct Web Publishing</item>
-                          <item translatable="yes">Export For Printing</item>
-                        </items>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinButton" id="Export_width_spin">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="input_purpose">digits</property>
-                        <property name="adjustment">Export_width</property>
-                        <property name="climb_rate">1</property>
-                        <property name="snap_to_ticks">True</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinButton" id="Export_height_spin">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="adjustment">Export_height</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">&lt;b&gt;Format&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">3</property>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkComboBoxText" id="Export_format">
+                      <object class="GtkGrid">
                         <property name="visible">True</property>
-                        <property name="sensitive">False</property>
                         <property name="can_focus">False</property>
-                        <property name="active">2</property>
-                        <items>
-                          <item translatable="yes">PNG</item>
-                          <item translatable="yes">JPEG</item>
-                          <item translatable="yes">TIFF</item>
-                        </items>
+                        <property name="halign">center</property>
+                        <property name="hexpand">True</property>
+                        <property name="row_spacing">6</property>
+                        <property name="column_spacing">12</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">Preset</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">Format</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">Width</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">Height</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="Export_width_spin">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">True</property>
+                            <property name="text" translatable="yes">0</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">Export_width</property>
+                            <property name="climb_rate">1</property>
+                            <property name="snap_to_ticks">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="Export_height_spin">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">True</property>
+                            <property name="text" translatable="yes">0</property>
+                            <property name="adjustment">Export_height</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBoxText" id="Export_format">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">False</property>
+                            <property name="active">2</property>
+                            <items>
+                              <item translatable="yes">PNG</item>
+                              <item translatable="yes">JPEG</item>
+                              <item translatable="yes">TIFF</item>
+                            </items>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="Export_quality_spin">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">True</property>
+                            <property name="text" translatable="yes">90</property>
+                            <property name="adjustment">Export_quality</property>
+                            <property name="value">90</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</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">JPEG Quality</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSwitch" id="Export_pngcrush">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">True</property>
+                            <property name="halign">start</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">5</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">PNG Crush</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">5</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBoxText" id="Export_preset">
+                            <property name="width_request">250</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="active">4</property>
+                            <items>
+                              <item translatable="yes">Custom Settings</item>
+                              <item translatable="yes">Export For Facebook</item>
+                              <item translatable="yes">Export For Flickr</item>
+                              <item translatable="yes">Export For Direct Web Publishing</item>
+                              <item translatable="yes">Export For Printing</item>
+                            </items>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">0</property>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinButton" id="Export_quality_spin">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
-                        <property name="text" translatable="yes">90</property>
-                        <property name="adjustment">Export_quality</property>
-                        <property name="value">90</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">4</property>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
                       </packing>
                     </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</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">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="name">Watermark</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="valign">start</property>
+                    <property name="margin_top">6</property>
+                    <property name="margin_bottom">18</property>
+                    <property name="vexpand">False</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</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">JPEG Quality</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSwitch" id="Export_pngcrush">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
                         <property name="halign">start</property>
+                        <property name="label" translatable="yes">&lt;b&gt;Metadata&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">5</property>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel">
+                      <object class="GtkGrid">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">PNG Crush</property>
+                        <property name="halign">center</property>
+                        <property name="hexpand">True</property>
+                        <property name="row_spacing">6</property>
+                        <property name="column_spacing">12</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">Scheme</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">Artist</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">Copyright</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Export_artist">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Export_copyright">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBoxText" id="Export_metadata">
+                            <property name="width_request">250</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="active">0</property>
+                            <items>
+                              <item translatable="yes">Include Original Metadata</item>
+                              <item translatable="yes">Ignore Original Metadata</item>
+                              <item translatable="yes">Strip GPS Information</item>
+                            </items>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</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">Description</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Export_description">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">5</property>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
                       </packing>
                     </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">1</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
                   </packing>
                 </child>
               </object>
@@ -284,5 +466,8 @@
         </child>
       </object>
     </child>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
   </object>
 </interface>

+ 112 - 2
ui/PF2_Activity.glade

@@ -360,6 +360,108 @@
             <property name="position">3</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="pixbuf">complete.png</property>
+                <property name="pixel_size">128</property>
+                <property name="icon_size">6</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</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="label" translatable="yes">Export Complete</property>
+                <attributes>
+                  <attribute name="scale" value="1.5"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="PF2_export_complete_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">8</property>
+                <property name="margin_bottom">8</property>
+                <property name="ellipsize">end</property>
+                <attributes>
+                  <attribute name="scale" value="1"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButtonBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">center</property>
+                <property name="layout_style">expand</property>
+                <child>
+                  <object class="GtkButton" id="PF2_export_complete_back">
+                    <property name="label" translatable="yes">Back to Photo</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="PF2_export_again">
+                    <property name="label" translatable="yes">Export Again</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="name">export_complete</property>
+            <property name="title" translatable="yes">page0</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
       </object>
       <packing>
         <property name="expand">True</property>
@@ -401,8 +503,6 @@
                   <object class="GtkBox">
                     <property name="visible">True</property>
                     <property name="can_focus">False</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>
@@ -411,6 +511,8 @@
                       <object class="GtkRevealer" id="PF2_histogram_reveal">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="margin_left">18</property>
+                        <property name="margin_right">18</property>
                         <property name="reveal_child">True</property>
                         <child>
                           <object class="GtkBox">
@@ -530,6 +632,8 @@
                       <object class="GtkRevealer" id="PF2_layers_reveal">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="margin_left">18</property>
+                        <property name="margin_right">18</property>
                         <property name="transition_type">slide-up</property>
                         <child>
                           <object class="GtkBox">
@@ -753,6 +857,8 @@
                       <object class="GtkRevealer" id="PF2_layer_mask_reveal">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="margin_left">18</property>
+                        <property name="margin_right">18</property>
                         <property name="transition_type">slide-up</property>
                         <child>
                           <object class="GtkBox">
@@ -917,6 +1023,8 @@
                       <object class="GtkBox">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="margin_left">18</property>
+                        <property name="margin_right">18</property>
                         <property name="orientation">vertical</property>
                         <property name="spacing">6</property>
                         <child>
@@ -983,6 +1091,8 @@
                               <object class="GtkStack" id="PF2_tool_stack">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
+                                <property name="margin_left">18</property>
+                                <property name="margin_right">18</property>
                                 <property name="hhomogeneous">False</property>
                                 <property name="vhomogeneous">False</property>
                                 <property name="transition_type">crossfade</property>

二進制
ui/complete.png