# Copyright (C) 2008-2010 Adam Olsen
#
# 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# The developers of the Exaile media player hereby grant permission
# for non-GPL compatible GStreamer and Exaile plugins to be used and
# distributed together with GStreamer and Exaile. This permission is
# above and beyond the permissions granted by the GPL license by which
# Exaile is covered. If you modify this code, you may extend this
# exception to your version of the code, but you are not obligated to
# do so. If you do not wish to do so, delete this exception statement
# from your version.

import copy
import datetime
import gio
import glib
import gobject
import gtk
import io
import os
import pango
import string

from xl.nls import gettext as _
from xl.metadata._base import CoverImage
from xl import (
    common,
    metadata,
    trax,
    xdg
)

from xlgui.widgets import dialogs

IGNORE = (None, None)

dialog_tags = { 'originalalbum': (_('Original album'), 'text'),
                'lyricist': (_('Lyricist'), 'text'),
                'part': IGNORE, #
                'website': (_('Website'), 'text'),
                'cover': (_('Cover'), 'image'),
                'originalartist': (_('Original artist'), 'text'),
                'author': (_('Author'), 'text'),
                'originaldate': (_('Original date'), 'text'),
                'date': (_('Date'), 'text'),
                'arranger': (_('Arranger'), 'text'),
                'conductor': (_('Conductor'), 'text'),
                'performer': (_('Performer'), 'text'),
                'artist': (_('Artist'), 'text'),
                'album': (_('Album'), 'text'),
                'copyright': (_('Copyright'), 'text'),
                'lyrics': (_('Lyrics'), 'text'),
                'tracknumber': (_('Track'), 'int', 0, 500),
                'version': (_('Version'), 'text'),
                'title': (_('Title'), 'text'),
                'isrc': (_('ISRC'), 'text'),
                'genre': (_('Genre'), 'text'),
                'composer': (_('Composer'), 'text'),
                'encodedby': (_('Encoded by'), 'text'),
                'organization': (_('Organization'), 'text'),
                'discnumber': (_('Disc'), 'int', 0, 50),
                'bpm': (_('BPM'), 'int', 0, 300),
                'comment': (_('Comment'), 'text'),
                '__bitrate': (_('Bitrate'), 'prop:bitrate'),
                '__date_added': (_('Date added'), 'prop:datetime'),
                '__length': (_('Length'), 'prop:time'),
                '__loc': (_('Location'), 'prop:location'),
                '__basedir': IGNORE,
                '__modified': (_('Modified'), 'prop:datetime'),
                '__playtime': IGNORE,
                '__playcount': (_('Times played'), 'text'),
                '__last_played': (_('Last played'), 'prop:datetime'),
                }

class TrackPropertiesDialog(gobject.GObject):

    def __init__(self, parent, tracks, current_position=0, with_extras=False):
        """
            :param parent: the parent window for modal operation
            :type parent: :class:`gtk.Window`
            :param tracks: the tracks to process
            :type tracks: list of :class:`xl.trax.Track` objects
            :param current_position: the position of the currently
                selected track in the list
            :type current_position: int
            :param with_extras: whether there are extra, non-selected tracks in
                `tracks` (currently happens when only 1 track is selected)
            :type with_extras: bool
        """
        gobject.GObject.__init__(self)

        self.builder = gtk.Builder()
        self.builder.add_from_file(xdg.get_data_path('ui', 'trackproperties_dialog.ui'))
        self.builder.connect_signals(self)
        self.dialog = self.builder.get_object('TrackPropertiesDialog')
        self.dialog.set_transient_for(parent)

        self.__default_attributes = pango.AttrList()
        self.__changed_attributes = pango.AttrList()
        self.__changed_attributes.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1))

        self.message = dialogs.MessageBar(
            parent=self.builder.get_object('main_container'),
            buttons=gtk.BUTTONS_CLOSE
        )

        self.remove_tag_button = self.builder.get_object('remove_tag_button')
        self.cur_track_label = self.builder.get_object('current_track_label')
        self.apply_button = self.builder.get_object('apply_button')
        self.prev_button = self.builder.get_object('prev_track_button')
        self.next_button = self.builder.get_object('next_track_button')

        self.tags_table = self.builder.get_object('tags_table')
        self.properties_table = self.builder.get_object('properties_table')
        self.rows = []

        self.new_tag_combo = self.builder.get_object('new_tag_combo')
        self.new_tag_combo_list = gtk.ListStore(str, str)
        for tag in dialog_tags:
            if not tag.startswith('__'):
                self.new_tag_combo_list.append((tag, dialog_tags[tag][0]))
        self.new_tag_combo_list.set_sort_column_id(1, gtk.SORT_ASCENDING)
        self.new_tag_combo.set_model(self.new_tag_combo_list)
        self.new_tag_combo.set_text_column(1)
        self.add_tag_button = self.builder.get_object('add_tag_button')
        self.add_tag_button.set_sensitive(False)

        self.def_tags = [
            'tracknumber',
            'title',
            'artist',
            'album',
            'discnumber',
            'date',
            'genre',
            'cover',
            'comment',
        ]

        # Store the tracks and a working copy
        self.tracks = tracks
        self.trackdata = self._tags_copy(tracks)
        self.trackdata_original = self._tags_copy(tracks)
        self.current_position = current_position

        self._build_from_track(self.current_position)

        self.dialog.resize(600, 350)
        self.dialog.show()

        self.rows[0].field.grab_focus()

    def _tags_copy(self, tracks):
        l = []
        for track in tracks:
            t = {}
            for tag in self.def_tags:
                if tag in ('cover', 'comment'):
                    tagval = track.get_tag_disk(tag)
                else:
                    tagval = track.get_tag_raw(tag)
                if tagval:
                    if isinstance(tagval, list):
                        t[tag] = tagval[:]
                    else:
                        t[tag] = [ tagval ]
                else:
                    t[tag] = ['']

                if tag == "tracknumber" or tag == "discnumber":
                    for i, entry in enumerate(t[tag]):
                        if len(entry.split('/')) < 2:
                            t[tag][i] += '/0'

            for tag in track.list_tags():
                if tag not in self.def_tags:
                    tagval = track.get_tag_raw(tag)
                    if isinstance(tagval, list):
                        t[tag] = tagval[:]
                    else:
                        t[tag] = [ tagval ]

            l.append(t)

        return l

    def _tags_write(self, data):
        errors = []
        dialog = SavingProgressWindow(self.dialog, len(data))
        for n, trackdata in data:
            track = self.tracks[n]
            poplist = []

            for tag in trackdata:
                if not tag.startswith("__"):
                    if tag in ("tracknumber", "discnumber") \
                       and trackdata[tag] == ["0/0"]:
                        poplist.append(tag)
                        continue
                    track.set_tag_raw(tag, trackdata[tag])

            # In case a tag has been removed..
            for tag in track.list_tags():
                if tag in dialog_tags:
                    if dialog_tags[tag] is not IGNORE:
                        try:
                            trackdata[tag]
                        except KeyError:
                            poplist.append(tag)
                else:
                    try:
                        trackdata[tag]
                    except KeyError:
                        poplist.append(tag)

            for tag in poplist:
                track.set_tag_raw(tag, None)

            if not track.write_tags():
                errors.append(track.get_loc_for_io());
                
            trax.track._CACHER.remove(track)
            dialog.step()
        dialog.destroy()
        
        if len(errors) > 0:
            self.message.clear_buttons()
            self.message.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
            self.message.show_error(
                _('Writing of tags failed'),
                _('Tags could not be written to the following files:\n'
                  '{files}').format(files='\n'.join(errors))
            )

    def _build_from_track(self, position):

        for table in [self.tags_table, self.properties_table]:
            for child in table.get_children():
                table.remove(child)

            table.resize(1,4)

        self.rows = []

        # Previous, next and current track label
        self.prev_button.set_sensitive(True)
        self.next_button.set_sensitive(True)

        if position == 0:
            self.prev_button.set_sensitive(False)

        if position == (len(self.trackdata) - 1):
            self.next_button.set_sensitive(False)

        self.cur_track_label.set_text(
            _("Editing track %(current)d of %(total)d") % {
                'current': self.current_position + 1,
                'total': len(self.tracks)
            }
        )

        trackdata = self.trackdata[position]

        for tag in self.def_tags:

            for i, entry in enumerate(trackdata[tag]):
                if len(self.trackdata) == 1:
                    ab = False
                    ab_dbl = 0
                else:
                    ab = True
                    ab_dbl = 2

                field = None
                if dialog_tags[tag][1] == 'int':
                    if tag == 'tracknumber':
                        field = TagDblNumField(
                            dialog_tags[tag][2],
                            dialog_tags[tag][3],
                            all_button=ab_dbl
                        )
                    elif tag == 'discnumber':
                        field = TagDblNumField(
                            dialog_tags[tag][2],
                            dialog_tags[tag][3],
                            all_button=ab_dbl
                        )
                    else:
                        field = TagNumField(
                            dialog_tags[tag][2],
                            dialog_tags[tag][3],
                            all_button=ab
                        )
                elif dialog_tags[tag][1] == 'image':
                    field = TagImageField()
                else:
                    if tag in ('comment', 'lyrics'):
                        field = TagTextField(all_button=ab)
                    else:
                        field = TagField(all_button=ab)

                row = TagRow(self, self.tags_table, field, tag, entry, i)
                self.rows.append(row)

                try:
                    if self.trackdata[self.current_position][tag] != \
                       self.trackdata_original[self.current_position][tag]:
                        row.label.set_attributes(self.__changed_attributes)
                except KeyError:
                    row.label.set_attributes(self.__changed_attributes)

        for tag in trackdata:
            if tag not in self.def_tags:
                try:
                    fieldtype = dialog_tags[tag][1]
                except KeyError:
                    fieldtype = 'text'

                if fieldtype is not None:
                    for i, entry in enumerate(trackdata[tag]):
                        field = None
                        if not tag.startswith('__'):
                            if fieldtype == 'int':
                                field = TagNumField(
                                    dialog_tags[tag][2],
                                    dialog_tags[tag][3],
                                    all_button=ab
                                )
                            elif fieldtype == 'image':
                                field = TagImageField()
                            else:
                                if tag in ('comment', 'lyrics'):
                                    field = TagTextField(all_button=ab)
                                else:
                                    field = TagField(all_button=ab)

                            self.rows.append(
                                TagRow(self, self.tags_table, field, tag, entry, i))

                        else:
                            field = PropertyField(fieldtype)

                            self.rows.append(
                                TagRow(self, self.properties_table, field, tag, entry, i))


        self._check_for_changes()
        self._build_tables_from_rows()

    def _build_tables_from_rows(self):

        tables = [self.tags_table, self.properties_table]

        #clear the tables to start with
        for table in tables:
            for child in table.get_children():
                table.remove(child)

            table.resize(1,4)

        cur_row = {tables[0]:0, tables[1]:0}

        paddings = [0, gtk.FILL, gtk.FILL|gtk.EXPAND, 0]

        for row in self.rows:
            columns = [
                    gtk.Label(),
                    row.label,
                    row.field,
                    gtk.Label()]

            for col, content in enumerate(columns):
                row.table.attach(content, col, col + 1, cur_row[row.table],
                        cur_row[row.table] + 1,
                        xoptions=paddings[col], yoptions=0)

            cur_row[row.table] += 1
            row.table.resize(cur_row[row.table] + 1, 4)

        for table in tables:
            table.show_all()

        self.remove_tag_button.toggled()

    def on_apply_button_clicked(self, w):
        modified = []
        for n, trackdata in enumerate(self.trackdata):
            if trackdata != self.trackdata_original[n]:
                modified.append((n, trackdata))

        if modified:
            self._tags_write(modified)

            del self.trackdata
            del self.trackdata_original
            self.trackdata = self._tags_copy(self.tracks)
            self.trackdata_original = self._tags_copy(self.tracks)

            self.apply_button.set_sensitive(False)
            for row in self.rows:
                if row.multi_id == 0:
                    row.label.set_attributes(self.__default_attributes)

        # Hide close confirmation if necessary
        if self.message.get_message_type() == gtk.MESSAGE_QUESTION:
            self.message.hide()

    def on_close_button_clicked(self, w):
        if self.trackdata != self.trackdata_original:
            def on_response(message, response):
                """
                    Applies changes before closing if requested
                """
                if response == gtk.RESPONSE_APPLY:
                    self.apply_button.clicked()

                self.dialog.destroy()

            self.message.connect('response', on_response)
            self.message.clear_buttons()
            self.message.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
            self.message.add_button(gtk.STOCK_APPLY, gtk.RESPONSE_APPLY)
            self.message.show_question(
                _('Apply changes before closing?'),
                _('Your changes will be lost if you do not apply them now.')
            )
        else:
            self.dialog.destroy()

    def on_prev_track_button_clicked(self, widget):
        self.current_position -= 1
        self._build_from_track(self.current_position)

    def on_next_track_button_clicked(self, widget):
        self.current_position += 1
        self._build_from_track(self.current_position)

    def on_title_case_button_clicked(self, w):
        for row in self.rows:
            if isinstance(row.field, TagField) \
               or isinstance(row.field, TagTextField):
                val = row.field.get_value()
                val = string.capwords(val, ' ')
                row.field.set_value(val)

        self._check_for_changes()

    def on_new_tag_entry_changed(self, entry):
        """
            Enables or disables the button for adding tags,
            effectively preventing empty tag names
        """
        self.add_tag_button.set_sensitive(len(entry.get_text()) > 0)

    def on_add_tag_button_clicked(self, w):
        tag = None

        index = self.new_tag_combo.get_active()

        if index != -1:
            tag = self.new_tag_combo_list[index][0]
        else:
            tag = self.new_tag_combo.get_child().get_text()

        if not tag:
            return

        trackdata = self.trackdata[self.current_position]

        try:
            trackdata[tag].append('')
        except KeyError:
            trackdata[tag] = ['']

        self._build_from_track(self.current_position)

    def on_remove_tag_button_toggled(self, widget):
        for row in self.rows:
            row.set_remove_mode(widget.get_active())

    def _check_for_changes(self):
        apply_flag = False
        for i, trackdata in enumerate(self.trackdata):
            for tag in trackdata:
                try:
                    if trackdata[tag] != self.trackdata_original[i][tag]:
                        apply_flag = True
                except KeyError:
                    apply_flag = True

            if len(trackdata) != len(self.trackdata_original[i]):
                apply_flag = True

        if apply_flag:
            if not self.apply_button.get_property("sensitive"):
                self.apply_button.set_sensitive(True)
        else:
            self.apply_button.set_sensitive(False)

    def update_tag(self, widget, tag, multi_id, val):

        trackdata = self.trackdata[self.current_position]
        original_trackdata = self.trackdata_original[self.current_position]
        trackdata[tag][multi_id] = val()

        for row in self.rows:
            if row.tag == tag and row.multi_id == 0:
                try:
                    if trackdata[tag] != original_trackdata[tag]:
                        row.label.set_attributes(self.__changed_attributes)
                    else:
                        row.label.set_attributes(self.__default_attributes)
                except KeyError:
                    row.label.set_attributes(self.__changed_attributes)

            if row.tag == tag and row.multi_id == multi_id:
                all_vals = []
                for trackdata in self.trackdata:
                    try:
                        all_vals.append(trackdata[tag][multi_id])
                    except KeyError:
                        all_vals.append('')

                row.field.set_value(val(), all_vals, doupdate=False)

        self._check_for_changes()

    def apply_all(self, field, multi_id, val, split_num=0):
        special_cases = ["discnumber", "tracknumber"]
        apply_flag = False
        value = val()

        if field in special_cases:
            original_values = value.split("/")
            for i, trackdata in enumerate(self.trackdata):
                values = trackdata[field][multi_id].split("/")
                values[split_num] = original_values[split_num]
                try:
                    trackdata[field][multi_id] = values[0] + "/" + values[1]
                except KeyError:
                    trackdata[field] = [values[0] + "/" + values[1]]
                except IndexError:
                    trackdata[field].append(values[0] + "/" + values[1])

        else:
            for i, trackdata in enumerate(self.trackdata):
                try:
                    trackdata[field][multi_id] = value
                except KeyError:
                    trackdata[field] = [value]
                except IndexError:
                    trackdata[field].append(value)

        self._check_for_changes()

    def remove_row(self, w, tag, multi_id):
        for row in self.rows:
            if row.tag == tag and row.multi_id == multi_id:
                self.trackdata[self.current_position][tag].pop(multi_id)
                if len(self.trackdata[self.current_position][tag]) == 0:
                    self.trackdata[self.current_position].pop(tag)

        self._build_from_track(self.current_position)

    def run(self):
        return self.dialog.run()

    def hide(self):
        self.dialog.hide()

class TagRow(object):
    def __init__(self, parent, parent_table, field, tag_name, value, multi_id):
        self.parent = parent
        self.table = parent_table
        self.tag = tag_name
        self.field = field
        self.field.register_parent_row(self)
        self.multi_id = multi_id
        all_vals = []
        for track in parent.trackdata:
            try:
                all_vals.append(track[tag_name][multi_id])
            except KeyError:
                all_vals.append(None)

        self.field.set_value(value, all_vals)

        try:
            name = dialog_tags[self.tag][0]
        except KeyError:
            if self.tag.startswith('__'):
                name = self.tag[2:]
            else:
                name = self.tag

        self.name = name
        self.label = gtk.Label()

        if multi_id == 0:
            self.label.set_text(_('%s:') % name.capitalize())
            self.label.create_pango_context()
            self.label.set_alignment(0.0, .50)

        self.clear_button = gtk.Button()
        self.clear_button.set_image(gtk.image_new_from_stock(
            gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON))
        self.clear_button.set_relief(gtk.RELIEF_NONE)
        self.clear_button.connect("clicked", self.clear)

        if not isinstance(field, PropertyField):
            self.field.pack_start(self.clear_button, expand=False, fill=False)

        self.field.show_all()

        # Remove mode settings
        self.remove_mode = False
        self.remove_button = gtk.Button()
        self.remove_button.set_image(gtk.image_new_from_stock(
            gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON))
        self.remove_button.connect("clicked", parent.remove_row, self.tag, self.multi_id)

        self.field.register_update_func(parent.update_tag)
        self.field.register_all_func(parent.apply_all)

    def set_remove_mode(self, val):
        if not self.tag.startswith('__') or self.multi_id != 0:
            if val and not self.remove_mode:
                self.field.remove(self.clear_button)
                self.field.pack_start(self.remove_button, expand=False, fill=False)
                self.field.show_all()
                self.remove_mode = True

            if not val and self.remove_mode:
                self.field.remove(self.remove_button)
                self.remove_mode = False
                self.field.pack_start(self.clear_button, expand=False, fill=False)
                self.field.show_all()

    def clear(self, w):
        self.field.set_value('')

class TagField(gtk.HBox):
    def __init__(self, all_button=True):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        # Create the widgets
        self.field = gtk.Entry()
        self.all_func = None
        self.parent_row = None

        self.pack_start(self.field)

        self.all_button = None
        if all_button:
            self.all_button = AllButton(self)
            self.pack_start(self.all_button, expand=False, fill=False)

    def grab_focus(self):
        """
            Gives focus to the internal widget
        """
        self.field.grab_focus()

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def set_value(self, val, all_vals=None, doupdate=True):
        if doupdate:
            self.field.set_text(val)

        if all_vals != None and self.all_button != None:
            # Set the value of the all button
            self.all_button.set_active(all(val == v for v in all_vals))

    def get_value(self):
        return unicode(self.field.get_text(), 'utf-8')

    def register_update_func(self, f):
        tag = self.parent_row.tag
        multi_id = self.parent_row.multi_id
        self.field.connect("changed", f, tag, multi_id, self.get_value)

    def register_all_func(self, f):
        self.all_func = f

class TagTextField(gtk.HBox):
    def __init__(self, all_button=True):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        self.buffer = gtk.TextBuffer()
        self.field = gtk.TextView(self.buffer)
        self.field.set_size_request(200, 150) # XXX
        scrollwindow = gtk.ScrolledWindow()
        scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        scrollwindow.set_shadow_type(gtk.SHADOW_IN)
        scrollwindow.add(self.field)
        self.all_func = None
        self.parent_row = None

        self.pack_start(scrollwindow)

        self.all_button = None
        if all_button:
            self.all_button = AllButton(self)
            self.pack_start(self.all_button, expand=False, fill=False)

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def set_value(self, val, all_vals=None, doupdate=True):
        if doupdate:
            self.buffer.set_text(val)

        if all_vals != None and self.all_button != None:
            # Set the value of the all button
            flag = True
            for v in all_vals:
                if val != v:
                    flag = False

            if flag:
                self.all_button.set_active(True)
            else:
                self.all_button.set_active(False)

    def get_value(self):
        return unicode(self.buffer.get_text(
            self.buffer.get_start_iter(),
            self.buffer.get_end_iter(),
            True
        ), 'utf-8')

    def register_update_func(self, f):
        tag = self.parent_row.tag
        multi_id = self.parent_row.multi_id
        self.buffer.connect("changed", f, tag, multi_id, self.get_value)

    def register_all_func(self, f):
        self.all_func = f

class TagNumField(gtk.HBox):
    def __init__(self, min=0, max=10000, step=1, page=10, all_button=True):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        # Create the widgets
        self.field = gtk.SpinButton()
        self.field.set_range(min, max)
        self.field.set_increments(step, page)
        self.all_func = None
        self.parent_row = None

        self.pack_start(self.field)

        self.all_button = None
        if all_button:
            self.all_button = AllButton(self)
            self.pack_start(self.all_button, expand=False, fill=False)

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def set_value(self, val, all_vals=None, doupdate=True):
        if doupdate:
            if val != '':
                self.field.set_value(float(val))
            else:
                self.field.set_value(0)

        if all_vals != None and self.all_button != None:
            # Set the value of the all button
            flag = True
            for v in all_vals:
                if val != v:
                    flag = False

            if flag:
                self.all_button.set_active(True)
            else:
                self.all_button.set_active(False)

    def get_value(self):
        return unicode(int(self.field.get_value()))

    def register_update_func(self, f):
        tag = self.parent_row.tag
        multi_id = self.parent_row.multi_id
        self.field.connect("value-changed", f, tag, multi_id, self.get_value)

    def register_all_func(self, f):
        self.all_func = f

class TagDblNumField(gtk.HBox):

    def __init__(self, min=0, max=10000, step=1, page=10, all_button=1):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        self.field = [gtk.SpinButton(), gtk.SpinButton()]
        self.all_func = None
        self.parent_row = None
        for f in self.field:
            f.set_range(min, max)
            f.set_increments(step, page)

        # TRANSLATORS: This is the 'of' between numbers in fields like
        # tracknumber, discnumber, etc. in the tagger.
        lbl = gtk.Label(_('of:'))
        self.all_button = [None, None]
        if all_button:
            if all_button == 1:
                self.all_button = [None, AllButton(self, 1)]
            if all_button == 2:
                self.all_button = [AllButton(self), AllButton(self, 1)]

        self.pack_start(self.field[0])
        if all_button and self.all_button[0] != None:
            self.pack_start(self.all_button[0], expand=False, fill=False)
        self.pack_start(lbl)
        self.pack_start(self.field[1])
        if all_button:
            self.pack_start(self.all_button[1], expand=False, fill=False)

    def grab_focus(self):
        """
            Gives focus to the internal widget
        """
        self.field[0].grab_focus()

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def register_all_func(self, f):
        self.all_func = f

    def set_value(self, val, all_val=None, doupdate=True):
        if len(val.split('/')) < 2:
            val += '/'

        vals = val.split('/')

        if doupdate:
            for x in range(2):
                if vals[x] != '':
                    self.field[x].set_value(float(vals[x]))
                else:
                    self.field[x].set_value(0)

        if all_val != None:
            all_vals = []
            for v in all_val:
                all_vals.append(v.split('/'))

            # Set the value of the all button
            flags = [True, True]
            for i in range(2):
                for v in all_vals:
                    if vals[i] != v[i]:
                        flags[i] = False

                if self.all_button[i] != None:
                    if flags[i]:
                        self.all_button[i].set_active(True)
                    else:
                        self.all_button[i].set_active(False)

    def get_value(self):
        f0 = unicode(int(self.field[0].get_value()))
        f1 = unicode(int(self.field[1].get_value()))
        return f0 + '/' + f1

    def register_update_func(self, f):
        tag = self.parent_row.tag
        multi_id = self.parent_row.multi_id
        val = unicode(self.field[0].get_value()) + '/' \
                + unicode(self.field[1].get_value())
        self.field[0].connect("value-changed", f, tag, multi_id, self.get_value)
        self.field[1].connect("value-changed", f, tag, multi_id, self.get_value)

class TagImageField(gtk.HBox):
    def __init__(self, all_button=True):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        self.parent_row = None
        self.all_func = None
        self.update_func = None
        # Prevents the update function from being called, make 
        # sure you do that manually after the batch update
        self.batch_update = False

        self.pixbuf = None
        self.info = CoverImage(None, None, None, None)
        self.default_type = 3
        self.mime_info = {
            'image/jpeg': {
                # Title for display
                'title': _('JPEG image'),
                # Type and options for GDK Pixbuf saving
                'type': 'jpeg',
                'options': {'quality': '90'}
            },
            'image/png': {
                'title': _('PNG image'),
                'type': 'png',
            },
            'image/': {
                'title': _('Image'),
                # Store unknown images as JPEG
                'type': 'jpeg',
                'options': {'quality': '90'}
            },
            # TODO: Handle linked images
            '-->': {
                'title': _('Linked image')
            }
        }

        builder = gtk.Builder()
        builder.add_from_file(xdg.get_data_path('ui', 'trackproperties_dialog_cover_row.ui'))
        builder.connect_signals(self)
        cover_row = builder.get_object('cover_row')
        cover_row.reparent(self)

        button = builder.get_object('button')
        button.drag_dest_set(gtk.DEST_DEFAULT_ALL, [], gtk.gdk.ACTION_COPY)
        button.drag_dest_add_uri_targets()

        self.image = builder.get_object('image')
        self.info_label = builder.get_object('info_label')
        self.type_model = builder.get_object('type_model')
        self.type_selection = builder.get_object('type_selection')
        self.type_selection.set_sensitive(False)
        self.description_entry = builder.get_object('description_entry')
        self.description_entry.set_sensitive(False)

        self.all_button = None
        if all_button:
            self.all_button = AllButton(self)
            self.pack_start(self.all_button, expand=False, fill=False)

    def grab_focus(self):
        """
            Gives focus to the internal widget
        """
        self.image.grab_focus()

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def register_update_func(self, func):
        self.update_func = func

    def register_all_func(self, function):
        self.all_func = function

    def set_value(self, val, all_vals=None, doupdate=True):
        if doupdate:
            if val:
                loader = gtk.gdk.PixbufLoader()

                try:
                    loader.write(val.data)
                    loader.close()
                except glib.GError:
                    pass
                else:
                    self.batch_update = True
                    self.set_pixbuf(loader.get_pixbuf(), val.mime)
                    self.type_selection.set_active(val.type)
                    self.type_selection.set_sensitive(True)
                    self.description_entry.set_text(val.desc)
                    self.description_entry.set_sensitive(True)
                    self.batch_update = False
            else:
                self.batch_update = True
                self.set_pixbuf(None)
                self.type_selection.set_active(-1)
                self.type_selection.set_sensitive(False)
                self.description_entry.set_text('')
                self.description_entry.set_sensitive(False)
                self.batch_update = False
                self.call_update_func()

        if not None in (all_vals, self.all_button):
            self.all_button.set_active(all(val == v for v in all_vals))

    def get_value(self):
        if not self.pixbuf:
            return None

        mime = self.mime_info[self.info.mime]
        # Retrieve proper image data
        writer = io.BytesIO()
        self.pixbuf.save_to_callback(writer.write, mime['type'], mime['options'])
        # Move to the beginning of the buffer to allow read operations
        writer.seek(0)

        return self.info._replace(data=writer.read())

    def call_update_func(self):
        """
            Wrapper around the update function
        """
        if not self.update_func or self.batch_update:
            return

        self.update_func(self, self.parent_row.tag, self.parent_row.multi_id, self.get_value)

    def set_pixbuf(self, pixbuf, mime=None):
        """
            Updates the displayed cover image and info values
        """
        self.pixbuf = pixbuf

        if pixbuf is None:
            self.image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_DIALOG)
            self.info_label.set_markup('')
        else:
            self.image.set_from_pixbuf(pixbuf.scale_simple(
                100, 100, gtk.gdk.INTERP_BILINEAR))

            width, height = pixbuf.get_width(), pixbuf.get_height()
            if mime is None:
                markup = _('{width}x{height} pixels').format(width=width, height=height)
            else:
                markup = _('{format} ({width}x{height} pixels)').format(
                    format=self.mime_info.get(mime, self.mime_info['image/'])['title'],
                    width=width, height=height
                )
            self.info_label.set_markup(markup)

            self.info = self.info._replace(mime=mime)

    def on_button_clicked(self, button):
        """
            Allows setting the cover image using a file selection dialog
        """
        dialog = dialogs.FileOperationDialog(
            title=_('Select image to set as cover'),
            parent=self.get_toplevel(),
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                     gtk.STOCK_OK, gtk.RESPONSE_OK)
        )
        dialog.set_select_multiple(False)
        filefilter = gtk.FileFilter()
        # Not using gtk.FileFilter.add_pixbuf_formats since
        # not all image formats are supported in tags
        filefilter.set_name(_('Supported image formats'))
        filefilter.add_pattern('*.[jJ][pP][gG]')
        filefilter.add_pattern('*.[jJ][pP][eE][gG]')
        filefilter.add_pattern('*.[pP][nN][gG]')
        dialog.add_filter(filefilter)

        if dialog.run() == gtk.RESPONSE_OK:
            filename = dialog.get_filename()

            try:
                pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
                info = gtk.gdk.pixbuf_get_file_info(filename)[0]
            except TypeError:
                pass
            else:
                self.batch_update = True
                self.set_pixbuf(pixbuf, info['mime_types'][0])
                self.type_selection.set_active(self.default_type)
                self.type_selection.set_sensitive(True)
                self.description_entry.set_text(os.path.basename(filename).rsplit('.', 1)[0])
                self.description_entry.set_sensitive(True)
                self.batch_update = False
                self.call_update_func()

        dialog.destroy()

    def on_button_drag_data_received(self, widget, context, x, y, selection, info, time):
        """
            Allows setting the cover image via drag and drop
        """
        if selection.target == 'text/uri-list':
            filename = gio.File(selection.get_uris()[0]).get_path()

            try:
                pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
                info = gtk.gdk.pixbuf_get_file_info(filename)[0]
            except TypeError:
                pass
            else:
                self.batch_update = True
                self.set_pixbuf(pixbuf, info['mime_types'][0])
                self.type_selection.set_active(self.default_type)
                self.description_entry.set_sensitive(True)
                self.description_entry.set_text(os.path.basename(filename).rsplit('.', 1)[0])
                self.description_entry.set_sensitive(True)
                self.batch_update = False
                self.call_update_func()

    def on_type_selection_changed(self, combobox):
        """
            Notifies about changes in the cover type
        """
        self.info = self.info._replace(type=self.type_model[combobox.get_active()][0])
        self.call_update_func()

    def on_description_entry_changed(self, entry):
        """
            Notifies about changes in the cover description
        """
        self.info = self.info._replace(desc=entry.get_text())
        self.call_update_func()

class PropertyField(gtk.HBox):
    def __init__(self, property_type='text'):
        gtk.HBox.__init__(self, homogeneous=False, spacing=5)

        # Informs of special formatting required
        self.property_type = property_type
        self.field = gtk.Entry()
        self.field.set_editable(False)
        self.pack_start(self.field)
        self.parent_row = None

        if self.property_type == 'prop:location':
            self.folder_button = gtk.Button()
            self.folder_button.set_tooltip_text(_('Open Directory'))
            self.folder_button.set_image(gtk.image_new_from_stock(
                gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON))
            self.pack_start(self.folder_button, expand=False, fill=False)
            self.folder_button.connect("clicked", self.folder_button_clicked)

    def register_parent_row(self, parent_row):
        self.parent_row = parent_row

    def set_value(self, val, all_vals=None, doupdate=True):
        if self.property_type == 'prop:bitrate':
            try:
                val = str(float(val) / 1000.0) + ' Kbps'
            except (TypeError, ValueError):
                pass
        elif self.property_type == 'prop:datetime':
            d = datetime.datetime.fromtimestamp(val)
            val = d.strftime("%x %X")
        elif self.property_type == 'prop:time':
            val = "%(m)d:%(s)02d" % {'m': val // 60, 's': val % 60}
        elif self.property_type == 'prop:location':
            f = gio.File(val)
            val = f.get_parse_name()

            if not f.get_path():
                # Sanitize URLs of remote locations
                val = common.sanitize_url(val)
                # Disable folder button for non-browsable locations
                self.folder_button.set_sensitive(False)
        else:
            val = str(val)

        if doupdate:
            self.field.set_text(val)
            self.field.set_tooltip_text(val)

    def folder_button_clicked(self, w):
        common.open_file_directory(self.field.get_text())

    def register_update_func(self, f):
        pass

    def register_all_func(self, f):
        pass

class AllButton(gtk.ToggleButton):
    def __init__(self, parent_field, id_num=0):
        gtk.ToggleButton.__init__(self)
        self.set_tooltip_text(_("Apply current value to all tracks"))
        self.set_relief(gtk.RELIEF_NONE)
        self.connect("toggled", self.set_all_mode)
        self.field = parent_field
        self.id_num = id_num
        self.set_active(True)
        self.set_active(False)

    def set_all_mode(self, w=None, do_apply=True):

        if self.get_active():
            if do_apply and self.field.parent_row:
                tag = self.field.parent_row.tag
                multi_id = self.field.parent_row.multi_id
                if self.field.all_func != None:
                    self.field.all_func(tag, multi_id, self.field.get_value, self.id_num)
            im = gtk.Image()
            im.set_from_stock(gtk.STOCK_DND_MULTIPLE, gtk.ICON_SIZE_BUTTON)
            self.set_image(im)
        else:
            im = gtk.Image()
            im.set_from_stock(gtk.STOCK_DND, gtk.ICON_SIZE_BUTTON)
            self.set_image(im)


class SavingProgressWindow(gtk.Window):
    def __init__(self, parent, total, text=_("Saved %(count)s of %(total)s.")):
        gtk.Window.__init__(self)
        self.count = 0
        self.total = total
        self.text = text

        if parent:
            self.set_transient_for(parent)
        self.set_modal(True)
        self.set_decorated(False)
        self.set_resizable(False)
        self.set_focus_on_map(False)
        self.add(gtk.Frame())
        self.get_child().set_shadow_type(gtk.SHADOW_OUT)
        vbox = gtk.VBox(spacing=12)
        vbox.set_border_width(12)
        self._label = gtk.Label()
        self._label.set_use_markup(True)
        self._label.set_markup(self.text % {'count': 0, 'total': self.total})
        vbox.pack_start(self._label)
        self._progress = gtk.ProgressBar()
        self._progress.set_size_request(300, -1)
        vbox.pack_start(self._progress)

        self.get_child().add(vbox)

        self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
        self.show_all()
        while gtk.events_pending():
            gtk.main_iteration()

    def step(self):
        self.count += 1
        self._progress.set_fraction(
                common.clamp(self.count / float(self.total), 0, 1))
        self._label.set_markup(self.text % {
            'count': self.count,
            'total': self.total
        })
        while gtk.events_pending():
            gtk.main_iteration()


# vim: et sts=4 sw=4
