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 + + +