# -*- coding: utf-8 -*-
 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009-2011 Rob G. Healey <robhealey1@gmail.com>
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# $Id: EditExifMetadata.py 17749 2011-06-12 02:58:47Z robhealey1 $

# ************************************************************************
# Python Modules
# ************************************************************************
import os
from datetime import datetime, date
import calendar, time


# abilty to escape certain characters from output...
from xml.sax.saxutils import escape as _html_escape

from itertools import chain

from decimal import Decimal, getcontext
getcontext().prec = 4
from fractions import Fraction

import subprocess

# ------------------------------------------------------------------------
# GTK modules
# ------------------------------------------------------------------------
import gtk

# ------------------------------------------------------------------------
# GRAMPS modules
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# GRAMPS modules
# ------------------------------------------------------------------------
import GrampsDisplay

from gen.plug import Gramplet
from DateHandler import displayer as _dd

import gen.lib
import gen.mime
import Utils
from PlaceUtils import conv_lat_lon

from QuestionDialog import WarningDialog, QuestionDialog

from gen.ggettext import gettext as _

#####################################################################
#               Check for pyexiv2 library...
#####################################################################
# pyexiv2 download page (C) Olivier Tilloy
_DOWNLOAD_LINK = "http://tilloy.net/dev/pyexiv2/download.html"

# make sure the pyexiv2 library is installed and at least a minimum version
software_version = False
Min_VERSION = (0, 1, 3)
Min_VERSION_str = "pyexiv2-%d.%d.%d" % Min_VERSION
Pref_VERSION_str = "pyexiv2-%d.%d.%d" % (0, 3, 0)

# to be able for people that have pyexiv2-0.1.3... 
LesserVersion = False

try:
    import pyexiv2
    software_version = pyexiv2.version_info

except ImportError, msg:
    WarningDialog(_("You need to install, %s or greater, for this addon "
        "to work. \n  I would recommend installing, %s, and it may be "
        "downloaded from here: \n%s") % ( Min_VERSION_str, Pref_VERSION_str,
        _DOWNLOAD_LINK), str(msg))
    raise Exception(_("Failed to load 'Edit Image Exif Metadata'..."))
               
# This only happends if the user has pyexiv2-0.1.3 installed
except AttributeError:
    LesserVersion = True

# the library is either not installed or does not meet 
# minimum required version for this addon....
if (software_version and (software_version < Min_VERSION)):
    msg = _("The minimum required version for pyexiv2 must be %s \n"
        "or greater.  Or you do not have the python library "
        "installed yet.  You may download it from here: %s\n\n "
        "I recommend getting, %s") % (Min_VERSION_str,
        _DOWNLOAD_LINK, Pref_VERSION_str)
    WarningDialog(msg)
    raise Exception(msg)

# *******************************************************************
#         Determine if we have access to outside Programs
#
# The programs are ImageMagick, and jhead
# * ImageMagick -- Convert and Delete all Exif metadata...
# * jhead       -- re-initialize a jpeg, and other features...
#********************************************************************
# Windows 32bit systems
system_platform = os.sys.platform
MAGICK_FOUND_ = False
JHEAD_FOUND_ = False
if system_platform == "win32":
    if Utils.search_for("convert.exe"):
        MAGICK_FOUND_ = "convert.exe"

    if Utils.search_for("jhead.exe"):
        _JHEAD_FOUND_ = "jhead.exe"

elif system_platform == "linux2":
    if Utils.search_for("convert"):
        MAGICK_FOUND_ = "convert"

    if Utils.search_for("jhead"):
        JHEAD_FOUND_ = "jhead"

else:
    if Utils.search_for("convert"):
        MAGICK_FOUND_ = "convert"

    if Utils.search_for("jhead"):
        JHEAD_FOUND_ = "jhead"

# if external programs are not found, let the user know about 
# the missing functionality?
if not MAGICK_FOUND_:
    print(_("ImageMagick's convert program was not found "
        "on this computer.\n  You may download it from "
        "here: %s...") % ("http://www.imagemagick.org/script/index.php"))

if not JHEAD_FOUND_:
    print(_("Jhead program was not found on this computer.\n"
        "You may download it from: %s...") % (
            "http://www.sentex.net/~mwandel/jhead/"))

# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------
# available image types for exiv2 and pyexiv2
# ["jpeg", "jpg", "exv", "tiff", "dng", "nef", "pef", "pgf", "png", "psd", "jp2"]

# define tooltips for all entries
_TOOLTIPS = {

    # Description...
    "Description"       : _("Provide a short descripion for this image."),

    # Artist 
    "Artist"            : _("Enter the Artist/ Author of this image.  The "
        "person's name or the company who is responsible for the creation "
        "of this image."),

    # Copyright
    "Copyright" : _("Enter the copyright information for this image. \n"
        "Example: (C) 2010 Smith and Wesson"),

    # Calendar date select...
    "Date:Select" : _("Allows you to select a date from a pop-up window "
        "calendar. \n  Warning:  You will still need to edit the time..."),

    # Original Date/ Time... 
    "DateTime" : _("Original Date/ Time of this image.\n"
        "Example: 1826-Apr-12 14:30:00, 1826-April-12, 1998-01-31 13:30:00"),

    # Convert to decimal button...
    "GPSFormat:Decimal" : _("Converts Degree, Minutes, Seconds GPS "
        "coordinates to a Decimal representation."),

    # convert to degrees, minutes, seconds button...
    "GPSFormat:DMS" : _("Converts Decimal GPS coordinates "
        "to a Degrees, Minutes, Seconds representation."),

    # GPS Latitude...
    "Latitude" : _(u"Enter the GPS Latitude coordinates for your image,\n"
        u"Example: 43.722965, 43 43 22 N, 38° 38′ 03″ N, 38 38 3"),

    # GPS Longitude...
    "Longitude" : _(u"Enter the GPS Longitude coordinates for your image,\n"
        u"Example: 10.396378, 10 23 46 E, 105° 6′ 6″ W, -105 6 6") }.items()

# set up Exif keys for Image.exif_keys
_DATAMAP = {
    "Exif.Image.ImageDescription"  : "Description",
    "Exif.Image.DateTime"          : "Modified",
    "Exif.Image.Artist"            : "Artist",
    "Exif.Image.Copyright"         : "Copyright",
    "Exif.Photo.DateTimeOriginal"  : "DateTime",
    "Exif.GPSInfo.GPSLatitudeRef"  : "LatitudeRef",
    "Exif.GPSInfo.GPSLatitude"     : "Latitude",
    "Exif.GPSInfo.GPSLongitudeRef" : "LongitudeRef",
    "Exif.GPSInfo.GPSLongitude"    : "Longitude"}
_DATAMAP  = dict( (key, val) for key, val in _DATAMAP.items() )
_DATAMAP.update( (val, key) for key, val in _DATAMAP.items()  )

# Toolt tips for the buttons in the gramplet...
_BUTTONTIPS = {

    # copyto button...
    "CopyTo" : _("Copies information from the Display area to the Edit area."),

    # Clear Edit Area button... 
    "Clear" : _("Clears the Exif metadata from the Edit area."),

    # Wiki Help button...
    "Help" : _("Displays the Gramps Wiki Help page for 'Edit Image "
        "Exif Metadata' in your web browser."),

    # Save Exif Metadata button...
    "Save" : _("Saves/ writes the Exif metadata to this image.\n"
        "WARNING: Exif metadata will be erased if you save a blank entry field...") }

# if ImageMagick is installed on this computer then, add button 
# tooltips for these two buttons...
if MAGICK_FOUND_:
    _BUTTONTIPS.update( {

        # Convert to .Jpeg button...
        "Convert" : _("If your image is not a jpeg image, convert it to jpeg?"),

        # Delete/ Erase/ Wipe Exif metadata button...
        "Delete" : _("WARNING:  This will completely erase all Exif metadata "
            "from this image!  Are you sure that you want to do this?") } )

# ------------------------------------------------------------------------
#
# 'Edit Image Exif metadata' Gramplet class...
#
# ------------------------------------------------------------------------
class EditExifMetadata(Gramplet):

    def init(self):

        self.exif_column_width = 12
        self.exif_widgets = {}

        # set all dirty variables to False to begin this addon...
        self._dirty = False

        self.orig_image    = False
        self.image_path    = False
        self.plugin_image  = False

        self.connect_signal("Media", self.update)
        vbox = self.__build_gui()

        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(vbox)
        vbox.show_all()

        # provide tooltips for all fields and buttons...
        _setup_widget_tooltips(self.exif_widgets)

    def __build_gui(self):

        vbox = gtk.VBox()

        medialabel = gtk.HBox(False)
        self.exif_widgets["Media:Label"] = gtk.Label()
        self.exif_widgets["Media:Label"].set_alignment(0.0, 0.0)
        medialabel.pack_start(self.exif_widgets["Media:Label"], expand =False)
        vbox.pack_start(medialabel, expand =False)

        mimetype = gtk.HBox(False)
        self.exif_widgets["Mime:Type"] = gtk.Label()
        self.exif_widgets["Mime:Type"].set_alignment(0.0, 0.0)
        mimetype.pack_start(self.exif_widgets["Mime:Type"], expand =False)
        vbox.pack_start(mimetype, expand =False)

        messagearea = gtk.HBox(False)
        self.exif_widgets["Message:Area"] = gtk.Label(_("Click an image to begin..."))
        self.exif_widgets["Message:Area"].set_alignment(0.5, 0.0)
        messagearea.pack_start(self.exif_widgets["Message:Area"], expand =False)
        vbox.pack_start(messagearea, expand =False)

        self.model = gtk.ListStore(object, str, str)
        view = gtk.TreeView(self.model)

        # Key Column
        view.append_column( self.__create_column(_("Key"), 1) )

        # Value Column
        view.append_column( self.__create_column(_("Value"), 2) )

        # copyto, Clear, Convert horizontal box
        ccc_box = gtk.HButtonBox()
        ccc_box.set_layout(gtk.BUTTONBOX_START)
        vbox.pack_start(ccc_box, expand =False, fill =False, padding =10)

        # Copy To Edit Area button...
        ccc_box.add( self.__create_button(
            "CopyTo", False, [self.copyto], gtk.STOCK_COPY, False) )

        # Clear button...
        ccc_box.add( self.__create_button(
            "Clear", False, [self.clear_metadata], gtk.STOCK_CLEAR, False) )

        # is ImageMagick installed?
        if MAGICK_FOUND_:
            # Convert button...
            ccc_box.add( self.__create_button(
                "Convert", False, [self.__convert_dialog],
                    gtk.STOCK_CONVERT, False) )

        for items in [

            # Image Description
            ("Description",     _("Description"),     None, False, [],  True,  0),

            # Last Modified Date/ Time
            ("Modified",        _("Last Changed"),    None, True,  [],  True,  0),

            # Artist field
            ("Artist",          _("Artist"),          None, False, [],  True,  0),

            # copyright field
            ("Copyright",       _("Copyright"),       None, False, [],  True,  0),

            # calendar date clickable entry
            ("Date",            "",                   None, True,
            [("Select",         _("Select Date"),  "button", self.select_date)],
                                                                     True,  0),
            # Original Date/ Time Entry, 1826-April-12 14:06:00
            ("DateTime",         _("Date/ Time"),     None, False, [], True, 0),

            # Convert GPS coordinates
            ("GPSFormat",       _("Convert GPS"),     None, True,
            [("Decimal",        _("Decimal"),         "button", self.convert2decimal),
             ("DMS",            _("Deg. Min. Sec."),  "button", self.convert2dms)], 
                                                                       False, 0),    
  
            # Latitude and Longitude for this image 
            ("Latitude",        _("Latitude"),        None, False, [],  True,  0),
            ("Longitude",       _("Longitude"),       None, False, [],  True,  0) ]:

            pos, text, choices, readonly, callback, dirty, default = items
            row = self.make_row(pos, text, choices, readonly, callback, dirty, default)
            vbox.pack_start(row, False)

        # Help, Save, Delete horizontal box
        hsd_box = gtk.HButtonBox()
        hsd_box.set_layout(gtk.BUTTONBOX_START)
        vbox.pack_start(hsd_box, expand =False, fill =False, padding =10)

        # Help button...
        hsd_box.add( self.__create_button(
            "Help", False, [self._help_page], gtk.STOCK_HELP) )

        # Save button...
        hsd_box.add( self.__create_button(
            "Save", False, [self.save_metadata, self.update, self.display_exif_tags, self.copyto],
                gtk.STOCK_SAVE, False))

        if MAGICK_FOUND_:
            # Delete All Metadata button...
            hsd_box.add(self.__create_button(
                "Delete", False, [self.__delete_dialog], gtk.STOCK_DELETE, False))

        # adds Exif Metadata Viewing Area
        vbox.pack_start(view, padding =10)

        return vbox

    def update_has_data(self):
        active_handle = self.get_active('Media')
        active = self.dbstate.db.get_object_from_handle(active_handle)
        self.set_has_data(self.get_has_data(active))

    def get_has_data(self, media):
        """
        Return True if the gramplet has data, else return False.
        """
        if media is None:
            return False

        full_path = Utils.media_path_full(self.dbstate.db, media.get_path() )
        if not os.path.isfile(full_path):
            return False

        if LesserVersion: # prior to v0.2.0
            metadata = pyexiv2.Image(full_path)
            try:
                metadata.readMetadata()
            except (IOError, OSError):
                return False

            if metadata.exifKeys():
                return True

        else: # v0.2.0 and above
            metadata = pyexiv2.ImageMetadata(full_path)
            try:
                metadata.read()
            except (IOError, OSError):
                return False

            if metadata.exif_keys:
                return True

        return False

    def _help_page(self):
        """
        will bring up a Wiki help page.
        """

        GrampsDisplay.help(webpage = "Edit Image Exif Metadata")

    def activate_buttons(self, ButtonList):
        """
        Enable/ activate the buttons that are in ButtonList
        """

        for ButtonName in ButtonList:
            self.exif_widgets[ButtonName].set_sensitive(True)

    def deactivate_buttons(self, ButtonList):
        """
        disable/ de-activate buttons in ButtonList
        """
        for ButtonName in ButtonList:
            self.exif_widgets[ButtonName].set_sensitive(False)

    def main(self): # return false finishes
        """
        get the active media, mime type, and reads the image metadata
        """
        db = self.dbstate.db

        # clear Display and Edit Areas
        self.clear_metadata(self.orig_image)
        self.model.clear()

        # De-activate the buttons except for Help...
        self.deactivate_buttons(["CopyTo", "Clear", "Save"])

        if MAGICK_FOUND_:
            self.deactivate_buttons(["Convert", "Delete"])

        # Re-post initial image message...
        self.exif_widgets["Message:Area"].set_text(_("Select an "
            "image to begin..."))

        active_handle = self.get_active("Media")
        if not active_handle:
            self.set_has_data(False)
            return

        self.orig_image = db.get_object_from_handle(active_handle)
        self.image_path = Utils.media_path_full(db, self.orig_image.get_path() )
        if (not self.orig_image or not os.path.isfile(self.image_path)):
            self.exif_widgets["Message:Area"].set_text(_("Image is either "
            "missing or deleted,\n  Choose a different image..."))
            return

        # check image read privileges...
        _readable = os.access(self.image_path, os.R_OK)
        if not _readable:
            self.exif_widgets["Message:Area"].set_text(_("Image is NOT readable,\n"
                "Choose a different image..."))
            return

        # check image write privileges...
        _writable = os.access(self.image_path, os.W_OK)
        if not _writable:
            self.exif_widgets["Message:Area"].set_text(_("Image is NOT writable,\n"
                "You will NOT be able to save Exif metadata...."))

        # Mime type information...
        mime_type = self.orig_image.get_mime_type()
        _mtype = gen.mime.get_description(mime_type)
        self.exif_widgets["Mime:Type"].set_text(_mtype)

        # determine if it is a mime image object?
        if mime_type:
            if mime_type.startswith("image"):
                self.activate_buttons(["Save"])

                # display file description/ title...
                self.exif_widgets["Media:Label"].set_text( _html_escape(
                    self.orig_image.get_description()))

                # will create the image and read it...
                self.setup_image(self.image_path, True)

                # Checks to make sure that ImageMagick is installed on 
                # this computer and the image is NOT a jpeg image...
                if MAGICK_FOUND_:
                    basename, extension = os.path.splitext(self.image_path)
                    if extension not in [".jpeg", ".jpg", ".jfif"]:
                        self.activate_buttons(["Convert"])

                # displays the imge Exif metadata
                self.display_exif_tags()

            else:
                self.exif_widgets["Message:Area"].set_text(_("Choose a "
                    "different image..."))
                return

        else:
            self.exif_widgets["Message:Area"].set_text(_("Choose a "
                "different image..."))
            return

    def __create_column(self, name, colnum, fixed =True):
        """
        will create the column for the column row...
        """

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(name, renderer, text =colnum)

        if fixed:
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            column.set_expand(True)

        else:
            column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            column.set_expand(False)

        column.set_alignment(0.0)
        column.set_sort_column_id(colnum)

        return column

    def __create_button(self, pos, text, callback =False , icon =False, sensitive = True):
        """
        creates and returns a button for display
        """

        if (icon and not text):
            button = gtk.Button(stock=icon)
        else:
            button = gtk.Button(text)

        if callback is not []:
            for call_ in callback:
                button.connect("clicked", call_)

        if not sensitive:
            button.set_sensitive(False)
        self.exif_widgets[pos] = button

        return button

    def __convert_dialog(self, object):
        """
        Handles the Convert question Dialog...
        """

        # is ImageMagick installled?
        if MAGICK_FOUND_:
            QuestionDialog(_("Edit Image Exif Metadata"), _("Convert this "
                "image to a .jpeg image?"), _("Convert"), self.convert2Jpeg)

    def __delete_dialog(self, object):
        """
        Handles the Delete Dialog...
        """

        QuestionDialog(_("Edit Image Exif Metadata"), _("WARNING!  You are "
            "about to completely delete the Exif metadata from this image?"),
            _("Delete"), self.strip_metadata)

    def setup_image(self, full_path, createimage =False):
        """
        will return an image instance and read the Exif metadata.

        if createimage is True, it will create the pyexiv2 image instance...

        LesserVersion -- prior to pyexiv2-0.2.0
                      -- pyexiv2-0.2.0 and above...
        """

        if createimage:
            if LesserVersion:
                self.plugin_image = pyexiv2.Image(full_path)
            else:
                self.plugin_image = pyexiv2.ImageMetadata(full_path)

        if LesserVersion:
            try:
                self.plugin_image.readMetadata()
            except (IOError, OSError):
                self.set_has_data(False)
                return 

        else:
            try:
                self.plugin_image.read()
            except (IOError, OSError):
                self.set_has_data(False)
                return

    def make_row(self, pos, text, choices=None, readonly=False, 
        callback_list =[], mark_dirty=False, default=0):

        # Edit Image Exif Metadata
        row = gtk.HBox()
        label = gtk.Label()
        if readonly:
            label.set_text("<b>%s</b>" % text)
            label.set_width_chars(self.exif_column_width)
            label.set_use_markup(True)
            self.exif_widgets[pos] = gtk.Label()
            self.exif_widgets[pos].set_alignment(0.0, 0.5)
            self.exif_widgets[pos].set_use_markup(True)
            label.set_alignment(0.0, 0.5)
            row.pack_start(label, False)
            row.pack_start(self.exif_widgets[pos], False)
        else:
            label.set_text("%s: " % text)
            label.set_width_chars(self.exif_column_width)
            label.set_alignment(1.0, 0.5) 
            if choices == None:
                self.exif_widgets[pos] = gtk.Entry()
                if mark_dirty:
                    self.exif_widgets[pos].connect("changed", self._mark_dirty)
                row.pack_start(label, False)
                row.pack_start(self.exif_widgets[pos], True)
            else:
                eventBox = gtk.EventBox()
                self.exif_widgets[pos] = gtk.combo_box_new_text()
                eventBox.add(self.exif_widgets[pos])
                for add_type in choices:
                    self.exif_widgets[pos].append_text(add_type)
                self.exif_widgets[pos].set_active(default) 
                if mark_dirty:
                    self.exif_widgets[pos].connect("changed", self._mark_dirty)
                row.pack_start(label, False)
                row.pack_start(eventBox, True)
        for name, text, cbtype, callback in callback_list:
            if cbtype == "button":
                label = gtk.Label()
                label.set_text(text)
                self.exif_widgets[pos + ":" + name + ":Label"] = label
                row.pack_start(label, False)
                icon = gtk.STOCK_EDIT
                size = gtk.ICON_SIZE_MENU
                button = gtk.Button()
                image = gtk.Image()
                image.set_from_stock(icon, size)
                button.add(image)
                button.set_relief(gtk.RELIEF_NONE)
                button.connect("clicked", callback)
                self.exif_widgets[pos + ":" + name] = button
                row.pack_start(button, False)
            elif cbtype == "checkbox":
                button = gtk.CheckButton(text)
                button.set_active(True)
                button.connect("clicked", callback)
                self.exif_widgets[pos + ":" + name] = button
                row.pack_start(button, False)
        row.show_all()
        return row

# -----------------------------------------------
# Error Checking functions
# -----------------------------------------------
    def _mark_dirty(self, object):
        pass

    def _get_value(self, keytag):
        """
        gets the value from the Exif Key, and returns it...

        @param: keytag -- image metadata key
        """

        KeyValue = ""
        if LesserVersion:
            KeyValue = self.plugin_image[keytag]
        else:
            try:
                KeyValue = self.plugin_image[keytag].value

            except (KeyError, ValueError, AttributeError):
                pass

        return KeyValue

    def display_exif_tags(self, mediadatatags_ =None):
        """
        once the pyexiv2.Image has been created, we display
            all of the image Exif metadata...
        """

        mediadatatags_ = _get_exif_keypairs(self.plugin_image)
        if not mediadatatags_:
            return

        self.model.clear()

        # set has data flag...
        self.set_has_data(len(mediadatatags_) > 0)

        # Activate Clear and Save buttons...
        self.activate_buttons(["Clear", "Save"])

        # check to see if we got metadata from the media object?
        if mediadatatags_:

            # activate copyto button...
            self.activate_buttons(["CopyTo"])

            # set Message Area to Display...
            self.exif_widgets["Message:Area"].set_text(_("Displaying image "
                "Exif metadata..."))

            for keytag in mediadatatags_:

                if LesserVersion:
                    label = self.plugin_image.tagDetails(keytag)[0]
                    human_value = self.plugin_image.interpretedExifValue(keytag)

                else:
                    try:
                        tag = self.plugin_image[keytag]
                        label = tag.label
                        human_value = tag.human_value
                    except AttributeError:
                        human_value = False

                if keytag in ("Exif.Image.DateTime",
                    "Exif.Photo.DateTimeOriginal",
                    "Exif.Photo.DateTimeDigitized"):
                    human_value = _process_datetime(self._get_value(keytag))

                if human_value is not False:
                    self.model.append((self.plugin_image, label, human_value))

    def copyto(self, imagekeytags_ =None):
        """
        reads the image metadata after the pyexiv2.Image has been created
        """

        imagekeytags_ = _get_exif_keypairs(self.plugin_image)
        if imagekeytags_:
            imagekeytags_ = [keytag for keytag in imagekeytags_ if keytag in _DATAMAP]

            self.exif_widgets["Message:Area"].set_text(_("Copying Exif "
                "metadata to the Edit Area..."))

        for keytag in imagekeytags_:

            # name for matching to exif_widgets 
            widgetsName = _DATAMAP[keytag]

            tagvalue_ = self._get_value(keytag)
            if tagvalue_:

                if widgetsName in ["Description", "Artist", "Copyright"]:
                    self.exif_widgets[widgetsName].set_text(tagvalue_)

                # Original Date of the image...
                elif widgetsName == "DateTime":
                    use_date = self._get_value(keytag)
                    use_date = _process_datetime(use_date) if use_date else False
                    if use_date is not False:
                        self.exif_widgets[widgetsName].set_text(use_date)

                # latituderef, Latitude, longituderef, Longitude...
                elif widgetsName == "Latitude":

                    latitude  =  self._get_value(keytag)
                    longitude = self._get_value(_DATAMAP["Longitude"] )

                    # if latitude and longitude exist, display them?
                    if (latitude and longitude):

                        # split latitude metadata into (degrees, minutes, 
                        # and seconds) from Rational
                        latdeg, latmin, latsec = rational_to_dms(latitude)

                        # split longitude metadata into degrees, minutes, and seconds
                        longdeg, longmin, longsec = rational_to_dms(longitude)

                        # check to see if we have valid GPS coordinates?
                        latfail = any(coords == False for coords in [latdeg, latmin, latsec])
                        longfail = any(coords == False for coords in [longdeg, longmin, longsec])
                        if (not latfail and not longfail):

                            # Latitude Direction Reference
                            latituderef = self._get_value(_DATAMAP["LatitudeRef"] )

                            # Longitude Direction Reference
                            longituderef = self._get_value(_DATAMAP["LongitudeRef"] )

                            # set display for Latitude GPS coordinates
                            self.exif_widgets["Latitude"].set_text(
                                """%s° %s′ %s″ %s""" % (latdeg, latmin, latsec, latituderef))

                            # set display for Longitude GPS coordinates
                            self.exif_widgets["Longitude"].set_text(
                                """%s° %s′ %s″ %s""" % (longdeg, longmin, longsec, longituderef))

        # enable Save button after metadata has been "Copied to Edit Area"...
        self.activate_buttons(["Save"])

        if MAGICK_FOUND_:
            self.activate_buttons(["Delete"])

        # Clear the Message Area...
        self.exif_widgets["Message:Area"].set_text("")

    def clear_metadata(self, obj, cleartype = "All"):
        """
        clears all data fields to nothing

        @param: cleartype -- 
            "Date" = clears only Date entry fields
            "All" = clears all data fields
        """

        # set Message Area text...
        self.exif_widgets["Message:Area"].set_text(_("Edit area has been cleared..."))

        # clear all data fields
        if cleartype == "All":
            for widgetsName in ["Description", "Artist", "Copyright", "DateTime",
                "Latitude", "Longitude"]:  
                self.exif_widgets[widgetsName].set_text("")

        # clear only the date/ time field
        else:
             self.exif_widgets["DateTime"].set_text("")

    def convert2Jpeg(self):
        """
        Will attempt to convert an image to jpeg if it is not?
        """

        # if ImageMagick's convert is installed...
        if MAGICK_FOUND_:

            filepath, basename = os.path.split(self.image_path)
            basename, oldext = os.path.splitext(self.image_path)
            newextension = ".jpeg"

            convert = subprocess.check_call(["convert", self.image_path, 
                    os.path.join(filepath, basename + newextension) ] )
            if str(convert):

                # set Message Area to Convert...
                self.exif_widgets["Message:Area"].set_text(_("Converting image,\n"
                    "You will need to delete the original image file..."))

                self.deactivate_buttons(["Convert"])

    def _set_exif_keytag(self, keytag, KeyValue):
        """
        sets the value for the metadata keytags
        """

        if LesserVersion:
            self.plugin_image[keytag] = KeyValue

        else:
            try:  # tag is being modified...
                self.plugin_image[keytag] = KeyValue

            except KeyError:  # tag has not been set...
                self.plugin_image[keytag] = pyexiv2.ExifTag(keytag, KeyValue)

            except (ValueError, AttributeError):  # there is an issue with 
                                                  # either keytag or KeyValue
                pass

    def write_metadata(self, imageinstance):
        """
        writes the Exif metadata to the image.

        LesserVersion -- prior to pyexiv2-0.2.0
                      -- pyexiv2-0.2.0 and above... 
        """
        if LesserVersion:
            imageinstance.writeMetadata()

        else:
            imageinstance.write()

    def addsymbols2gps(self, latitude =False, longitude =False):
        """
        converts a degrees, minutes, seconds representation of 
        Latitude/ Longitude
        without their symbols to having them...
        """

        if not latitude and not longitude:
            return [False]*2

        latituderef, longituderef = "N", "E"

        if (latitude.count(".") == 1 and longitude.count(".") == 1):
            latitude, longitude = self.convert2dms(latitude, longitude)

        # add DMS symbols if necessary?
        # the conversion to decimal format, require the DMS symbols
        elif ( (latitude.count("°") == 0 and longitude.count("°") == 0) and
            (latitude.count("′") == 0 and longitude.count("′") == 0) and
            (latitude.count('″') == 0 and longitude.count('″') == 0) ):

            # is there a direction element here?
            if (latitude.count("N") == 1 or latitude.count("S") == 1):
                latdeg, latmin, latsec, latituderef = latitude.split(" ", 3)
            else:
                atitudeRef = "N"
                latdeg, latmin, latsec = latitude.split(" ", 2)
                if latdeg[0] == "-":
                    latdeg = latdeg.replace("-", "")
                    latituderef = "S"

            # is there a direction element here?
            if (longitude.count("E") == 1 or longitude.count("W") == 1):
                longdeg, longmin, longsec, longituderef = longitude.split(" ", 3)
            else:
                longituderef = "E"
                longdeg, longmin, longsec = longitude.split(" ", 2)
                if longdeg[0] == "-":
                    longdeg = longdeg.replace("-", "")
                    longituderef = "W"

            latitude  = """%s° %s′ %s″ %s""" % (latdeg, latmin, latsec, latituderef)
            longitude = """%s° %s′ %s″ %s""" % (longdeg, longmin, longsec, longituderef)

        return latitude, longitude

    def convert2decimal(self, object):
        """
        will convert a decimal GPS coordinates into decimal format.

        @param: latitude  -- GPS Latitude coordinates from data field...
        @param: longitude -- GPS Longitude coordinates from data field...
        """

        latitude  =  self.exif_widgets["Latitude"].get_text()
        longitude = self.exif_widgets["Longitude"].get_text()

        if not latitude and not longitude:
            return

        # is Latitude/ Longitude are in DMS format?
        if (latitude.count(" ") >= 2 and longitude.count(" ") >= 2): 

            # add DMS symbols if necessary?
            # the conversion to decimal format, require the DMS symbols 
            if ( (latitude.count("°") == 0 and longitude.count("°") == 0) and
                (latitude.count("′") == 0 and longitude.count("′") == 0) and
                (latitude.count('″') == 0 and longitude.count('″') == 0) ):

                latitude, longitude = self.addsymbols2gps(latitude, longitude)

            # convert degrees, minutes, seconds w/ symbols to an 8 point decimal
            latitude, longitude = conv_lat_lon( unicode(latitude),
                                                unicode(longitude), "D.D8")

            self.exif_widgets["Latitude"].set_text(latitude)
            self.exif_widgets["Longitude"].set_text(longitude)
        return latitude, longitude

    def convert2dms(self, latitude =False, longitude =False):
        """
        will convert a decimal GPS coordinates into degrees, minutes, seconds
        for display only
        """

        latitude  =  self.exif_widgets["Latitude"].get_text()
        longitude = self.exif_widgets["Longitude"].get_text()

        if not latitude and not longitude:
            return [False]*2

        latituderef, longituderef = "N", "E"

        # if coordinates are in decimal format?
        if ((latitude.find(".") <= 6) and (longitude.find(".") <= 6)):

            # convert latitude and longitude to a DMS with separator of ":"
            latitude, longitude = conv_lat_lon(latitude, longitude, "DEG-:")
 
            # remove negative symbol if there is one?
            if latitude[0] == "-":
                latitude = latitude.replace("-", "")
                latituderef = "S"
            latdeg, latmin, latsec = latitude.split(":", 2)

           # remove negative symbol if there is one?
            if longitude[0] == "-":
                longitude = longitude.replace("-", "")
                longituderef = "W"
            longdeg, longmin, longsec = longitude.split(":", 2)

            latitude = """%s° %s′ %s″ %s""" % (latdeg, latmin, latsec, latituderef)
            self.exif_widgets["Latitude"].set_text(latitude)

            longitude = """%s° %s′ %s″ %s""" % (longdeg, longmin, longsec, longituderef)
            self.exif_widgets["Longitude"].set_text(longitude)
        return latitude, longitude

    def save_metadata(self, datatags =False):
        """
        gets the information from the plugin data fields
        and sets the keytag = keyvalue image metadata
        """

        # determine if there has been something entered in the data fields?
        datatags = (self.exif_widgets["Description"].get_text(), 
                    self.exif_widgets["Artist"].get_text(), 
                    self.exif_widgets["Copyright"].get_text(),
                    self.exif_widgets["DateTime"].get_text(),
                    self.exif_widgets["Latitude"].get_text(),
                    self.exif_widgets["Longitude"].get_text() )

        # Description data field
        description = self.exif_widgets["Description"].get_text() 
        self._set_exif_keytag(_DATAMAP["Description"], description)

        # Modify Date/ Time... not a data field, but saved anyway...
        self._set_exif_keytag(_DATAMAP["Modified"], datetime.now() )

        # display modified Date/ Time
        self.exif_widgets["Modified"].set_text(
                _format_datetime(datetime.now() ) )
 
        # Artist/ Author data field
        artist = self.exif_widgets["Artist"].get_text()
        self._set_exif_keytag(_DATAMAP["Artist"], artist)

        # Copyright data field
        copyright = self.exif_widgets["Copyright"].get_text()
        self._set_exif_keytag(_DATAMAP["Copyright"], copyright)

        # Original Date/ Time data field
        datetime_ = self.exif_widgets["DateTime"].get_text()
        if datetime_:
            datetime_ = _process_datetime(datetime_, False)
            if datetime_ is not False:
                self._set_exif_keytag(_DATAMAP["DateTime"], datetime_)

        # Latitude/ Longitude data fields...
        self.__process_lat_long( self.exif_widgets["Latitude"].get_text(),
                                self.exif_widgets["Longitude"].get_text() )

        if datatags:
            # set Message Area to Saved...
            self.exif_widgets["Message:Area"].set_text(_("Saving Exif "
                    "metadata to the image..."))

        else:
            # set Message Area to Cleared...
            self.exif_widgets["Message:Area"].set_text(_("Image Exif "
                "metadata has been cleared from this image..."))

        # writes all Exif Metadata to image even if the fields are all empty.
        self.write_metadata(self.plugin_image)

        # Activate Delete button...
        if MAGICK_FOUND_:
            self.activate_buttons(["Delete"])

    def __process_lat_long(self, latitude, longitude):
        """
        process the latitude/ longitude for saving...
        """

        if not latitude and not longitude:
            return False, False

        # complete some error checking to prevent crashes...
        # if "?" character exist, remove it?
        if "?" in latitude:
            latitude = latitude.replace("?", "")
        if "?" in longitude:
            longitude = longitude.replace("?", "")

        # if "," character exists, remove it?
        if "," in latitude: 
            latitude = latitude.replace(",", "")
        if "," in longitude:
            longitude = longitude.replace(",", "") 

        # if there is no spaces then convert to DMS?
        if latitude.find(" ") == longitude.find(" "):
            if ((latitude.find(".") is not -1) and (longitude.find(".") is not -1)):
                latitude, longitude = self.convert2dms(latitude, longitude)

        # DMS is True...
        if ((latitude.find(" ") is not -1) and (longitude.find(" ") is not -1)):

            if latitude.find("N") > -1:
                latituderef = "N"
                latitude = latitude.replace("N", "")
            elif latitude.find(_("S")) > -1:
                latituderef = "S"
                latitude = latitude.replace("S", "")
            elif ((latitude.find(_("N")) == -1) and (latitude.find(_("S")) == -1)):
                if latitude.find("-") == -1:
                    latituderef = "N"
                else:
                    latituderef = "S"
                    latitude = latitude.replace("-", "")

            if longitude.find("E") > -1:
                longituderef = "E"
                longitude = longitude.replace("E", "")
            elif longitude.find("W") > -1:
                longituderef = "W"
                longitude = longitude.replace("W", "")
            elif ((longitude.find("E") == -1) and (longitude.find("W") == -1)):
                if longitude.find("-") == -1:
                    longituderef = "E"
                else:
                    longituderef = "W"
                    longitude = longitude.replace("-", "")

            # remove symbols before saving Latitude/ Longitude GPS coordinates
            latitude, longitude = _removesymbolsb4saving(latitude, longitude) 

            # remove leading and trailing whitespace
            latitude  =  latitude.strip()
            longitude = longitude.strip()
 
            # convert to pyexiv2.Rational for saving...
            latitude  =  coords_to_rational(latitude)
            longitude = coords_to_rational(longitude)

            # save Latitude and LatitudeRef
            self._set_exif_keytag(_DATAMAP["Latitude"], latitude)
            self._set_exif_keytag(_DATAMAP["LatitudeRef"], latituderef)

            # save Longitude and LongitudeRef
            self._set_exif_keytag(_DATAMAP["Longitude"], longitude)
            self._set_exif_keytag(_DATAMAP["LongitudeRef"], longituderef)

    def strip_metadata(self, erase_results =None):
        """
        Will completely and irrevocably erase all Exif metadata from this image.
        """

        if MAGICK_FOUND_:
            erase = subprocess.check_call( ["convert", self.image_path,
                "-strip", self.image_path] )
            erase_results = str(erase)

        else:
            mediadatatags_ = _get_exif_keypairs(self.plugin_image)
            if mediadatatags_: 
                for keytag in mediadatatags_:
                    del self.plugin_image[keytag]
                erase_results = True

                # write wiped metadata to image...
                self.write_metadata(self.plugin_image)

        if erase_results:

            # set Message Area for deleting...
            self.exif_widgets["Message:Area"].set_text(_("Deleting all Exif metadata..."))

            # Clear the Display and Edit Areas
            self.clear_metadata(self.plugin_image)
            self.model.clear()

            # Notify the User...
            self.exif_widgets["Message:Area"].set_text(_("All Exif metadata has been "
                    "deleted from this image..."))
            self.update()

            # re- initialize the image...
            if JHEAD_FOUND_:
                reinit = subprocess.check_call( ["jhead", "-purejpg", self.image_path] )
                reinitialize = str(reinit)
                if reinitialize:
                    self.exif_widgets["Message:Area"].set_text(_("Image has "
                    "be re- initialized for Exif metadata..."))

# -----------------------------------------------
#              Date Calendar functions
# -----------------------------------------------
    def select_date(self, object):
        """
        will allow you to choose a date from the calendar widget
        """
 
        tip = _("Double click a day to return the date.")

        self.app = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.app.tooltip = tip
        self.app.set_title(_("Select Date"))
        self.app.set_default_size(450, 200)
        self.app.set_border_width(10)
        self.exif_widgets["Calendar"] = gtk.Calendar()
        self.exif_widgets["Calendar"].connect('day-selected-double-click', self.double_click)
        self.app.add(self.exif_widgets["Calendar"])
        self.exif_widgets["Calendar"].show()
        self.app.show()

    def double_click(self, object):
        """
        receives double-clicked and returns the selected date
        widget
        """
        now = time.localtime()

        year, month, day = self.exif_widgets["Calendar"].get_date()
        self.exif_widgets["DateTime"].set_text( "%04d-%s-%02d %02d:%02d:%02d" % (
            year, _dd.long_months[month + 1], day, now[3], now[4], now[5]) )

        # close this window
        self.app.destroy()

    
def string_to_rational(coordinate):
    """
    convert string to rational variable for GPS
    """

    if '.' in coordinate:
        value1, value2 = coordinate.split('.')
        return pyexiv2.Rational(int(float(value1 + value2)), 10**len(value2))
    else:
        return pyexiv2.Rational(int(coordinate), 1)

def _get_exif_keypairs(plugin_image):
    """
    Will be used to retrieve and update the Exif metadata from the image.
    """

    if not plugin_image:
        return False
     
    mediadatatags_ = False
    if LesserVersion:  # prior to pyexiv2-0.2.0

        # get all keytags for this image for diplay only...
        mediadatatags_ = [keytag for keytag in plugin_image.exifKeys() ]

    else:  # pyexiv2-0.2.0 and above

        # get all keytags for this image for diplay only...
        mediadatatags_ = [keytag for keytag in chain(
            plugin_image.exif_keys, plugin_image.xmp_keys,
            plugin_image.iptc_keys) ]

    return mediadatatags_

def coords_to_rational(coordinates):
    """
    returns the GPS coordinates to Latitude/ Longitude
    """

    return [string_to_rational(coordinate) for coordinate in coordinates.split(" ")]

def convert_value(value):
    """
    will take a value from the coordinates and return its value
    """

    if isinstance(value, (Fraction, pyexiv2.Rational)):

        return str( ( Decimal(value.numerator) / Decimal(value.denominator) ) )

def _removesymbolsb4saving(latitude, longitude):
    """
    will recieve a DMS with symbols and return it without them

    @param: latitude -- Latitude GPS coordinates
    @param: longitude -- GPS Longitude coordinates
    """

    # check to see if latitude/ longitude exist?
    if (latitude and longitude):

        # remove degrees symbol if it exist?
        latitude = latitude.replace("°", "")
        longitude = longitude.replace("°", "")

        # remove minutes symbol if it exist?
        latitude = latitude.replace("′", "")
        longitude = longitude.replace("′", "")

        # remove seconds symbol if it exist?
        latitude = latitude.replace('″', "")
        longitude = longitude.replace('″', "")

    return latitude, longitude

def rational_to_dms(coords):
    """
    takes a rational set of coordinates and returns (degrees, minutes, seconds)

    [Fraction(40, 1), Fraction(0, 1), Fraction(1079, 20)]
    """

    degrees, minutes, seconds = [False]*3
    # coordinates look like:
    #     [Rational(38, 1), Rational(38, 1), Rational(150, 50)]
    # or [Fraction(38, 1), Fraction(38, 1), Fraction(318, 100)]   
    if isinstance(coords, list):
    
        if len(coords) == 3:
            return [convert_value(coordinate) for coordinate in coords]

    return degrees, minutes, seconds

def _format_datetime(exif_dt):
    """
    Convert a python datetime object into a string for display, using the
    standard Gramps date format.
    """

    date_part = gen.lib.Date()
    if isinstance(exif_dt, datetime):
        date_part.set_yr_mon_day(exif_dt.year, exif_dt.month, exif_dt.day)
        date_str = _dd.display(date_part)
        time_str = _('%(hr)02d:%(min)02d:%(sec)02d') % {'hr': exif_dt.hour,
                                                        'min': exif_dt.minute,
                                                        'sec': exif_dt.second}
    elif isinstance(exif_dt, str):
        exif_dt = _get_date_format(exif_dt)
        if exif_dt == False:
            return False

        date_part.set_yr_mon_day(exif_dt[0], exif_dt[1], exif_dt[2])
        date_str = _dd.display(date_part)
        time_str = _('%(hr)02d:%(min)02d:%(sec)02d') % {'hr' : exif_dt[3],
                                                        'min': exif_dt[4],
                                                        'sec': exif_dt[5]}
    return _('%(date)s %(time)s') % {'date': date_str, 'time': time_str}

def _get_date_format(datestr):
    """
    attempt to retrieve date format from date string
    """

    # attempt to determine the dateformat of the variable passed to it...
    tmpdate = False
    for dateformat in ["%Y-%m-%d %H:%M:%S", "%Y %m %d %H:%M:%S",
                       "%Y-%b-%d %H:%M:%S", "%Y %b %d %H:%M:%S",
                       "%Y-%B-%d %H:%M:%S", "%Y %B %d %H:%M:%S",
                       "%d-%m-%Y %H:%M:%S", "%d %m %Y %H:%M:%S",
                       "%d-%b-%Y %H:%M:%S", "%d %b %Y %H:%M:%S",
                       "%d-%B-%Y %H:%M:%S", "%d %B %Y %H:%M:%S",
                       "%m-%d-%Y %H:%M:%S", "%m %d %Y %H:%M:%S",
                       "%b-%d-%Y %H:%M:%S", "%b %d %Y %H:%M:%S",
                       "%B-%d-%Y %H:%M:%S", "%B %d %Y %H:%M:%S"]:

        # find date string format
        try:
            tmpdate = time.strptime(datestr, dateformat)
            break

        # datestring format  not found...
        except ValueError:
            pass

    return tmpdate

def _create_datetime(date_elements):
    """
    will create and retrun a str or datetime from (
        year, month, day, hour, minutes, and seconds) ...

    if the year is less than 1900, then it will return a string representation...
    """
    pyear, pmonth, day, hour, minutes, seconds = date_elements

    # do some error trapping...
    if pmonth > 12:
        pmonth = 12
    elif pmonth <= 0:
        pmonth = 1

    if hour >= 24:
        hour = 23
    elif hour < 0:
        hour = 0

    if minutes > 59:
        minutes = 59
    elif minutes < 0:
        minutes = 0
 
    if seconds > 59:
        seconds = 59
    elif seconds < 0:
        seconds = 0

    # get the number of days in year for all months
    numdays = [0] + [calendar.monthrange(year, month)[1] for year in [pyear] 
        for month in range(1, 13) ]

    if day > numdays[pmonth]:
        day = numdays[pmonth]
    elif day <= 0:
        day = 1

    if pyear < 1900:
        try:
            tmpdate = "%04d-%s-%02d %02d:%02d:%02d" % (
                    pyear, _dd.long_months[pmonth], day,
                    hour, minutes, seconds)

        except ValueError:
            tmpdate = False

    else:
        try:
            tmpdate = datetime(pyear, pmonth, day, hour, minutes, seconds)

        except ValueError:
            tmpdate = False

    if tmpdate is False:
        tmpdate = ""

    return tmpdate

def _process_datetime(tmpdate, exif_type =True):
    """
    will attempt to parse the date/ time Exif metadata entry into its pieces...
        (year, month, day, hour, minutes, seconds)
    """

    if not tmpdate:
        return False

    datetype = type(tmpdate)

    # if variable is already in datetime.datetime() format, return it?
    if datetype == datetime:
        pyear, pmonth, day = tmpdate.year, tmpdate.month, tmpdate.day
        hour, minutes, seconds = tmpdate.hour, tmpdate.minute, tmpdate.second        

    elif any(datetype == value for value in [date, gen.lib.date.Date, list] ):
        hour, minutes, seconds = time.localtime()[3:6]

        # datetime.date format
        if isinstance(tmpdate, date):
            pyear, pmonth, day = tmpdate.year, tmpdate.month, tmpdate.day

        # gen.lib.date.Date format
        elif isinstance(tmpdate, gen.lib.date.Date):
            pyear, pmonth, day = tmpdate.get_year(), tmpdate.get_month(), tmpdate.get_day()

        # list format
        else:
            pyear, pmonth, day = tmpdate[0].year, tmpdate[0].month, tmpdate[0].day

    #  string format...
    elif datetype == str:

        datestr = _get_date_format(tmpdate)
        if datestr is not False:
            pyear, pmonth, day, hour, minutes, seconds = datestr[0:6]

        else:
            pyear, pmonth, day, hour, minutes, seconds = [False]*6

    if (not pyear and not pmonth):
        tmpdate = False

    else:

        # create datetime...
        tmpdate = _create_datetime([pyear, pmonth, day, hour, minutes, seconds])

    if tmpdate is not False:

        if isinstance(tmpdate, datetime):

            # for display only...
            # make datetime displayed as user has set in preferences...
            if exif_type:
                tmpdate = _format_datetime(tmpdate)

    return tmpdate

def _setup_widget_tooltips(exif_widgets):
    """
    setup tooltips for each entry field and button.
    """

    # add tooltips for the data entry fields...
    for widget, tooltip in _TOOLTIPS:
        exif_widgets[widget].set_tooltip_text(tooltip)

    # add tooltips for the buttons...
    for widget, tooltip in _BUTTONTIPS.items():
        exif_widgets[widget].set_tooltip_text(tooltip)
