diff --git a/picard/cluster.py b/picard/cluster.py
index 5e7474e2a..e8418ff66 100644
--- a/picard/cluster.py
+++ b/picard/cluster.py
@@ -102,7 +102,7 @@ class Cluster(QtCore.QObject, Item):
def can_remove(self):
"""Return if this object can be removed."""
- return True
+ return not self.special
def can_edit_tags(self):
"""Return if this object supports tag editing."""
@@ -118,6 +118,9 @@ class Cluster(QtCore.QObject, Item):
def can_refresh(self):
return False
+ def can_browser_lookup(self):
+ return not self.special
+
def column(self, column):
if column == 'title':
return '%s (%d)' % (self.metadata['album'], self.metadata['totaltracks'])
@@ -271,6 +274,12 @@ class UnmatchedFiles(Cluster):
def can_edit_tags(self):
return False
+ def can_analyze(self):
+ return len(self.files) > 0
+
+ def can_autotag(self):
+ return len(self.files) > 0
+
class ClusterList(list, Item):
"""A list of clusters."""
@@ -295,6 +304,9 @@ class ClusterList(list, Item):
def can_autotag(self):
return len(self) > 0
+ def can_browser_lookup(self):
+ return False
+
class ClusterDict(object):
diff --git a/picard/file.py b/picard/file.py
index 5d9f6830d..454d04ae9 100644
--- a/picard/file.py
+++ b/picard/file.py
@@ -185,7 +185,6 @@ class File(LockableObject, Item):
self.orig_metadata['~length'] = format_time(length)
for k, v in temp_info.items():
self.orig_metadata[k] = v
- self.metadata.changed = False
self.error = None
self.clear_pending()
return self, old_filename, new_filename
diff --git a/picard/metadata.py b/picard/metadata.py
index 13ae609ae..951dc125b 100644
--- a/picard/metadata.py
+++ b/picard/metadata.py
@@ -38,7 +38,6 @@ class Metadata(object):
def __init__(self):
super(Metadata, self).__init__()
self._items = {}
- self.changed = False
self.images = []
self.length = 0
@@ -197,7 +196,6 @@ class Metadata(object):
def __setitem__(self, name, value):
self.__set(name, value)
- self.changed = True
def add(self, name, value):
if value or value == 0:
@@ -236,9 +234,6 @@ class Metadata(object):
def __delitem__(self, name):
del self._items[name]
- def set_changed(self, changed=True):
- self.changed = changed
-
def apply_func(self, func):
new = Metadata()
for key, values in self.rawitems():
diff --git a/picard/resources.py b/picard/resources.py
index d66155f11..cdea43c9e 100644
--- a/picard/resources.py
+++ b/picard/resources.py
@@ -2,7 +2,7 @@
# Resource object code
#
-# Created: Sat Dec 17 14:09:48 2011
+# Created: Sun Jan 29 22:16:09 2012
# by: The Resource Compiler for PyQt (Qt v4.7.4)
#
# WARNING! All changes made in this file will be lost!
@@ -2228,6 +2228,155 @@ qt_resource_data = "\
\x3e\x60\xd9\x01\x60\x1b\x6e\xde\xfc\xbf\xe9\x22\x80\x57\x06\xfb\
\x74\xf5\xdb\x85\x7f\x01\xf6\xcb\xb9\xc0\xbf\x0a\x97\x37\x00\x00\
\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x09\x26\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\
+\x00\x00\x03\x1e\x69\x43\x43\x50\x49\x43\x43\x20\x50\x72\x6f\x66\
+\x69\x6c\x65\x00\x00\x78\x01\x85\x54\xdf\x6b\xd3\x50\x14\xfe\xda\
+\x65\x9d\xb0\xe1\x8b\x3a\x67\x11\x09\x3e\x68\x91\x6e\x64\x53\x74\
+\x43\x9c\xb6\x6b\x57\xba\xcd\x5a\xea\x36\xb7\x21\x48\x9b\xa6\x6d\
+\x5c\x9a\xc6\x24\xed\x7e\xb0\x07\xd9\x8b\x6f\x3a\xc5\x77\xf1\x07\
+\x3e\xf9\x07\x0c\xd9\x83\x6f\x7b\x92\x0d\xc6\x14\x61\xf8\xac\x88\
+\x22\x4c\xf6\x22\xb3\x9e\x9b\x34\x4d\x27\x53\x03\xb9\xf7\xbb\xdf\
+\xf9\xee\x39\x27\xe7\xe4\x5e\xa0\xf9\x71\x5a\xd3\x14\x2f\x0f\x14\
+\x55\x53\x4f\xc5\xc2\xfc\xc4\xe4\x14\xdf\xf2\x01\x5e\x1c\x43\x2b\
+\xfc\x68\x4d\x8b\x86\x16\x4a\x26\x47\x40\x0f\xd3\xb2\x79\xef\xb3\
+\xf3\x0e\x1e\xc6\x6c\x74\xee\x6f\xdf\xab\xfe\x63\xd5\x9a\x95\x0c\
+\x11\xf0\x1c\x20\xbe\x94\x35\xc4\x22\xe1\x59\xa0\x69\x5c\xd4\x74\
+\x13\xe0\xd6\x89\xef\x9d\x31\x35\xc2\xcd\x4c\x73\x58\xa7\x04\x09\
+\x1f\x67\x38\x6f\x63\x81\xe1\x8c\x8d\x23\x96\x66\x34\x35\x40\x9a\
+\x09\xc2\x07\xc5\x42\x3a\x4b\xb8\x40\x38\x98\x69\xe0\xf3\x0d\xd8\
+\xce\x81\x14\xe4\x27\x26\xa9\x92\x2e\x8b\x3c\xab\x45\x52\x2f\xe5\
+\x64\x45\xb2\x0c\xf6\xf0\x1f\x73\x83\xf2\x5f\xb0\xa8\x94\xe9\x9b\
+\xad\xe7\x10\x8d\x6d\x9a\x19\x4e\xd1\x7c\x8a\xde\x1f\x39\x7d\x70\
+\x8c\xe6\x00\xd5\xc1\x3f\x5f\x18\xbd\x41\xb8\x9d\x70\x58\x36\xe3\
+\xa3\x35\x7e\x42\xcd\x24\xae\x11\x26\xbd\xe7\xee\x74\x69\x98\xed\
+\x65\x9a\x97\x59\x29\x12\x25\x1c\x24\xbc\x62\x54\xae\x33\x6c\x69\
+\xe6\x0b\x03\x89\x9a\xe6\xd3\xed\xf4\x50\x92\xb0\x9f\x34\xbf\x34\
+\x33\x59\xf3\xe3\xed\x50\x95\x04\xeb\x31\xc5\xf5\xf6\x4b\x46\xf4\
+\xba\xbd\xd7\xdb\x91\x93\x07\xe3\x35\x3e\xa7\x29\xd6\x7f\x40\xfe\
+\xbd\xf7\xf5\x72\x8a\xe5\x79\x92\xf0\xeb\xb4\x1e\x8d\xd5\xf4\x5b\
+\x92\x3a\x56\xdb\xdb\xe4\xcd\xa6\x23\xc3\xc4\x77\x51\x3f\x03\x48\
+\x42\x82\x8e\x1c\x64\x28\xe0\x91\x42\x0c\x61\x9a\x63\xc4\xaa\xf8\
+\x4c\x16\x19\x22\x4a\xa4\xd2\x69\x74\x54\x79\xb2\x38\xd6\x3b\x28\
+\x93\x96\xed\x1c\x47\x78\xc9\x5f\x0e\xb8\x5e\x16\xf5\x5b\xb2\xb8\
+\xf6\xe0\xfb\x9e\xdd\x25\xd7\x8e\xbc\x15\x85\xc5\xb7\xa3\xd8\x51\
+\xed\xb5\x81\xe9\xba\xb2\x13\x9a\x1b\x7f\x75\x61\xa5\xa3\x6e\xe1\
+\x37\xb9\xe5\x9b\x1b\x6d\xab\x0b\x08\x51\xfe\x8a\xe5\xb1\x48\x5e\
+\x65\xca\x4f\x82\x51\xd7\x75\x36\xe6\x90\x53\x97\xfc\x75\x0b\xcf\
+\x32\x94\xee\x25\x76\x12\x58\x0c\xba\xac\xf0\x5e\xf8\x2a\x6c\x0a\
+\x4f\x85\x17\xc2\x97\xbf\xd4\xc8\xce\xde\xad\x11\xcb\x80\x71\x2c\
+\x3e\xab\x9e\x53\xcd\xc6\xec\x25\xd2\x4c\xd2\xeb\x64\xb8\xbf\x8a\
+\xf5\x42\xc6\x18\xf9\x90\x31\x43\x5a\x9d\xbe\x24\x4d\x9c\x8a\x39\
+\xf2\xda\x50\x0b\x27\x06\x77\x82\xeb\xe6\xe2\x5c\x2f\xd7\x07\x9e\
+\xbb\xcc\x5d\xe1\xfa\xb9\x08\xad\x2e\x72\x23\x8e\xc2\x17\xf5\x45\
+\x7c\x21\xf0\xbe\x33\xbe\x3e\x5f\xb7\x6f\x88\x61\xa7\xdb\xbe\xd3\
+\x64\xeb\xa3\x31\x5a\xeb\xbb\xd3\x91\xba\xa2\xb1\x7a\x94\x8f\xdb\
+\x27\xf6\x3d\x8e\xaa\x13\x19\xb2\xb1\xbe\xb1\x7e\x56\x08\x2b\xb4\
+\xa2\x63\x6a\x4a\xb3\x74\x4f\x00\x03\x25\x6d\x4e\x97\xf3\x05\x93\
+\xef\x11\x84\x0b\x7c\x88\xae\x2d\x89\x8f\xab\x62\x57\x90\x4f\x2b\
+\x0a\x6f\x99\x0c\x5e\x97\x0c\x49\xaf\x48\xd9\x2e\xb0\x3b\x8f\xed\
+\x03\xb6\x53\xd6\x5d\xe6\x69\x5f\x73\x39\xf3\x2a\x70\xe9\x1b\xfd\
+\xc3\xeb\x2e\x37\x55\x06\x5e\x19\xc0\xd1\x73\x2e\x17\xa0\x33\x75\
+\xe4\x09\xb0\x7c\x5e\x2c\xeb\x15\xdb\x1f\x3c\x9e\xb7\x80\x91\x3b\
+\xdb\x63\xad\x3d\x6d\x61\xba\x8b\x3e\x56\xab\xdb\x74\x2e\x5b\x1e\
+\x01\xbb\x0f\xab\xd5\x9f\xcf\xaa\xd5\xdd\xe7\xe4\x7f\x0b\x78\xa3\
+\xfc\x06\xa9\x23\x0a\xd6\xc2\xa1\x5f\x32\x00\x00\x00\x09\x70\x48\
+\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x05\xae\x49\x44\x41\x54\x38\x11\x6d\x95\x5b\x6c\x14\x55\x18\
+\xc7\xbf\x99\xd9\xd9\x76\x7b\xef\x96\xb6\xb4\xdd\xda\x0b\xb7\x4a\
+\x29\xd1\x16\x22\x09\x18\x5a\x41\x0c\x22\x3c\x20\x4a\xa2\x81\xc8\
+\x8b\x35\xe0\x83\x4f\x26\xbe\xd8\x16\x1f\x7c\xd0\x44\x89\x4f\x40\
+\x14\x50\x8c\x5a\x49\x54\x88\x72\x69\xb5\x21\xa4\x09\x6d\x81\xde\
+\xdc\x96\xde\xb7\xed\x6e\xf7\x3a\x9d\xdd\x99\xdd\xb9\xef\x1c\xbf\
+\xd9\xd6\x4d\x51\x4e\xf2\x65\xce\x7c\xe7\xcc\xef\x7c\xdf\x77\xfe\
+\xe7\x0c\x10\x42\xe0\x69\xd6\xde\xde\x63\x7b\x9a\x7f\xad\xaf\x93\
+\x10\x06\xe0\x0d\x66\xad\xef\xdf\x3e\x65\x75\xd6\x36\x8a\xa2\x68\
+\xf4\x51\xe8\x4b\x5a\x7e\x7c\x77\x96\x54\x1d\xab\xab\xdf\x5e\xe9\
+\x2a\x2f\xcd\xb7\x53\x4a\x9c\x1f\xb8\x7a\x67\x7a\x02\x46\x26\x71\
+\x5e\xea\xe3\x8e\x8e\x0e\x5b\x5b\x5b\x9b\xf1\x04\x67\x2d\x78\x15\
+\x6a\xae\x02\xab\xbf\x38\x77\xf9\xdd\xca\xda\xea\x23\x65\x25\x85\
+\x5b\x6a\x9e\xa9\xb0\x89\x92\x02\x7d\x6e\x2f\x04\x42\xcb\x9c\x6f\
+\xc1\x7b\xff\xa7\x1b\xfd\x97\xfd\x8f\x2e\x5e\xb3\xe6\xff\x0f\x6e\
+\x81\x57\xe1\x56\xa4\xa9\xfe\xe9\xd6\x8f\x8e\x5e\xbf\x7e\x67\x7e\
+\x72\x3e\x4c\xa6\x42\x2a\x91\x0c\x1c\xc1\x36\xb3\x18\x24\xbf\xf6\
+\x2d\x90\xab\xf7\xbc\xa4\x7b\x28\x40\x7e\xff\xb3\x9f\x7c\xf0\xe1\
+\xe7\x57\x91\x5b\x84\xc3\xd0\xde\xde\x9e\x2e\x9f\x6d\x35\xba\x74\
+\x49\xce\x9c\xf9\xf8\xe4\x6b\xc7\x0e\x5f\xa9\xad\xdf\x0e\xc4\xd4\
+\x74\x55\x96\xc0\x17\x94\x99\x98\x20\x52\x51\x85\x00\x03\x26\xe4\
+\xb2\x24\xe9\xcc\x48\x9a\x05\x9b\x2b\xed\xdb\xb6\x9c\x78\xbb\xc6\
+\xe5\x74\x61\xb6\xaf\x23\x9c\x4b\x47\x6e\xad\xd4\xd3\xb3\xb2\x51\
+\x00\x7b\x5f\x7c\x30\x30\x2c\xab\xe8\x9c\x8f\x28\x32\x1f\x13\xc8\
+\xd4\x52\x8c\x4c\x86\x0d\x32\xea\x93\x49\xdf\x6c\x9c\xfc\x35\xec\
+\x23\x43\x93\x5e\x32\x8b\xd1\x73\xbc\xa0\x4b\x09\x51\x9b\xf3\x2c\
+\x90\xb3\x9f\x9c\xfb\xde\x62\x59\x66\x6d\x28\xda\xca\xae\xe2\x1b\
+\xfb\xe3\xcf\x7f\x74\xe3\x80\xd5\x14\x4d\x91\xc8\xe3\xd9\x45\x32\
+\x32\xcf\x93\x19\x9e\x90\x69\x2e\x49\xfa\x67\x45\xd2\x37\xc9\x11\
+\x7f\x88\x23\x09\x49\x4e\x4d\x8c\x45\x79\xc3\xe3\x99\x23\xb7\xef\
+\xf4\x90\x57\x0e\xbf\xf7\x26\x3a\x2d\x38\x63\x23\x9d\x9d\x96\x02\
+\xe0\xc0\xc1\xf7\xf7\xef\x6c\xac\xdf\x67\xf5\x55\x45\xb6\xcd\xf8\
+\x05\x30\x58\x27\x38\x1c\x0e\x30\x93\x26\x98\x26\x81\x24\x06\x93\
+\x9f\x41\x81\x2f\x1c\x83\xbe\xf1\x00\x64\xdb\x29\xa8\x2c\xce\x61\
+\x6c\x60\x68\x95\xae\x52\xfb\x81\xe6\xe7\x4e\x61\x49\xae\x21\x38\
+\x49\x23\x27\x25\x93\xb7\x8e\xb7\xec\x73\x55\xba\x20\x9e\x90\x8d\
+\xd1\xd9\x30\xc3\x9b\x39\x90\x95\x9d\x85\xc3\x04\x90\x09\x9a\x91\
+\x04\x1d\xcd\x66\xa3\x61\xeb\x86\x72\x68\xac\x73\x01\x63\x77\x40\
+\x80\x4b\x80\xaa\x6a\x8c\x20\x8a\x90\x9b\xe7\xd8\xe5\x74\x1e\xac\
+\xb3\x82\xb3\xc0\x96\x56\xe9\x9a\xea\xb2\xad\x3e\x2e\x0e\x57\x7a\
+\x66\xcc\xa9\x28\x03\x92\x4e\x40\x48\x68\xa0\xe9\x26\xa8\xba\x01\
+\x7c\x5c\x85\x1c\x8c\xb0\xa6\x34\x17\x1c\x19\x76\xa8\x2c\xc9\x83\
+\x96\xc6\x6a\xa8\x2e\xcb\x85\xc5\x00\x4f\x07\xc3\x3c\xf8\x97\x95\
+\x82\x9d\xbb\x6b\x37\x59\xcc\x94\x2a\xf0\x99\xb9\x18\x14\x0b\x37\
+\x36\xb0\xb0\x69\x7d\x0e\xb5\x20\x18\x90\xe4\x04\x10\xe2\x76\x28\
+\xc8\x62\x81\xa6\x08\xc4\x12\x2a\x94\x96\x67\x81\x8d\x49\xc5\x82\
+\x59\x60\x69\x0c\x1d\xcd\x00\x31\x2e\x03\xc7\xf1\xc4\xb7\xac\x51\
+\x61\x99\x71\xae\x05\x9b\x13\x5e\x4e\xcf\x18\x0b\x81\xae\x88\xb0\
+\xbd\xa2\x08\x4a\x8b\xf2\x60\xd8\xc3\x83\x7b\x3e\x06\x79\x0e\x1b\
+\x88\x92\x06\x94\xa1\x40\x45\x01\x0b\x76\x1b\x05\x84\xa2\x21\x11\
+\x4f\x20\x58\x83\xe2\x7c\x16\xfa\x06\xc3\xd4\x62\x44\x01\x57\x51\
+\xa6\x96\x06\x63\xb1\x95\xf6\xcf\xbe\x5d\x44\x05\x40\x0c\x57\x7f\
+\x09\x53\x2c\x75\xe6\x40\xbe\x83\x06\x6d\x28\x00\xee\xa5\x18\xe0\
+\xee\x81\x3f\x1c\x85\xb9\x85\x00\x48\x92\x0c\x2f\x37\x3d\x03\xe5\
+\x85\x2c\x88\x58\x5b\x21\x16\x23\xa1\xa8\x42\x59\x4a\xca\xd4\x64\
+\xaf\x05\xb6\xf2\x4a\xa9\x22\xce\x85\xfb\x89\x14\x83\x75\xd9\x36\
+\x26\x2b\x83\x4d\xdd\x01\x05\xb9\x59\x70\x74\x77\x0d\xec\xab\x2b\
+\x80\x65\x5e\x80\xa8\x28\xc1\xfd\x69\x1e\xae\x0d\x47\x60\xc0\xbd\
+\x08\xd1\x28\x0f\xa8\x63\x70\x4f\x2f\x25\xc7\xfd\x0a\x64\x33\xc9\
+\xe9\x99\xae\x87\xe3\x29\x70\x6b\xeb\x85\x54\x9d\xbb\xbf\xfc\xee\
+\xa6\xba\xec\x0f\x86\x05\x95\x1e\x1c\xf3\x18\x9a\x2a\x83\x2c\x2b\
+\x40\xd3\xb8\x86\x9e\x00\x4f\x90\x87\x38\x66\x43\x61\xea\xf9\x74\
+\x12\x4f\x23\x0f\x91\xc8\x32\xf0\xfc\x32\x08\x62\x1c\x0a\x33\xf1\
+\x4c\xaa\xf2\x8d\x61\xb1\x37\x94\x02\x5f\xb8\xd0\x9a\xba\xc5\x86\
+\x94\xa1\xc9\xa8\x77\xfa\x1b\xc6\x90\xe1\xfc\xad\x49\xa6\x7f\x70\
+\xc2\x14\x62\xcb\x30\x37\xe7\x81\xde\xa1\x39\x88\x88\x6a\x0a\x10\
+\x89\xc6\x21\x1c\x93\x52\xf5\x15\x84\x28\x78\x7d\x01\xcd\x1b\x96\
+\x59\x4a\x11\x83\xbe\xbf\xdd\x5f\x5b\xd0\x96\x96\x0e\x9b\x75\x97\
+\x12\x3c\xdf\x74\x73\x73\x33\x39\x72\x68\xef\x62\xdd\xe6\x86\x17\
+\x98\x82\x0a\x57\x1e\xab\x99\x79\xac\x4c\xee\x0e\x4c\xd3\x3f\xf4\
+\x73\xe0\x40\xfd\x26\xf0\x76\x53\x15\x0d\x6b\x9c\x00\x3d\x1e\x23\
+\x65\xd9\x8a\x11\xe2\x13\xf6\xc7\x1e\x01\x42\xf3\xb3\x9f\x0e\x0d\
+\xfe\x76\xeb\x2e\x45\x91\x9e\x4b\x6d\x46\xfa\xf2\xa9\xdb\xb3\x27\
+\x77\xa2\xb7\x37\x1f\x17\xdc\x79\xf8\x54\xc7\xd9\xe3\xaf\xee\xda\
+\x96\xcd\xaa\x70\xa9\x7b\xc1\xec\x8f\x50\xe6\x7a\xd6\xa4\x14\x5d\
+\xc7\x52\xa8\x60\xea\x32\x14\xb0\x26\xf3\x7c\x55\x16\xf0\x5c\x14\
+\x46\x47\xa6\xce\x8f\x3d\xfa\xe5\x62\x59\x4d\x4d\xc4\x3f\xc7\x04\
+\x08\x99\x52\xd3\x60\x3c\x24\xb6\xda\xfa\xa6\xf2\x59\xf7\x43\x4b\
+\x87\x75\xef\x9c\x3c\x7d\xc2\x51\x5c\xd5\xe2\xd7\xf3\x1d\x04\xf7\
+\x57\x52\x10\x88\xf5\xa5\x89\x01\x2c\xfe\x03\x68\x94\x9e\xc0\x71\
+\x9e\xb1\x91\xd1\x6b\x11\xef\xbd\xdb\xc5\xc5\xc5\x9c\x14\xa6\x96\
+\x12\x10\x0a\x59\x55\x48\x83\xad\xda\x20\xdc\x5e\x5e\x5b\xbf\xde\
+\x50\x7d\x85\x21\x5f\x74\x1d\xc0\x86\xc6\xfd\x87\xf6\xee\x60\x73\
+\xf2\x37\xd2\x0c\x5b\x44\x4c\xc2\x24\x75\x5d\x52\x25\x71\x89\x0f\
+\xfb\xdc\xc3\x0f\x6e\x0d\xe0\x67\x33\x25\x15\x15\x62\xc8\xa7\x05\
+\x01\xc2\x61\x64\xae\xfc\x79\xb0\x63\x31\xd3\x0d\xe1\x94\xd3\xb9\
+\x31\xb7\xa9\xa9\x26\xa7\xab\xab\x2b\x13\x07\x72\xd1\xac\x2c\xac\
+\xa7\xa5\x20\x05\x0d\x85\x0d\x51\x34\xb9\xea\xd9\x26\x79\x7e\xfc\
+\x61\x0c\x39\x12\xbe\xa7\xdb\x13\x11\xa7\xbd\xab\x9d\x1d\xad\xad\
+\x6c\x49\x46\x06\x7d\xf3\xc6\x57\x14\x78\x56\xf4\x6e\x0d\x59\x1b\
+\xed\x68\x68\x20\x39\x81\x80\xd1\xd9\xd9\x99\x8a\xf0\xbf\xdf\xfe\
+\x03\x41\x9e\x9c\x5e\xe1\xf9\x56\x48\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
\x00\x00\x03\x54\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
@@ -8923,6 +9072,11 @@ qt_resource_name = "\
\x00\x70\
\x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2d\x00\x73\x00\x75\x00\x62\x00\x6d\x00\x69\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\
\
+\x00\x16\
+\x08\x9b\xc7\x87\
+\x00\x6c\
+\x00\x6f\x00\x6f\x00\x6b\x00\x75\x00\x70\x00\x2d\x00\x6d\x00\x75\x00\x73\x00\x69\x00\x63\x00\x62\x00\x72\x00\x61\x00\x69\x00\x6e\
+\x00\x7a\x00\x2e\x00\x70\x00\x6e\x00\x67\
\x00\x13\
\x06\x89\x11\xa7\
\x00\x70\
@@ -8958,8 +9112,8 @@ qt_resource_struct = "\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1a\x00\x00\x00\x02\
\x00\x00\x02\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x57\x19\
-\x00\x00\x02\x8c\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x2e\
-\x00\x00\x01\xce\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x21\
+\x00\x00\x02\x8c\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x2f\
+\x00\x00\x01\xce\x00\x02\x00\x00\x00\x0e\x00\x00\x00\x21\
\x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\
\x00\x00\x00\xe0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1f\
\x00\x00\x00\xf0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1e\
@@ -8983,24 +9137,25 @@ qt_resource_struct = "\
\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x55\x9d\
\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x52\x58\
\x00\x00\x01\x20\x00\x00\x00\x00\x00\x01\x00\x00\x50\xdf\
-\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xd1\xc6\
-\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x01\xbd\x13\
-\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xb3\xe6\
-\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xc7\x79\
-\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x02\x14\x50\
-\x00\x00\x03\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x91\xa6\
-\x00\x00\x03\x60\x00\x00\x00\x00\x00\x01\x00\x00\x89\x8b\
-\x00\x00\x05\xac\x00\x00\x00\x00\x00\x01\x00\x00\x9f\x5c\
-\x00\x00\x05\xf8\x00\x00\x00\x00\x00\x01\x00\x00\xa7\x28\
+\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xda\xf0\
+\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x01\xc6\x3d\
+\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xbd\x10\
+\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\xd0\xa3\
+\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x02\x1d\x7a\
+\x00\x00\x03\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x9a\xd0\
+\x00\x00\x03\x60\x00\x00\x00\x00\x00\x01\x00\x00\x92\xb5\
+\x00\x00\x05\xde\x00\x00\x00\x00\x00\x01\x00\x00\xa8\x86\
+\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x00\xb0\x52\
\x00\x00\x05\x00\x00\x00\x00\x00\x00\x01\x00\x00\x7f\xe4\
+\x00\x00\x05\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x8f\x5d\
+\x00\x00\x04\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xa3\x6c\
\x00\x00\x05\x28\x00\x00\x00\x00\x00\x01\x00\x00\x86\x33\
-\x00\x00\x04\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x9a\x42\
-\x00\x00\x05\x82\x00\x00\x00\x00\x00\x01\x00\x00\x9c\x46\
-\x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x97\x1b\
-\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00\xa9\xff\
-\x00\x00\x05\x54\x00\x00\x00\x00\x00\x01\x00\x00\x8d\x78\
-\x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x00\xa2\x31\
-\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\xaf\x64\
+\x00\x00\x05\xb4\x00\x00\x00\x00\x00\x01\x00\x00\xa5\x70\
+\x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\xa0\x45\
+\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00\xb3\x29\
+\x00\x00\x05\x86\x00\x00\x00\x00\x00\x01\x00\x00\x96\xa2\
+\x00\x00\x06\x02\x00\x00\x00\x00\x00\x01\x00\x00\xab\x5b\
+\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\xb8\x8e\
\x00\x00\x03\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x68\x62\
\x00\x00\x03\x60\x00\x00\x00\x00\x00\x01\x00\x00\x60\x89\
\x00\x00\x04\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x6d\xff\
diff --git a/picard/tagger.py b/picard/tagger.py
index 9c41d829b..af577faab 100644
--- a/picard/tagger.py
+++ b/picard/tagger.py
@@ -88,7 +88,6 @@ from picard.webservice import XmlWebService
class Tagger(QtGui.QApplication):
file_state_changed = QtCore.pyqtSignal(int)
- selected_metadata_changed = QtCore.pyqtSignal()
cluster_added = QtCore.pyqtSignal(Cluster)
cluster_removed = QtCore.pyqtSignal(Cluster)
album_added = QtCore.pyqtSignal(Album)
diff --git a/picard/ui/edittagdialog.py b/picard/ui/edittagdialog.py
new file mode 100644
index 000000000..d526eca74
--- /dev/null
+++ b/picard/ui/edittagdialog.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+#
+# Picard, the next-generation MusicBrainz tagger
+# Copyright (C) 2011 Michael Wiencek
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from PyQt4 import QtCore, QtGui
+from picard.util.tags import TAG_NAMES
+from picard.ui.ui_edittagdialog import Ui_EditTagDialog
+
+
+class EditTagDialog(QtGui.QDialog):
+
+ def __init__(self, window, tag):
+ QtGui.QDialog.__init__(self, window)
+ self.ui = Ui_EditTagDialog()
+ self.ui.setupUi(self)
+ self.window = window
+ self.value_list = self.ui.value_list
+ self.metadata_box = window.metadata_box
+ self.tag = tag
+ self.modified_tags = {}
+ tag_names = self.ui.tag_names
+ tag_names.setCompleter(None)
+ tag_names.editTextChanged.connect(self.tag_changed)
+ self.default_tags = sorted(set(TAG_NAMES.keys() + self.metadata_box.tag_names))
+ tag_names.addItem("")
+ tag_names.addItems([tn for tn in self.default_tags if not tn.startswith("~")])
+ self.tag_changed(tag)
+ self.ui.edit_value.clicked.connect(self.edit_value)
+ self.ui.add_value.clicked.connect(self.add_value)
+ self.ui.remove_value.clicked.connect(self.remove_value)
+ self.value_list.itemChanged.connect(self.value_edited)
+
+ def edit_value(self):
+ item = self.value_list.currentItem()
+ if item:
+ self.value_list.editItem(item)
+
+ def add_value(self):
+ self._modified_tag().append("")
+ item = QtGui.QListWidgetItem()
+ item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
+ self.value_list.addItem(item)
+ self.value_list.editItem(item)
+
+ def remove_value(self):
+ value_list = self.value_list
+ row = value_list.row(value_list.currentItem())
+ value_list.takeItem(row)
+ del self._modified_tag()[row]
+
+ def disable_all(self):
+ self.value_list.clear()
+ self.value_list.setEnabled(False)
+ self.ui.edit_value.setEnabled(False)
+ self.ui.add_value.setEnabled(False)
+ self.ui.remove_value.setEnabled(False)
+
+ def enable_all(self):
+ self.value_list.setEnabled(True)
+ self.ui.edit_value.setEnabled(True)
+ self.ui.add_value.setEnabled(True)
+ self.ui.remove_value.setEnabled(True)
+
+ def tag_changed(self, text):
+ tag_names = self.ui.tag_names
+ tag_names.editTextChanged.disconnect(self.tag_changed)
+ if self.value_list.count() == 0 and self.tag and self.tag not in self.default_tags:
+ tag_names.removeItem(tag_names.currentIndex())
+ row = tag_names.findText(text, QtCore.Qt.MatchFixedString)
+ if row == -1:
+ tag_names.addItem(text)
+ tag_names.setCurrentIndex(tag_names.count() - 1)
+ tag_names.model().sort(0)
+ else:
+ tag_names.setCurrentIndex(row)
+ self.tag = unicode(text)
+ if row == 0:
+ self.disable_all()
+ tag_names.editTextChanged.connect(self.tag_changed)
+ return
+ self.enable_all()
+ value_list = self.value_list
+ value_list.clear()
+ new_tags = self.metadata_box.new_tags
+ values = self.modified_tags.get(self.tag, [])
+ if not values:
+ different = new_tags.different_placeholder(self.tag)
+ if different:
+ item = QtGui.QListWidgetItem(different)
+ item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
+ font = item.font()
+ font.setItalic(True)
+ item.setFont(font)
+ value_list.addItem(item)
+ else:
+ values = new_tags.get(self.tag, [""])[0]
+ for value in values:
+ if value:
+ item = QtGui.QListWidgetItem(value)
+ item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
+ value_list.addItem(item)
+ value_list.setCurrentItem(value_list.item(0), QtGui.QItemSelectionModel.SelectCurrent)
+ tag_names.editTextChanged.connect(self.tag_changed)
+
+ def value_edited(self, item):
+ row = self.value_list.row(item)
+ self._modified_tag()[row] = unicode(item.text())
+ font = item.font()
+ font.setItalic(False)
+ item.setFont(font)
+
+ def _modified_tag(self):
+ return self.modified_tags.setdefault(self.tag,
+ list(self.metadata_box.new_tags.get(self.tag, [("")])[0]))
+
+ def accept(self):
+ self.window.ignore_selection_changes = True
+ for tag, values in self.modified_tags.items():
+ self.modified_tags[tag] = [v for v in values if v]
+ modified_tags = self.modified_tags.items()
+ for obj in self.metadata_box.objects:
+ for tag, values in modified_tags:
+ if values:
+ obj.metadata[tag] = values
+ else:
+ obj.metadata._items.pop(tag, None)
+ obj.update()
+ self.window.ignore_selection_changes = False
+ self.window.update_selection()
+ QtGui.QDialog.accept(self)
diff --git a/picard/ui/item.py b/picard/ui/item.py
index 263c79682..fb1abfe98 100644
--- a/picard/ui/item.py
+++ b/picard/ui/item.py
@@ -45,3 +45,6 @@ class Item(object):
def can_view_info(self):
return False
+
+ def can_browser_lookup(self):
+ return True
diff --git a/picard/ui/itemviews.py b/picard/ui/itemviews.py
index 8d46ec2dd..14bb31a5f 100644
--- a/picard/ui/itemviews.py
+++ b/picard/ui/itemviews.py
@@ -96,7 +96,7 @@ class MainPanel(QtGui.QSplitter):
self._ignore_selection_changes = False
self._selected_objects = set()
- TreeItem.selected_metadata_changed = self.tagger.selected_metadata_changed
+ TreeItem.window = window
TreeItem.base_color = self.palette().base().color()
TreeItem.text_color = self.palette().text().color()
TrackItem.track_colors = {
@@ -551,7 +551,7 @@ class ClusterItem(TreeItem):
if self.obj.special and album and album.loaded:
album.item.update(update_tracks=False)
if self.isSelected():
- TreeItem.selected_metadata_changed.emit()
+ TreeItem.window.update_selection()
def add_file(self, file):
self.add_files([file])
@@ -606,7 +606,7 @@ class AlbumItem(TreeItem):
for i, column in enumerate(MainPanel.columns):
self.setText(i, album.column(column[1]))
if self.isSelected():
- TreeItem.selected_metadata_changed.emit()
+ TreeItem.window.update_selection()
class TrackItem(TreeItem):
@@ -650,7 +650,7 @@ class TrackItem(TreeItem):
self.setForeground(i, color)
self.setBackground(i, bgcolor)
if self.isSelected():
- TreeItem.selected_metadata_changed.emit()
+ TreeItem.window.update_selection()
if update_album:
self.parent().update(update_tracks=False)
@@ -667,7 +667,7 @@ class FileItem(TreeItem):
self.setForeground(i, color)
self.setBackground(i, bgcolor)
if self.isSelected():
- TreeItem.selected_metadata_changed.emit()
+ TreeItem.window.update_selection()
@staticmethod
def decide_file_icon(file):
diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py
index f61b75372..ffb3a7c80 100644
--- a/picard/ui/mainwindow.py
+++ b/picard/ui/mainwindow.py
@@ -63,7 +63,7 @@ class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.selected_objects = []
- self.tagger.selected_metadata_changed.connect(self.update_selection)
+ self.ignore_selection_changes = False
self.setupUi()
def setupUi(self):
@@ -301,6 +301,11 @@ class MainWindow(QtGui.QMainWindow):
self.remove_action.setEnabled(False)
self.connect(self.remove_action, QtCore.SIGNAL("triggered()"), self.remove)
+ self.browser_lookup_action = QtGui.QAction(icontheme.lookup('lookup-musicbrainz'), _(u"&MusicBrainz Lookup"), self)
+ self.browser_lookup_action.setStatusTip(_(u"Lookup selected item on MusicBrainz"))
+ self.browser_lookup_action.setEnabled(False)
+ self.browser_lookup_action.triggered.connect(self.browser_lookup)
+
self.show_file_browser_action = QtGui.QAction(_(u"File &Browser"), self)
self.show_file_browser_action.setCheckable(True)
if self.config.persist["view_file_browser"]:
@@ -484,6 +489,7 @@ class MainWindow(QtGui.QMainWindow):
toolbar.addAction(self.analyze_action)
toolbar.addAction(self.view_info_action)
toolbar.addAction(self.remove_action)
+ toolbar.addAction(self.browser_lookup_action)
self.search_toolbar = toolbar = self.addToolBar(_(u"&Search Bar"))
toolbar.setObjectName("search_toolbar")
search_panel = QtGui.QWidget(toolbar)
@@ -643,6 +649,9 @@ class MainWindow(QtGui.QMainWindow):
def refresh(self):
self.tagger.refresh(self.selected_objects)
+ def browser_lookup(self):
+ self.tagger.lookup(self.selected_objects[0].metadata)
+
def update_actions(self):
can_remove = False
can_save = False
@@ -650,6 +659,9 @@ class MainWindow(QtGui.QMainWindow):
can_refresh = False
can_autotag = False
can_view_info = False
+ can_browser_lookup = (
+ len(self.selected_objects) == 1 and
+ self.selected_objects[0].can_browser_lookup())
for obj in self.selected_objects:
if obj is None:
continue
@@ -674,9 +686,13 @@ class MainWindow(QtGui.QMainWindow):
self.analyze_action.setEnabled(can_analyze)
self.refresh_action.setEnabled(can_refresh)
self.autotag_action.setEnabled(can_autotag)
+ self.browser_lookup_action.setEnabled(can_browser_lookup)
self.cut_action.setEnabled(bool(self.selected_objects))
def update_selection(self, objects=None):
+ if self.ignore_selection_changes:
+ return
+
if objects is not None:
self.selected_objects = objects
else:
diff --git a/picard/ui/metadatabox.py b/picard/ui/metadatabox.py
index 411d6ec2a..3855d6397 100644
--- a/picard/ui/metadatabox.py
+++ b/picard/ui/metadatabox.py
@@ -24,44 +24,9 @@ from picard.cluster import Cluster
from picard.track import Track
from picard.file import File
from picard.config import TextOption
+from picard.util import partial
from picard.util.tags import display_tag_name
-from picard.ui.ui_edittagdialog import Ui_EditTagDialog
-
-
-class EditTagDialog(QtGui.QDialog):
-
- def __init__(self, parent, values):
- QtGui.QDialog.__init__(self, parent)
- self.ui = Ui_EditTagDialog()
- self.ui.setupUi(self)
- self.values = values
- self.list = self.ui.value_list
- for value in values[0]:
- item = QtGui.QListWidgetItem(value)
- item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
- self.list.addItem(item)
- self.list.setCurrentItem(self.list.item(0), QtGui.QItemSelectionModel.SelectCurrent)
- self.ui.add_value.clicked.connect(self.add_value)
- self.ui.remove_value.clicked.connect(self.remove_value)
-
- def add_value(self):
- item = QtGui.QListWidgetItem()
- item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
- self.list.addItem(item)
- self.list.editItem(item)
-
- def remove_value(self):
- vals = self.list
- vals.takeItem(vals.row(vals.currentItem()))
-
- def accept(self):
- values = []
- for i in xrange(self.list.count()):
- value = unicode(self.list.item(i).text())
- if value:
- values.append(value)
- self.values[0] = tuple(values)
- QtGui.QDialog.accept(self)
+from picard.ui.edittagdialog import EditTagDialog
class TagCounter(dict):
@@ -69,30 +34,36 @@ class TagCounter(dict):
empty = [("",)]
def __init__(self):
- self._counts = {}
- self._different = set()
+ self.counts = {}
+ self.different = set()
+ self.objects = 0
def add(self, tag, values):
- if tag not in self._different:
+ if tag not in self.different:
vals = self.setdefault(tag, set())
vals.add(tuple(sorted(values)))
if len(vals) > 1:
- self._different.add(tag)
+ self.different.add(tag)
self[tag] = self.empty
- self._counts[tag] = self._counts.get(tag, 0) + 1
-
- def different(self, tag):
- return tag in self._different
-
- def count(self, tag):
- return self._counts.get(tag, 0)
+ self.counts[tag] = self.counts.get(tag, 0) + 1
def clear(self):
dict.clear(self)
- self._counts.clear()
- self._different.clear()
+ self.counts.clear()
+ self.different.clear()
+ self.objects = 0
return self
+ def different_placeholder(self, tag):
+ count = self.counts.get(tag, 0)
+ missing = self.objects - count
+ if tag in self.different or (count > 0 and missing > 0):
+ if missing > 0:
+ return ungettext("(missing from %d item)", "(missing from %d items)", missing) % missing
+ else:
+ return _("(different across %d items)") % self.objects
+ return None
+
class MetadataBox(QtGui.QTableWidget):
@@ -120,7 +91,7 @@ class MetadataBox(QtGui.QTableWidget):
self.verticalHeader().setVisible(False)
self.setContentsMargins(0, 0, 0, 0)
self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
- self.setStyleSheet("border: none; font-size: 11px;")
+ self.setStyleSheet("border: none;")
self.itemChanged.connect(self.item_changed)
self._item_signals = True
self.colors = {
@@ -138,6 +109,8 @@ class MetadataBox(QtGui.QTableWidget):
self.updating = False
self.update_pending = False
self.selection_dirty = False
+ self.add_tag_action = QtGui.QAction(_(u"Add New Tag…"), parent)
+ self.add_tag_action.triggered.connect(partial(self.edit_tag, ""))
def edit(self, index, trigger, event):
if index.column() != 2:
@@ -149,17 +122,30 @@ class MetadataBox(QtGui.QTableWidget):
tag = self.tag_names[item.row()]
values = self.new_tags[tag]
if len(values) == 1 and len(values[0]) > 1:
- dialog = EditTagDialog(self.parent, values)
- if dialog.exec_():
- self.set_item_value(item, values)
- self._item_signals = False
- self.set_row_colors(item.row())
- self._item_signals = True
+ self.edit_tag(tag)
return False
else:
return QtGui.QTableWidget.edit(self, index, trigger, event)
return False
+ def contextMenuEvent(self, event):
+ item = self.itemAt(event.pos())
+ if not item:
+ return
+ menu = QtGui.QMenu(self)
+ tag = self.tag_names[item.row()]
+ if item.column() == 2 and tag != "~length":
+ edit_tag_action = QtGui.QAction(_(u"Edit “%s” Tag…") % tag, self.parent)
+ edit_tag_action.triggered.connect(partial(self.edit_tag, tag))
+ menu.addAction(edit_tag_action)
+ menu.addSeparator()
+ menu.addAction(self.add_tag_action)
+ menu.exec_(event.globalPos())
+ event.accept()
+
+ def edit_tag(self, tag):
+ EditTagDialog(self.parent, tag).exec_()
+
def update_selection(self):
self.selection_mutex.lock()
self.selection_dirty = True
@@ -210,7 +196,6 @@ class MetadataBox(QtGui.QTableWidget):
return None
orig_tags = self.orig_tags.clear()
new_tags = self.new_tags.clear()
- orig_total = 0
for file in files:
for name, values in file.orig_metadata._items.iteritems():
@@ -219,20 +204,20 @@ class MetadataBox(QtGui.QTableWidget):
for name, values in file.metadata._items.iteritems():
if not name.startswith("~") or name == "~length":
new_tags.add(name, values)
- orig_total += 1
+ orig_tags.objects += 1
- new_total = orig_total
+ new_tags.objects = orig_tags.objects
for track in tracks:
if track.num_linked_files == 0:
for name, values in track.metadata._items.iteritems():
if not name.startswith("~") or name == "~length":
new_tags.add(name, values)
- new_total += 1
+ new_tags.objects += 1
common_tags = MetadataBox.common_tags
all_tags = set(orig_tags.keys() + new_tags.keys())
self.tag_names = [t for t in common_tags if t in all_tags] + sorted(all_tags.difference(common_tags))
- return (orig_total, new_total)
+ return True
def _update_items(self, result=None, error=None):
if result is None or error is not None:
@@ -246,14 +231,9 @@ class MetadataBox(QtGui.QTableWidget):
return
self._item_signals = False
- orig_total, new_total = result
- orig_tags = self.orig_tags
- new_tags = self.new_tags
- tag_names = self.tag_names
- self.setRowCount(len(tag_names))
- set_item_value = self.set_item_value
+ self.setRowCount(len(self.tag_names))
- for i, name in enumerate(tag_names):
+ for i, name in enumerate(self.tag_names):
tag_item = self.item(i, 0)
if not tag_item:
tag_item = QtGui.QTableWidgetItem()
@@ -264,15 +244,12 @@ class MetadataBox(QtGui.QTableWidget):
self.setItem(i, 0, tag_item)
tag_item.setText(display_tag_name(name))
- orig_values = orig_tags[name] = list(orig_tags.get(name, TagCounter.empty))
- new_values = new_tags[name] = list(new_tags.get(name, TagCounter.empty))
-
orig_item = self.item(i, 1)
if not orig_item:
orig_item = QtGui.QTableWidgetItem()
orig_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.setItem(i, 1, orig_item)
- set_item_value(orig_item, orig_values, orig_tags.different(name), orig_tags.count(name), orig_total)
+ self.set_item_value(orig_item, self.orig_tags, name)
new_item = self.item(i, 2)
if not new_item:
@@ -280,7 +257,7 @@ class MetadataBox(QtGui.QTableWidget):
self.setItem(i, 2, new_item)
if name == "~length":
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
- set_item_value(new_item, new_values, new_tags.different(name), new_tags.count(name), new_total)
+ self.set_item_value(new_item, self.new_tags, name)
self.set_row_colors(i)
self._item_signals = True
@@ -288,14 +265,12 @@ class MetadataBox(QtGui.QTableWidget):
if self.update_pending:
self.update()
- def set_item_value(self, item, values, different=False, count=0, total=0):
+ def set_item_value(self, item, tags, name):
+ values = tags[name] = list(tags.get(name, TagCounter.empty))
font = item.font()
- missing = total - count
- if different or (count > 0 and missing > 0):
- if missing > 0:
- item.setText(ungettext("(missing from %d item)", "(missing from %d items)", missing) % missing)
- else:
- item.setText(_("(different across %d items)") % total)
+ different = tags.different_placeholder(name)
+ if different:
+ item.setText(different)
font.setItalic(True)
else:
value = values[0]
@@ -307,29 +282,25 @@ class MetadataBox(QtGui.QTableWidget):
item.setFont(font)
def set_row_colors(self, row):
- orig_item = self.item(row, 1)
- new_item = self.item(row, 2)
tag = self.tag_names[row]
- orig_values = self.orig_tags[tag]
- new_values = self.new_tags[tag]
- orig_blank = orig_values == TagCounter.empty and not self.orig_tags.different(tag)
- new_blank = new_values == TagCounter.empty and not self.new_tags.different(tag)
+ orig_values, new_values = self.orig_tags[tag], self.new_tags[tag]
+ orig_blank = orig_values == TagCounter.empty and not tag in self.orig_tags.different
+ new_blank = new_values == TagCounter.empty and not tag in self.new_tags.different
if new_blank and not orig_blank:
- orig_item.setForeground(self.colors["removed"])
+ self.item(row, 1).setForeground(self.colors["removed"])
elif orig_blank and not new_blank:
- new_item.setForeground(self.colors["added"])
+ self.item(row, 2).setForeground(self.colors["added"])
elif not (orig_blank or new_blank) and orig_values != new_values:
- orig_item.setForeground(self.colors["changed"])
- new_item.setForeground(self.colors["changed"])
+ self.item(row, 1).setForeground(self.colors["changed"])
+ self.item(row, 2).setForeground(self.colors["changed"])
else:
- orig_item.setForeground(self.colors["default"])
- new_item.setForeground(self.colors["default"])
+ self.item(row, 1).setForeground(self.colors["default"])
+ self.item(row, 2).setForeground(self.colors["default"])
def item_changed(self, item):
if not self._item_signals:
return
self._item_signals = False
- self.tagger.selected_metadata_changed.disconnect(self.parent.update_selection)
tag = self.tag_names[item.row()]
values = self.new_tags[tag]
if len(values) == 1 and len(values[0]) > 1:
@@ -337,15 +308,20 @@ class MetadataBox(QtGui.QTableWidget):
value = list(values[0])
else:
value = unicode(item.text())
- new_values = self.new_tags[tag] = [(value,)]
+ self.new_tags[tag] = [(value,)]
+ self.new_tags.different.discard(tag)
font = item.font()
font.setItalic(False)
item.setFont(font)
self.set_row_colors(item.row())
+ self.parent.ignore_selection_changes = True
for obj in self.objects:
- obj.metadata[tag] = value
+ if value:
+ obj.metadata._items[tag] = [value]
+ else:
+ obj.metadata._items.pop(tag, None)
obj.update()
- self.tagger.selected_metadata_changed.connect(self.parent.update_selection)
+ self.parent.ignore_selection_changes = False
self._item_signals = True
def restore_state(self):
diff --git a/picard/ui/ui_edittagdialog.py b/picard/ui/ui_edittagdialog.py
index 522048a09..2aaf88c20 100644
--- a/picard/ui/ui_edittagdialog.py
+++ b/picard/ui/ui_edittagdialog.py
@@ -2,36 +2,47 @@
# Form implementation generated from reading ui file 'ui/edittagdialog.ui'
#
-# Created: Fri Dec 16 23:07:11 2011
-# by: PyQt4 UI code generator 4.8.5
+# Created: Mon Dec 19 04:18:13 2011
+# by: PyQt4 UI code generator 4.7.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
-try:
- _fromUtf8 = QtCore.QString.fromUtf8
-except AttributeError:
- _fromUtf8 = lambda s: s
-
class Ui_EditTagDialog(object):
def setupUi(self, EditTagDialog):
- EditTagDialog.setObjectName(_fromUtf8("EditTagDialog"))
+ EditTagDialog.setObjectName("EditTagDialog")
EditTagDialog.setWindowModality(QtCore.Qt.ApplicationModal)
- EditTagDialog.resize(436, 240)
+ EditTagDialog.resize(400, 250)
EditTagDialog.setFocusPolicy(QtCore.Qt.StrongFocus)
- EditTagDialog.setWindowTitle(_("Edit tag"))
+ EditTagDialog.setWindowTitle("")
EditTagDialog.setModal(True)
- self.verticalLayout = QtGui.QVBoxLayout(EditTagDialog)
- self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
+ self.verticalLayout_2 = QtGui.QVBoxLayout(EditTagDialog)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.tag_names = QtGui.QComboBox(EditTagDialog)
+ self.tag_names.setEditable(True)
+ self.tag_names.setObjectName("tag_names")
+ self.verticalLayout_2.addWidget(self.tag_names)
+ self.horizontalLayout = QtGui.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
self.value_list = QtGui.QListWidget(EditTagDialog)
self.value_list.setFocusPolicy(QtCore.Qt.StrongFocus)
self.value_list.setTabKeyNavigation(False)
self.value_list.setProperty("showDropIndicator", False)
- self.value_list.setObjectName(_fromUtf8("value_list"))
- self.verticalLayout.addWidget(self.value_list)
- self.horizontalLayout = QtGui.QHBoxLayout()
- self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
+ self.value_list.setObjectName("value_list")
+ self.horizontalLayout.addWidget(self.value_list)
+ self.verticalLayout = QtGui.QVBoxLayout()
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.edit_value = QtGui.QPushButton(EditTagDialog)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(100)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.edit_value.sizePolicy().hasHeightForWidth())
+ self.edit_value.setSizePolicy(sizePolicy)
+ self.edit_value.setMinimumSize(QtCore.QSize(100, 0))
+ self.edit_value.setAutoDefault(False)
+ self.edit_value.setObjectName("edit_value")
+ self.verticalLayout.addWidget(self.edit_value)
self.add_value = QtGui.QPushButton(EditTagDialog)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(100)
@@ -39,10 +50,9 @@ class Ui_EditTagDialog(object):
sizePolicy.setHeightForWidth(self.add_value.sizePolicy().hasHeightForWidth())
self.add_value.setSizePolicy(sizePolicy)
self.add_value.setMinimumSize(QtCore.QSize(100, 0))
- self.add_value.setText(_("Add value"))
self.add_value.setAutoDefault(False)
- self.add_value.setObjectName(_fromUtf8("add_value"))
- self.horizontalLayout.addWidget(self.add_value)
+ self.add_value.setObjectName("add_value")
+ self.verticalLayout.addWidget(self.add_value)
self.remove_value = QtGui.QPushButton(EditTagDialog)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(120)
@@ -50,12 +60,13 @@ class Ui_EditTagDialog(object):
sizePolicy.setHeightForWidth(self.remove_value.sizePolicy().hasHeightForWidth())
self.remove_value.setSizePolicy(sizePolicy)
self.remove_value.setMinimumSize(QtCore.QSize(120, 0))
- self.remove_value.setText(_("Remove value"))
self.remove_value.setAutoDefault(False)
- self.remove_value.setObjectName(_fromUtf8("remove_value"))
- self.horizontalLayout.addWidget(self.remove_value)
- spacerItem = QtGui.QSpacerItem(33, 17, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
- self.horizontalLayout.addItem(spacerItem)
+ self.remove_value.setObjectName("remove_value")
+ self.verticalLayout.addWidget(self.remove_value)
+ spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem)
+ self.horizontalLayout.addLayout(self.verticalLayout)
+ self.verticalLayout_2.addLayout(self.horizontalLayout)
self.buttonbox = QtGui.QDialogButtonBox(EditTagDialog)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(150)
@@ -65,15 +76,16 @@ class Ui_EditTagDialog(object):
self.buttonbox.setMinimumSize(QtCore.QSize(150, 0))
self.buttonbox.setOrientation(QtCore.Qt.Horizontal)
self.buttonbox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save)
- self.buttonbox.setObjectName(_fromUtf8("buttonbox"))
- self.horizontalLayout.addWidget(self.buttonbox)
- self.verticalLayout.addLayout(self.horizontalLayout)
+ self.buttonbox.setObjectName("buttonbox")
+ self.verticalLayout_2.addWidget(self.buttonbox)
self.retranslateUi(EditTagDialog)
- QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL(_fromUtf8("accepted()")), EditTagDialog.accept)
- QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL(_fromUtf8("rejected()")), EditTagDialog.reject)
+ QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL("accepted()"), EditTagDialog.accept)
+ QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL("rejected()"), EditTagDialog.reject)
QtCore.QMetaObject.connectSlotsByName(EditTagDialog)
def retranslateUi(self, EditTagDialog):
- pass
+ self.edit_value.setText(QtGui.QApplication.translate("EditTagDialog", "Edit value", None, QtGui.QApplication.UnicodeUTF8))
+ self.add_value.setText(QtGui.QApplication.translate("EditTagDialog", "Add value", None, QtGui.QApplication.UnicodeUTF8))
+ self.remove_value.setText(QtGui.QApplication.translate("EditTagDialog", "Remove value", None, QtGui.QApplication.UnicodeUTF8))
diff --git a/picard/util/tags.py b/picard/util/tags.py
index 0fb4f374d..1ff0a2ef2 100644
--- a/picard/util/tags.py
+++ b/picard/util/tags.py
@@ -17,7 +17,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-tag_names = {
+TAG_NAMES = {
'album': N_('Album'),
'artist': N_('Artist'),
'title': N_('Title'),
@@ -85,15 +85,16 @@ tag_names = {
def display_tag_name(name):
if ':' in name:
name, desc = name.split(':', 1)
- name = _(tag_names.get(name + ':', name))
+ name = _(TAG_NAMES.get(name + ':', name))
return '%s [%s]' % (_(name), desc)
else:
- new_name = tag_names.get(name)
+ new_name = TAG_NAMES.get(name)
if new_name is None:
- new_name = tag_names.get(name + ':')
+ new_name = TAG_NAMES.get(name + ':')
if new_name is None:
return _(name)
else:
return '%s []' % (_(new_name),)
else:
return _(new_name)
+
diff --git a/resources/images/22x22/lookup-musicbrainz.png b/resources/images/22x22/lookup-musicbrainz.png
new file mode 100644
index 000000000..dec2f90a8
Binary files /dev/null and b/resources/images/22x22/lookup-musicbrainz.png differ
diff --git a/resources/picard.qrc b/resources/picard.qrc
index 4b36323c0..53aa16170 100644
--- a/resources/picard.qrc
+++ b/resources/picard.qrc
@@ -21,6 +21,7 @@
images/22x22/list-remove.png
images/22x22/system-search.png
images/22x22/picard-submit.png
+ images/22x22/lookup-musicbrainz.png
images/match-50.png
images/match-60.png
images/match-70.png
diff --git a/ui/edittagdialog.ui b/ui/edittagdialog.ui
index e6f3bbb88..d13ba3e3d 100644
--- a/ui/edittagdialog.ui
+++ b/ui/edittagdialog.ui
@@ -9,119 +9,149 @@
0
0
- 436
- 240
+ 400
+ 250
Qt::StrongFocus
- Edit tag
+
true
-
+
-
-
-
- Qt::StrongFocus
-
-
- false
-
-
- false
+
+
+ true
-
-
-
-
-
- 100
- 0
-
+
+
+ Qt::StrongFocus
-
-
- 100
- 0
-
+
+ false
-
- Add value
-
-
+
false
-
-
-
-
- 120
- 0
-
-
-
-
- 120
- 0
-
-
-
- Remove value
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Expanding
-
-
-
- 33
- 17
-
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- 150
- 0
-
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Save
-
-
+
+
-
+
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+ Edit value
+
+
+ false
+
+
+
+ -
+
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+ Add value
+
+
+ false
+
+
+
+ -
+
+
+
+ 120
+ 0
+
+
+
+
+ 120
+ 0
+
+
+
+ Remove value
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ 150
+ 0
+
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Save
+
+
+