diff --git a/data/images/tag.png b/data/images/tag.png new file mode 100644 index 000000000..0190a2de9 Binary files /dev/null and b/data/images/tag.png differ diff --git a/data/images/tag.svg b/data/images/tag.svg new file mode 100644 index 000000000..86e5e2ca4 --- /dev/null +++ b/data/images/tag.svg @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/images/track-saved.png b/data/images/track-saved.png new file mode 100644 index 000000000..9f3e2be32 Binary files /dev/null and b/data/images/track-saved.png differ diff --git a/data/picard.qrc b/data/picard.qrc index 69fb898bf..eee8672ac 100644 --- a/data/picard.qrc +++ b/data/picard.qrc @@ -27,5 +27,7 @@ images/match-90.png images/match-100.png images/magic-wand.png + images/track-saved.png + images/tag.png \ No newline at end of file diff --git a/picard/album.py b/picard/album.py index d3c9a8a64..a29f8c178 100644 --- a/picard/album.py +++ b/picard/album.py @@ -121,10 +121,10 @@ class Album(DataObject): def addLinkedFile(self, track, file): index = self.tracks.index(track) self.files.append(file) - self.emit(QtCore.SIGNAL("trackUpdated"), track) + self.emit(QtCore.SIGNAL("track_updated"), track) def removeLinkedFile(self, track, file): - self.emit(QtCore.SIGNAL("trackUpdated"), track) + self.emit(QtCore.SIGNAL("track_updated"), track) def getNumUnmatchedFiles(self): return len(self.unmatched_files) @@ -174,3 +174,7 @@ class Album(DataObject): """Return if this object can be removed.""" return True + def can_edit_tags(self): + """Return if this object supports tag editing.""" + return False + diff --git a/picard/cluster.py b/picard/cluster.py index b014e692a..427cbecca 100644 --- a/picard/cluster.py +++ b/picard/cluster.py @@ -58,3 +58,7 @@ class Cluster(QtCore.QObject): """Return if this object can be removed.""" return True + def can_edit_tags(self): + """Return if this object supports tag editing.""" + return False + diff --git a/picard/file.py b/picard/file.py index 89417d221..5706c086e 100644 --- a/picard/file.py +++ b/picard/file.py @@ -24,93 +24,77 @@ from PyQt4 import QtCore from picard.metadata import Metadata from picard.parsefilename import parseFileName from picard.similarity import similarity +from picard.util import LockableObject -class File(QtCore.QObject): +class File(LockableObject): - _id_counter = 1 + NEW = 0 + CHANGED = 1 + TO_BE_SAVED = 2 + SAVED = 3 def __init__(self, filename): - QtCore.QObject.__init__(self) - self._id = File._id_counter - File._id_counter += 1 - self.mutex = QtCore.QMutex(QtCore.QMutex.Recursive) + LockableObject.__init__(self) + self.id = self.new_id() self.filename = filename self.base_filename = os.path.basename(filename) self.cluster = None self.track = None + self.state = File.NEW self.orig_metadata = Metadata() self.metadata = Metadata() def __str__(self): return ('' % (self.id, self.base_filename)).encode("UTF-8") - def lock(self): - self.mutex.lock() - - def unlock(self): - self.mutex.unlock() + __id_counter = 1 - def getId(self): - return self._id - - id = property(getId) + @classmethod + def new_id(cls): + cls.__id_counter += 1 + return cls.__id_counter def save(self): """Save the file.""" - locker = QtCore.QMutexLocker(self.mutex) - try: - self._save() - if self.config.setting["rename_files"]: - format = self.config.setting["file_naming_format"] - filename = self.tagger.evaluate_script(format, self.metadata) - filename = os.path.basename(filename) + os.path.splitext(self.filename)[1] - filename = os.path.join(os.path.dirname(self.filename), filename) - os.rename(self.filename, filename) - self.filename = filename - except Exception, e: - raise - else: - self.orig_metadata.copy(self.metadata) - self.metadata.changed = False - - def _save(self): - """Save metadata to the file.""" raise NotImplementedError def remove_from_cluster(self): - locker = QtCore.QMutexLocker(self.mutex) if self.cluster is not None: self.log.debug("%s being removed from %s", self, self.cluster) self.cluster.remove_file(self) self.cluster = None def remove_from_track(self): - locker = QtCore.QMutexLocker(self.mutex) if self.track is not None: self.log.debug("%s being removed from %s", self, self.track) self.track.remove_file(self) self.track = None def move_to_cluster(self, cluster): - locker = QtCore.QMutexLocker(self.mutex) if cluster != self.cluster: self.remove_from_cluster() self.remove_from_track() self.log.debug("%s being moved to %s", self, cluster) + self.state = self.CHANGED self.cluster = cluster self.cluster.add_file(self) def move_to_track(self, track): - locker = QtCore.QMutexLocker(self.mutex) if track != self.track: self.remove_from_cluster() self.remove_from_track() self.log.debug("%s being moved to %s", self, track) + self.state = self.CHANGED + if self.orig_metadata["musicbrainz_trackid"] and \ + self.orig_metadata["musicbrainz_trackid"] == track.id: + self.state = self.SAVED + print self.metadata["musicbrainz_trackid"] + print track.id + print "state", self.state self.track = track self.track.add_file(self) def get_similarity(self, metadata=None): - locker = QtCore.QMutexLocker(self.mutex) if not metadata: metadata = self.metadata return self.orig_metadata.compare(metadata) @@ -123,3 +107,7 @@ class File(QtCore.QObject): """Return if this object can be removed.""" return True + def can_edit_tags(self): + """Return if this object supports tag editing.""" + return True + diff --git a/picard/plugins/picardmutagen/mp3.py b/picard/plugins/picardmutagen/mp3.py index 7f9f9c1e7..de22f8d04 100644 --- a/picard/plugins/picardmutagen/mp3.py +++ b/picard/plugins/picardmutagen/mp3.py @@ -39,7 +39,7 @@ class MutagenMP3File(File): self.metadata.copy(self.orig_metadata) - def _save(self): + def save(self): """Save ID3 tags to the file.""" tags = CompatID3(encode_filename(self.filename), translate=False) @@ -52,9 +52,9 @@ class MutagenMP3File(File): else: v1 = 0 - if self.config.setting["id3v2_encoding"] == "utf-8": + if self.config.setting["id3v2_encoding"].lower() == "utf-8": encoding = 3 - elif self.config.setting["id3v2_encoding"] == "utf-16": + elif self.config.setting["id3v2_encoding"].lower() == "utf-16": encoding = 1 else: encoding = 0 diff --git a/picard/resources.py b/picard/resources.py index cf9706685..5ca4b18b9 100644 --- a/picard/resources.py +++ b/picard/resources.py @@ -1,6 +1,6 @@ # Resource object code # -# Created: ?t 14. IX 13:09:50 2006 +# Created: so 16. IX 15:26:50 2006 # by: The Resource Compiler for PyQt (Qt v4.1.3) # # WARNING! All changes made in this file will be lost! @@ -1900,6 +1900,18 @@ qt_resource_data = "\ \x6a\xa1\x46\x96\xc1\x7c\xee\x56\x87\xab\x23\xd3\xcb\xdd\x81\xcf\ \x99\xc8\x57\x93\xb7\xb6\xbf\x05\x18\x00\xb3\x4c\x68\x8a\xf9\xd4\ \xd4\xe6\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\x9c\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ +\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x00\x06\x50\x4c\x54\x45\xff\xff\xff\ +\x00\x00\x00\x55\xc2\xd3\x7e\x00\x00\x00\x01\x74\x52\x4e\x53\x00\ +\x40\xe6\xd8\x66\x00\x00\x00\x0f\x49\x44\x41\x54\x78\xda\x62\x60\ +\x18\x05\xc8\x00\x20\xc0\x00\x01\x10\x00\x01\x3b\x42\x42\x4b\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x08\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -2433,23 +2445,131 @@ qt_resource_data = "\ \xd1\xa4\xf0\xe7\x6f\x64\xe7\x61\x16\x79\xf3\xdf\xc4\x6f\x97\x71\ \x39\x10\x00\x75\x1a\x77\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ -\x00\x00\x00\xe6\ +\x00\x00\x04\x2a\ +\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\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\x6f\x72\ +\x67\x9b\xee\x3c\x1a\x00\x00\x03\xbc\x49\x44\x41\x54\x38\x8d\xb5\ +\xd5\x5f\x6c\x53\x55\x1c\x07\xf0\xef\xb9\xff\xda\x6e\xeb\x9f\xb9\ +\x75\x1d\x43\xb3\x08\x73\x41\xb2\xbf\xea\x36\x8d\xf2\x82\x89\xcc\ +\x08\x0f\x83\xae\x69\x70\xf2\xe0\x50\x94\x04\x12\x40\xcd\x66\x42\ +\x02\x6a\xdc\x83\x0f\x26\xc4\x07\xcd\x02\x1a\x30\xa6\xe9\x9a\x18\ +\x36\xf7\x00\x4a\x7c\x80\x28\x5d\x35\x5a\xc2\xd6\x06\xcd\x9a\xe9\ +\xd6\x6d\x4c\x60\x32\xb6\xde\xde\x9e\x73\x7f\x3e\xf4\x0e\x06\x91\ +\x39\x35\xfb\xe5\x9e\x7b\x4e\x72\xef\xfd\xfc\x6e\x7e\xe7\x9c\x7b\ +\x19\x11\x61\x35\x42\x5a\x15\x15\x80\xb2\x38\xa8\xab\xdb\x54\xcc\ +\x34\x63\x07\x40\x05\x24\x49\x73\x12\x89\xe1\x9f\x63\xb1\xa1\xff\ +\x0a\x33\x22\x42\x43\x73\x73\x23\x11\xfb\x1a\xc0\xb7\x60\x2c\x0d\ +\x90\x5b\x96\xa4\x76\x97\xb3\x68\xa1\xf2\x41\xdf\x34\x11\x18\x91\ +\x99\xca\x66\x8d\x03\xe1\x50\xe4\x97\x15\xc3\x75\x4d\x2d\xfd\x8c\ +\xe8\x72\xfc\x87\xa1\xb7\x01\x20\x10\xf4\x6f\x7f\xa0\xa4\xe4\xe4\ +\xe8\x58\xba\x70\xdb\xd6\x56\x6a\xac\xaf\x31\xc7\x27\x26\xc4\xe0\ +\xc0\x20\x17\x42\xf4\x72\xce\x0f\x87\x43\x91\xb9\x65\xe1\xba\x27\ +\x9a\xed\x00\xa6\x00\xac\x8f\xc7\xa2\xd7\x02\x41\xbf\x4d\x55\xd5\ +\xb1\xdd\xaf\x74\xfa\x86\x13\x57\x68\x64\x24\x89\xbd\xaf\xbf\xcc\ +\x55\x45\x23\x06\x86\x6f\xce\x9d\xcb\x5d\x38\x7f\xc1\xe0\x9c\xbf\ +\x01\xe0\xd3\x70\x28\xf2\xb7\xb3\x2f\x01\x78\x06\x40\x22\x1e\x8b\ +\x5e\x03\x00\x59\x96\xdf\xac\xae\xae\x76\x96\x95\x95\x61\x6c\x34\ +\xc5\x7e\x9f\x98\x40\x46\xcf\xd0\xcd\xb9\x59\x31\xb7\x70\x93\xb7\ +\xb6\x6e\x91\xbb\xbb\xbb\x0a\x1e\xdd\xb8\xe1\x43\x55\x55\x2e\x07\ +\x82\xfe\xa7\xee\x07\x4f\x03\x28\xb6\x4a\x50\xce\x18\xeb\xde\xba\ +\xed\x85\x82\x91\x44\x02\x43\x3f\xfe\x04\x15\x8c\xcd\x4c\x5f\x05\ +\x81\x60\x18\x59\x73\x72\x7a\xdc\x10\xc4\x8d\x3d\xaf\xee\xd1\xf6\ +\xed\xdb\xbf\xce\xeb\xf5\x9e\xdd\xd9\x11\xec\x0b\x04\xfd\x6b\xef\ +\x85\x13\x00\x72\xf5\x4d\x2d\xef\x00\x38\x58\x5b\x57\x2b\x7b\x3c\ +\x6e\xf8\xca\xbc\x50\x65\x42\x71\xb1\x93\x5c\x1e\x97\x49\xd6\x82\ +\x27\x02\xfd\x39\x37\xcb\xaf\x8c\x26\xe7\x5d\x9e\x22\xe3\xe8\x91\ +\xa3\xea\xae\x5d\x2f\x3d\xef\x70\x38\x92\xc1\x9d\x81\xc3\x81\xa0\ +\xdf\x76\x7b\xf2\xea\x9b\x5a\x7c\x00\x22\x9a\xaa\x6c\xac\x5a\xf7\ +\x90\xa7\xab\xeb\x2d\x49\xb3\x69\xc8\x66\x33\x42\x08\x21\x4c\x22\ +\xd3\x34\xf3\x43\x61\x0a\x61\x9a\xc2\x14\x22\xdf\x2b\xb2\x8a\x87\ +\x2b\xab\x0a\x9d\x45\x6e\xed\xf4\xe9\x2f\xf5\x33\x67\xce\xde\x32\ +\x4d\xb3\x56\x02\x80\x78\x2c\x3a\x1d\x8f\x45\x37\x19\x39\x5e\x9b\ +\xc9\xe8\x9f\xf5\xf5\x45\x16\x1c\x76\x07\x29\xb2\x4a\x60\x0c\x20\ +\x22\x22\x02\x81\xac\xe3\xce\xdb\xeb\x86\x2e\x86\x93\xf1\x1b\x99\ +\xcc\xbc\x51\x55\x55\xa5\x48\x92\x34\x1c\x0e\x45\xae\xdf\xb5\xf3\ +\xe2\xb1\x68\x5a\x53\x95\xd7\x52\xa3\xa9\xf4\x40\xff\x57\xba\x66\ +\x73\x30\x57\x91\x9b\x31\x49\xba\x83\x02\xf9\x93\x95\x0a\x44\xe4\ +\x74\xba\x54\x67\x91\xdb\x76\xfc\xf8\x09\x70\xce\xf7\x2e\xd6\xf8\ +\xae\x08\x87\x22\xb9\x5c\x2e\xf7\xf8\xc5\x8b\xdf\xf7\xbe\xff\xee\ +\x7b\x7a\x32\x99\x24\x5f\x69\xb9\xe2\x76\x16\xcb\x00\xb3\xf0\x3c\ +\x0b\x0b\xdf\xb0\xbe\xc6\x3d\x30\xd8\x9f\xcd\x66\xb3\x9f\x84\x43\ +\x91\x5f\x6f\xd7\xf8\x7e\x11\x08\xfa\x1f\xd1\x34\xed\xe3\xd2\xd2\ +\x92\xa6\x17\x3b\x3a\xec\x15\x15\x15\xe6\xd5\x3f\x26\xb3\x37\x66\ +\xaf\x1b\xa6\x55\x65\x6f\xa9\x4f\x2b\xf7\xae\x75\x1e\x3a\x74\x70\ +\x5e\x08\x51\xb9\xb8\x71\x96\x85\x97\x24\xd8\xa2\xaa\x6a\x6f\x43\ +\x63\x83\xa7\xdd\xdf\x6e\x53\x14\x59\x8c\x8d\xa7\x6e\xcd\x67\xe6\ +\xf9\x93\x8f\x3d\x5d\x72\xec\xa3\x63\xfa\xa5\xf8\xa5\x03\xe1\x50\ +\xe4\xc4\xe2\x33\x2b\x82\x2d\x5c\x91\x24\x69\xbf\x2c\xcb\x47\xda\ +\xda\xda\xa4\xcd\x9b\x9f\x55\x85\xe0\xe6\xd4\xd4\x14\x7a\x7a\x7a\ +\x46\x39\xe7\x35\x4b\x77\xe1\x8a\xe1\x25\x09\xbc\x8a\xa2\x7c\x50\ +\x58\x58\xd8\xde\xb9\xbb\x53\x3a\x75\xf2\x94\x98\x99\x99\x79\x2e\ +\x1c\x8a\x7c\xb7\xf4\xbe\x7f\x0d\x2f\x49\xd0\xa0\xa8\x4a\x2f\x08\ +\xbf\x7d\xf1\x79\x68\xc7\xbd\xd7\x97\x85\x19\x63\x0c\x80\xdd\x6a\ +\x36\x00\x1a\xf2\xdf\x70\x06\x80\x00\xf0\x35\x15\x6b\xe4\xc9\xf4\ +\xe4\x2c\x00\x1d\x80\xbe\xb8\xc6\xff\x09\x96\x2d\xd0\x0e\xc0\x01\ +\xa0\xc0\xea\x25\x00\x06\x80\x8c\xd5\x74\xab\x37\x56\x04\xff\x9f\ +\x58\xb5\x7f\xde\x5f\xf5\x72\xf5\x72\x11\x6f\xdf\x2b\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xf4\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x0d\x00\x00\x00\x0d\x08\x03\x00\x00\x00\x45\x35\x14\x4e\ \x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ \x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ \x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\x0c\x50\x4c\x54\x45\x19\x7b\x30\ -\x00\x5e\x20\x39\xb5\x4a\x74\x74\x74\xde\x81\xcf\x1f\x00\x00\x00\ -\x04\x74\x52\x4e\x53\xff\xff\xff\x00\x40\x2a\xa9\xf4\x00\x00\x00\ -\x50\x49\x44\x41\x54\x78\xda\x62\x60\x46\x06\x00\x01\xc4\x00\xc4\ -\x8c\x10\x00\x64\x01\x04\x10\x98\xc7\x00\x02\x20\x1e\x40\x00\x81\ -\x79\x4c\x20\x00\xe2\x01\x04\x10\x2a\x0f\x20\x80\x50\x79\x00\x01\ -\x84\xca\x03\x08\x20\x54\x1e\x40\x00\xa1\xf2\x00\x02\x08\x95\x07\ -\x10\x40\xa8\x3c\x80\x00\x42\xe5\x01\x04\x10\xaa\xcb\x00\x02\x0c\ -\x00\x88\x79\x01\x7c\x1c\x2f\xc7\x96\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ +\x79\x71\xc9\x65\x3c\x00\x00\x00\x09\x50\x4c\x54\x45\x00\x58\x26\ +\x00\xa6\x51\xff\xff\xff\x25\xa5\x29\xf6\x00\x00\x00\x03\x74\x52\ +\x4e\x53\xff\xff\x00\xd7\xca\x0d\x41\x00\x00\x00\x62\x49\x44\x41\ +\x54\x78\xda\x62\x60\x42\x06\x00\x01\xc4\x80\x60\x31\x30\x30\x01\ +\x04\x10\x9c\xc7\xc0\xc8\xc8\xc8\x00\x10\x40\x0c\xc8\x1c\x26\x80\ +\x00\x62\x40\xe6\x30\x01\x04\x10\x03\x58\x2f\x94\xc3\x04\x10\x40\ +\x20\x01\x46\x18\x87\x09\x20\x80\x20\xe2\x50\x0e\x13\x40\x00\x31\ +\xc0\xf5\x80\x00\x40\x00\x81\x55\xc3\x38\x4c\x00\x01\xc4\x80\xe2\ +\x04\x80\x00\x62\x40\x71\x19\x40\x00\xa1\xf2\x00\x02\x0c\x00\x5d\ +\x64\x01\x0e\xd9\x8f\x83\x25\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x02\x32\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ +\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x00\xcc\x50\x4c\x54\x45\xc6\xcc\x9b\ +\xea\xed\xcd\x9c\xde\x5c\x7b\xd3\x27\x75\xd1\x1c\xc3\xc8\x9a\xe1\ +\xe6\xb8\x78\xd2\x21\xd9\xe0\xa4\xda\xe0\xa5\xbd\xc0\xa4\xc9\xcb\ +\xbb\x80\xd4\x2f\xc7\xcb\xac\x85\xd6\x38\x76\xd2\x1d\x86\xd6\x38\ +\x8b\xd8\x42\xdc\xe2\xab\x93\xdb\x4e\xac\xe4\x75\x7a\xd3\x25\xe8\ +\xe8\xe5\xd9\xdb\xc8\xd0\xd7\x9e\xa2\xe0\x66\xf8\xf8\xf6\xbd\xc2\ +\x9a\x8a\xd8\x3f\xde\xe0\xd1\x9e\xdf\x60\xd4\xdb\xa0\x75\xd1\x1b\ +\x82\xd5\x32\x87\xd7\x3b\xa4\xe1\x6a\xe1\xe6\xb6\x74\xd1\x18\x8e\ +\xd9\x46\xdc\xe2\xa9\xa5\xe1\x6b\x7d\xd3\x2a\xc9\xcb\xb4\x94\xdb\ +\x4f\x91\xda\x4a\xc2\xc7\x9a\xd0\xd2\xc1\x7e\xd4\x2c\x84\xd5\x35\ +\xe5\xe9\xc0\xe0\xe5\xb4\x96\xdc\x52\x77\xd2\x1f\xdc\xde\xcd\x74\ +\xd1\x1a\xe3\xe8\xbc\xa8\xe2\x70\xb0\xe5\x7b\xce\xd2\xb5\x8c\xd8\ +\x43\x99\xdd\x57\x79\xd2\x23\xf8\xf9\xf6\xbe\xc2\x9b\xe4\xe8\xbe\ +\x47\x8a\x05\xbb\xc0\x97\xff\xff\xff\x49\x96\x4a\x76\x00\x00\x00\ +\x44\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\x00\x71\xd2\x8f\x12\x00\x00\x00\ +\x9c\x49\x44\x41\x54\x78\xda\x5c\xcf\xd7\x0e\x83\x30\x0c\x05\x50\ +\xb3\xa1\x85\xee\xbd\xe8\xde\x7b\x6f\x62\xf3\xff\xff\xd4\x40\x8a\ +\x0a\xbd\x0f\xb1\x74\x24\x5f\x39\xe0\xff\x05\xc2\xb7\x4a\x51\xee\ +\x02\x88\x61\x18\xc6\x48\x80\xc7\xf0\x56\xba\x0e\x12\x30\xad\x77\ +\x64\xa4\x18\xc8\xcf\x43\x11\x9b\x31\x58\x2c\xc7\x79\xf4\x22\xe0\ +\xa5\x8f\x56\x3f\xf3\x5b\xe1\x90\xdb\xf6\xec\x44\x87\xbd\x99\x29\ +\x62\xa5\xd1\x9e\x13\xe9\xa8\x94\x5f\x06\x92\xce\x61\xbd\x9a\x5c\ +\x24\x0b\x8d\x63\x56\x45\xc9\x02\xf0\x69\xb8\xdf\x99\x1a\xaa\xdd\ +\xf3\x08\x4d\xed\xc4\xa1\xe6\x16\xc0\x11\xa7\x83\x53\xe1\xa5\x29\ +\xa2\x74\xd0\x1c\xcc\xf7\xf7\xb7\xb1\x7c\x04\x18\x00\x81\x3b\x35\ +\xe0\x3f\xf2\x0f\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ \x00\x00\x02\xd1\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -2553,6 +2673,10 @@ qt_resource_name = "\ \x0d\xba\x66\x47\ \x00\x50\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x33\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x09\ +\x07\xbc\x8f\xc7\ +\x00\x65\ +\x00\x6d\x00\x70\x00\x74\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x0e\xe2\x04\x27\ \x00\x6d\ @@ -2615,6 +2739,14 @@ qt_resource_name = "\ \x00\x54\ \x00\x6f\x00\x6f\x00\x6c\x00\x62\x00\x61\x00\x72\x00\x41\x00\x64\x00\x64\x00\x44\x00\x69\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ +\x00\x07\ +\x0a\x7a\x57\xa7\ +\x00\x74\ +\x00\x61\x00\x67\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x03\x8a\x2a\x47\ +\x00\x74\ +\x00\x72\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x73\x00\x61\x00\x76\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ \x03\x26\x60\x87\ \x00\x6d\ @@ -2627,33 +2759,36 @@ qt_resource_name = "\ 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\x1b\x00\x00\x00\x02\ -\x00\x00\x02\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x7e\xb2\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x02\ +\x00\x00\x02\x34\x00\x00\x00\x00\x00\x01\x00\x00\x7f\x52\ \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x04\x82\ \x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x64\x1a\ -\x00\x00\x03\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x93\xf5\ +\x00\x00\x03\x9e\x00\x00\x00\x00\x00\x01\x00\x00\x99\xbb\ +\x00\x00\x03\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x98\xc3\ \x00\x00\x01\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x69\x8f\ -\x00\x00\x01\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x7a\x2f\ -\x00\x00\x02\x32\x00\x00\x00\x00\x00\x01\x00\x00\x81\x17\ +\x00\x00\x02\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x7a\xcf\ +\x00\x00\x02\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x81\xb7\ \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\xc6\x00\x00\x00\x00\x00\x01\x00\x00\x5b\x7c\ -\x00\x00\x02\x78\x00\x00\x00\x00\x00\x01\x00\x00\x88\x3a\ -\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x75\xad\ -\x00\x00\x03\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x94\xdf\ -\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x89\x62\ +\x00\x00\x02\x90\x00\x00\x00\x00\x00\x01\x00\x00\x88\xda\ +\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x76\x4d\ +\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x9b\xf1\ +\x00\x00\x02\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x8a\x02\ +\x00\x00\x01\x82\x00\x00\x00\x00\x00\x01\x00\x00\x74\xa1\ \x00\x00\x00\xd8\x00\x00\x00\x00\x00\x01\x00\x00\x5f\x23\ \x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x0e\x87\ +\x00\x00\x03\x66\x00\x00\x00\x00\x00\x01\x00\x00\x94\x95\ \x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xb1\ -\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x00\x8a\xc1\ -\x00\x00\x03\x26\x00\x00\x00\x00\x00\x01\x00\x00\x90\xce\ +\x00\x00\x02\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x8b\x61\ +\x00\x00\x03\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x91\x6e\ \x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x07\x59\ -\x00\x00\x02\xde\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xb7\ -\x00\x00\x01\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x78\x46\ +\x00\x00\x02\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x8d\x57\ +\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00\x78\xe6\ \x00\x00\x01\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6e\x6e\ -\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\x8f\xcd\ -\x00\x00\x02\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x87\x36\ -\x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x77\x54\ -\x00\x00\x01\x82\x00\x00\x00\x00\x00\x01\x00\x00\x74\xa1\ +\x00\x00\x03\x20\x00\x00\x00\x00\x00\x01\x00\x00\x90\x6d\ +\x00\x00\x02\x72\x00\x00\x00\x00\x00\x01\x00\x00\x87\xd6\ +\x00\x00\x01\xce\x00\x00\x00\x00\x00\x01\x00\x00\x77\xf4\ +\x00\x00\x01\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x75\x41\ \x00\x00\x01\x46\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x7c\ " diff --git a/picard/tagger.py b/picard/tagger.py index 2cf3c50cb..877ed22e6 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -74,7 +74,10 @@ class Tagger(QtGui.QApplication, ComponentManager, Component): self.worker = WorkerThread() self.connect(self.worker, QtCore.SIGNAL("add_files(const QStringList &)"), self.onAddFiles) self.connect(self.worker, QtCore.SIGNAL("file_updated(int)"), QtCore.SIGNAL("file_updated(int)")) - + self.connect(self.worker, + QtCore.SIGNAL("save_file_finished(PyObject*, bool)"), + self.save_file_finished) + self.browserIntegration = BrowserIntegration() self.files = {} @@ -208,8 +211,32 @@ class Tagger(QtGui.QApplication, ComponentManager, Component): def save(self, objects): """Save the specified objects.""" for file in self.get_files_from_objects(objects): - self.worker.save_file(file) + self.save_file(file) + def save_file(self, file): + """Save the file.""" + file.lock_for_write() + try: + file.state = File.TO_BE_SAVED + finally: + file.unlock() + self.worker.save_file(file) + + def save_file_finished(self, file, saved): + """Finalize file saving and notify views.""" + if saved: + file.lock_for_write() + try: + file.orig_metadata.copy(file.metadata) + file.metadata.changed = False + file.state = File.SAVED + finally: + file.unlock() + if file.track: + self.emit(QtCore.SIGNAL("track_updated"), file.track) + else: + self.emit(QtCore.SIGNAL("file_updated"), file) + def remove(self, objects): """Remove the specified objects.""" files = [] @@ -232,7 +259,7 @@ class Tagger(QtGui.QApplication, ComponentManager, Component): def load_album(self, album_id): album = Album(unicode(album_id), "[loading album information]", None) self.albums.append(album) - self.connect(album, QtCore.SIGNAL("trackUpdated"), self, QtCore.SIGNAL("trackUpdated")) + self.connect(album, QtCore.SIGNAL("track_updated"), self, QtCore.SIGNAL("track_updated")) self.emit(QtCore.SIGNAL("albumAdded"), album) self.worker.load_album(album) diff --git a/picard/track.py b/picard/track.py index c328a89e4..d355c9930 100644 --- a/picard/track.py +++ b/picard/track.py @@ -73,3 +73,7 @@ class Track(DataObject): return self.linked_file.can_remove() return False + def can_edit_tags(self): + """Return if this object supports tag editing.""" + return False + diff --git a/picard/ui/itemviews.py b/picard/ui/itemviews.py index 786206df0..f0068710c 100644 --- a/picard/ui/itemviews.py +++ b/picard/ui/itemviews.py @@ -24,7 +24,6 @@ from picard.cluster import Cluster from picard.file import File from picard.track import Track from picard.util import format_time, encode_filename, decode_filename -from picard.ui.tageditor import TagEditor from picard.config import TextOption __all__ = ["FileTreeView", "AlbumTreeView"] @@ -58,6 +57,18 @@ class BaseTreeView(QtGui.QTreeWidget): self.setDropIndicatorShown(True) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.lookupAct = QtGui.QAction(QtGui.QIcon(":/images/search.png"), _("&Lookup"), self) + + #self.analyze_action = QtGui.QAction(QtGui.QIcon(":/images/analyze.png"), _("&Analyze"), self) + + self.contextMenu = QtGui.QMenu(self) + self.contextMenu.addAction(self.main_window.edit_tags_action) + self.contextMenu.addSeparator() + self.contextMenu.addAction(self.lookupAct) + self.contextMenu.addAction(self.main_window.analyze_action) + self.contextMenu.addAction(self.main_window.save_action) + self.contextMenu.addAction(self.main_window.remove_action) + self.objectToItem = {} self.itemToObject = {} @@ -124,13 +135,13 @@ class BaseTreeView(QtGui.QTreeWidget): for item in items: obj = self.getObjectFromItem(item) if isinstance(obj, Album): - album_ids.append(str(obj.getId())) + album_ids.append(str(obj.id)) elif isinstance(obj, Track): print "track:", obj if obj.is_linked(): file_ids.append(str(obj.linked_file.id)) elif isinstance(obj, File): - file_ids.append(str(obj.getId())) + file_ids.append(str(obj.id)) mimeData = QtCore.QMimeData() mimeData.setData("application/picard.album-list", "\n".join(album_ids)) mimeData.setData("application/picard.file-list", "\n".join(file_ids)) @@ -218,25 +229,7 @@ class FileTreeView(BaseTreeView): def __init__(self, main_window, parent): BaseTreeView.__init__(self, main_window, parent) - # Create the context menu - - self.editTagsAct = QtGui.QAction(_("Edit &Tags..."), self) - self.connect(self.editTagsAct, QtCore.SIGNAL("triggered()"), self.editTags) - - self.lookupAct = QtGui.QAction(QtGui.QIcon(":/images/search.png"), _("&Lookup"), self) - - self.analyzeAct = QtGui.QAction(QtGui.QIcon(":/images/analyze.png"), _("&Analyze"), self) - - self.contextMenu = QtGui.QMenu(self) - self.contextMenu.addAction(self.editTagsAct) - self.contextMenu.addSeparator() - self.contextMenu.addAction(self.lookupAct) - self.contextMenu.addAction(self.analyzeAct) - self.contextMenu.addAction(self.main_window.save_action) - self.contextMenu.addAction(self.main_window.remove_action) - # Prepare some common icons - self.dirIcon = QtGui.QIcon(":/images/dir.png") self.fileIcon = QtGui.QIcon(":/images/file.png") @@ -280,9 +273,9 @@ class FileTreeView(BaseTreeView): #canAnalyze = True canRemove = True - self.editTagsAct.setEnabled(canEditTags) - self.lookupAct.setEnabled(canLookup) - self.analyzeAct.setEnabled(canAnalyze) + #self.editTagsAct.setEnabled(canEditTags) + #self.lookupAct.setEnabled(canLookup) + #self.analyze_action.setEnabled(canAnalyze) #self.remove_action.setEnabled(canRemove) self.contextMenu.popup(event.globalPos()) @@ -327,19 +320,10 @@ class FileTreeView(BaseTreeView): self.unregisterObject(file) self.updateCluster(cluster) - def openTagEditor(self, obj): - tagEditor = TagEditor(obj.metadata, self) - tagEditor.exec_() - self.emit(QtCore.SIGNAL("selectionChanged"), [obj]) - - def editTags(self): - objects = self.selected_objects() - self.openTagEditor(objects[0]) - def handleDoubleClick(self, index): obj = self.itemToObject[self.itemFromIndex(index)] - if isinstance(obj, File): - self.openTagEditor(obj) + if obj.can_edit_tags(): + self.main_window.edit_tags(obj) class AlbumTreeView(BaseTreeView): @@ -356,26 +340,35 @@ class AlbumTreeView(BaseTreeView): QtGui.QIcon(":/images/match-90.png"), QtGui.QIcon(":/images/match-100.png"), ] + self.icon_saved = QtGui.QIcon(":/images/track-saved.png") self.connect(self.tagger, QtCore.SIGNAL("albumAdded"), self.addAlbum) self.connect(self.tagger, QtCore.SIGNAL("albumRemoved"), self.remove_album) - self.connect(self.tagger, QtCore.SIGNAL("trackUpdated"), self.updateTrack) self.connect(self.tagger.worker, QtCore.SIGNAL("albumLoaded(QString)"), self.updateAlbum) + self.connect(self.tagger, QtCore.SIGNAL("track_updated"), + self.update_track) - def updateTrack(self, track): + def update_track(self, track): # Update track background item = self.getItemFromObject(track) if track.is_linked(): - similarity = track.linked_file.get_similarity() - item.setIcon(0, self.matchIcons[int(similarity * 5 + 0.5)]) + file = track.linked_file + if file.state == File.SAVED: + similarity = 1.0 + icon = self.icon_saved + else: + similarity = track.linked_file.get_similarity() + icon = self.matchIcons[int(similarity * 5 + 0.5)] else: similarity = 1 - item.setIcon(0, self.noteIcon) + icon = self.noteIcon + color = matchColor(similarity) for i in range(3): item.setBackgroundColor(i, color) - + item.setIcon(0, icon) + # Update track name albumItem = self.getItemFromObject(track.album) albumItem.setText(0, track.album.getName()) diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 272f3f369..23d7ef574 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -31,6 +31,7 @@ from picard.ui.coverartbox import CoverArtBox from picard.ui.metadatabox import MetadataBox from picard.ui.itemviews import FileTreeView, AlbumTreeView from picard.ui.options import OptionsDialog, OptionsDialogProvider +from picard.ui.tageditor import TagEditor class MainWindow(QtGui.QMainWindow): @@ -92,7 +93,7 @@ class MainWindow(QtGui.QMainWindow): self.connect(self.serverMetadataBox, QtCore.SIGNAL("file_updated(int)"), self, QtCore.SIGNAL("file_updated(int)")) self.coverArtBox = CoverArtBox(self) - if not self.showCoverArtAct.isChecked(): + if not self.show_cover_art_action.isChecked(): self.coverArtBox.hide() bottomLayout = QtGui.QHBoxLayout() @@ -124,7 +125,7 @@ class MainWindow(QtGui.QMainWindow): self.config.persist["window_position"] = self.pos() self.config.persist["window_size"] = self.size() self.config.persist["window_maximized"] = isMaximized - self.config.persist["view_cover_art"] = self.showCoverArtAct.isChecked() + self.config.persist["view_cover_art"] = self.show_cover_art_action.isChecked() self.fileTreeView.saveState() self.albumTreeView.saveState() @@ -139,40 +140,40 @@ class MainWindow(QtGui.QMainWindow): self.statusBar().showMessage(_("Ready")) def createActions(self): - self.optionsAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarOptions.png"), "&Options...", self) + self.options_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarOptions.png"), "&Options...", self) #self.openSettingsAct.setShortcut("Ctrl+O") - self.connect(self.optionsAct, QtCore.SIGNAL("triggered()"), self.showOptions) + self.connect(self.options_action, QtCore.SIGNAL("triggered()"), self.showOptions) - self.helpAct = QtGui.QAction(_("&Help..."), self) + self.help_action = QtGui.QAction(_("&Help..."), self) # TR: Keyboard shortcut for "Help" - self.helpAct.setShortcut(QtGui.QKeySequence(_("Ctrl+H"))) - #self.connect(self.helpAct, QtCore.SIGNAL("triggered()"), self.showHelp) + self.help_action.setShortcut(QtGui.QKeySequence(_("Ctrl+H"))) + #self.connect(self.help_action, QtCore.SIGNAL("triggered()"), self.showHelp) - self.aboutAct = QtGui.QAction(_("&About..."), self) - #self.connect(self.aboutAct, QtCore.SIGNAL("triggered()"), self.showAbout) + self.about_action = QtGui.QAction(_("&About..."), self) + #self.connect(self.about_action, QtCore.SIGNAL("triggered()"), self.showAbout) - self.add_filesAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarAddFiles.png"), _("&Add Files..."), self) - self.add_filesAct.setStatusTip(_("Add files to the tagger")) + self.add_files_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarAddFiles.png"), _("&Add Files..."), self) + self.add_files_action.setStatusTip(_("Add files to the tagger")) # TR: Keyboard shortcut for "Add Files..." - self.add_filesAct.setShortcut(QtGui.QKeySequence(_("Ctrl+O"))) - self.connect(self.add_filesAct, QtCore.SIGNAL("triggered()"), self.add_files) + self.add_files_action.setShortcut(QtGui.QKeySequence(_("Ctrl+O"))) + self.connect(self.add_files_action, QtCore.SIGNAL("triggered()"), self.add_files) - self.addDirectoryAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarAddDir.png"), _("A&dd Directory..."), self) - self.addDirectoryAct.setStatusTip(_("Add a directory to the tagger")) + self.add_directory_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarAddDir.png"), _("A&dd Directory..."), self) + self.add_directory_action.setStatusTip(_("Add a directory to the tagger")) # TR: Keyboard shortcut for "Add Directory..." - self.addDirectoryAct.setShortcut(QtGui.QKeySequence(_("Ctrl+D"))) - self.connect(self.addDirectoryAct, QtCore.SIGNAL("triggered()"), self.addDirectory) + self.add_directory_action.setShortcut(QtGui.QKeySequence(_("Ctrl+D"))) + self.connect(self.add_directory_action, QtCore.SIGNAL("triggered()"), self.addDirectory) self.save_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarSave.png"), _("&Save"), self) - self.addDirectoryAct.setStatusTip(_("Save selected files")) + self.add_directory_action.setStatusTip(_("Save selected files")) # TR: Keyboard shortcut for "Save" self.save_action.setShortcut(QtGui.QKeySequence(_("Ctrl+S"))) self.save_action.setEnabled(False) self.connect(self.save_action, QtCore.SIGNAL("triggered()"), self.save) - self.submitAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarSubmit.png"), _("S&ubmit PUIDs to MusicBrainz"), self) - self.submitAct.setEnabled(False) - self.connect(self.submitAct, QtCore.SIGNAL("triggered()"), self.submit) + self.submit_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarSubmit.png"), _("S&ubmit PUIDs to MusicBrainz"), self) + self.submit_action.setEnabled(False) + self.connect(self.submit_action, QtCore.SIGNAL("triggered()"), self.submit) self.exitAct = QtGui.QAction(_("E&xit"), self) self.exitAct.setShortcut(QtGui.QKeySequence(_("Ctrl+Q"))) @@ -183,84 +184,86 @@ class MainWindow(QtGui.QMainWindow): self.remove_action.setEnabled(False) self.connect(self.remove_action, QtCore.SIGNAL("triggered()"), self.remove) - self.showFileBrowserAct = QtGui.QAction(_("File &Browser"), self) - self.showFileBrowserAct.setCheckable(True) + self.show_file_browser_action = QtGui.QAction(_("File &Browser"), self) + self.show_file_browser_action.setCheckable(True) #if self.config.persi.value("persist/viewFileBrowser").toBool(): - # self.showFileBrowserAct.setChecked(True) - # self.connect(self.showFileBrowserAct, QtCore.SIGNAL("triggered()"), self.showFileBrowser) + # self.show_file_browser_action.setChecked(True) + # self.connect(self.show_file_browser_action, QtCore.SIGNAL("triggered()"), self.showFileBrowser) - self.showCoverArtAct = QtGui.QAction(_("&Cover Art"), self) - self.showCoverArtAct.setCheckable(True) + self.show_cover_art_action = QtGui.QAction(_("&Cover Art"), self) + self.show_cover_art_action.setCheckable(True) if self.config.persist["view_cover_art"]: - self.showCoverArtAct.setChecked(True) - self.connect(self.showCoverArtAct, QtCore.SIGNAL("triggered()"), self.showCoverArt) + self.show_cover_art_action.setChecked(True) + self.connect(self.show_cover_art_action, QtCore.SIGNAL("triggered()"), self.showCoverArt) - self.searchAct = QtGui.QAction(QtGui.QIcon(":/images/search.png"), _("Search"), self) - self.connect(self.searchAct, QtCore.SIGNAL("triggered()"), self.search) + self.search_action = QtGui.QAction(QtGui.QIcon(":/images/search.png"), _("Search"), self) + self.connect(self.search_action, QtCore.SIGNAL("triggered()"), self.search) - self.listenAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarListen.png"), _("Listen"), self) - self.listenAct.setEnabled(False) - self.connect(self.listenAct, QtCore.SIGNAL("triggered()"), self.listen) - - self.cdLookupAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarLookup.png"), _("&Lookup CD"), self) - self.cdLookupAct.setEnabled(False) - self.cdLookupAct.setShortcut(QtGui.QKeySequence(_("Ctrl+L"))) + self.cd_lookup_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarLookup.png"), _("&Lookup CD"), self) + self.cd_lookup_action.setEnabled(False) + self.cd_lookup_action.setShortcut(QtGui.QKeySequence(_("Ctrl+L"))) - self.analyzeAct = QtGui.QAction(QtGui.QIcon(":/images/analyze.png"), _("Anal&yze"), self) - self.analyzeAct.setEnabled(False) - self.analyzeAct.setShortcut(QtGui.QKeySequence(_("Ctrl+Y"))) + self.analyze_action = QtGui.QAction(QtGui.QIcon(":/images/analyze.png"), _("Anal&yze"), self) + self.analyze_action.setEnabled(False) + self.analyze_action.setShortcut(QtGui.QKeySequence(_("Ctrl+Y"))) - self.clusterAct = QtGui.QAction(QtGui.QIcon(":/images/ToolbarCluster.png"), _("Cluster"), self) - self.clusterAct.setEnabled(False) - self.clusterAct.setShortcut(QtGui.QKeySequence(_("Ctrl+U"))) + self.cluster_action = QtGui.QAction(QtGui.QIcon(":/images/ToolbarCluster.png"), _("Cluster"), self) + self.cluster_action.setEnabled(False) + self.cluster_action.setShortcut(QtGui.QKeySequence(_("Ctrl+U"))) - self.autoTagAct = QtGui.QAction(QtGui.QIcon(":/images/magic-wand.png"), _("Auto Tag"), self) - self.autoTagAct.setShortcut(QtGui.QKeySequence(_("Ctrl+T"))) - self.connect(self.autoTagAct, QtCore.SIGNAL("triggered()"), self.autoTag) + self.auto_tag_action = QtGui.QAction(QtGui.QIcon(":/images/magic-wand.png"), _("Auto Tag"), self) + self.auto_tag_action.setShortcut(QtGui.QKeySequence(_("Ctrl+T"))) + self.connect(self.auto_tag_action, QtCore.SIGNAL("triggered()"), self.autoTag) + + self.edit_tags_action = QtGui.QAction(QtGui.QIcon(":/images/tag.png"), + _("Edit &Tags..."), self) + self.edit_tags_action.setEnabled(False) + self.connect(self.edit_tags_action, QtCore.SIGNAL("triggered()"), + self.edit_tags) def createMenus(self): self.fileMenu = self.menuBar().addMenu(_("&File")) - self.fileMenu.addAction(self.add_filesAct) - self.fileMenu.addAction(self.addDirectoryAct) + self.fileMenu.addAction(self.add_files_action) + self.fileMenu.addAction(self.add_directory_action) self.fileMenu.addSeparator() self.fileMenu.addAction(self.save_action) - self.fileMenu.addAction(self.submitAct) + self.fileMenu.addAction(self.submit_action) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu(_("&Edit")) self.editMenu.addSeparator() - self.editMenu.addAction(self.optionsAct) + self.editMenu.addAction(self.options_action) self.viewMenu = self.menuBar().addMenu(_("&View")) - self.viewMenu.addAction(self.showFileBrowserAct) - self.viewMenu.addAction(self.showCoverArtAct) + self.viewMenu.addAction(self.show_file_browser_action) + self.viewMenu.addAction(self.show_cover_art_action) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu(_("&Help")) - self.helpMenu.addAction(self.helpAct) - self.helpMenu.addAction(self.aboutAct) + self.helpMenu.addAction(self.help_action) + self.helpMenu.addAction(self.about_action) def createToolBar(self): self.mainToolBar = self.addToolBar(self.tr("File")) self.mainToolBar.setObjectName("fileToolbar") - self.mainToolBar.addAction(self.add_filesAct) - self.mainToolBar.addAction(self.addDirectoryAct) + self.mainToolBar.addAction(self.add_files_action) + self.mainToolBar.addAction(self.add_directory_action) self.mainToolBar.addSeparator() self.mainToolBar.addAction(self.save_action) - self.mainToolBar.addAction(self.submitAct) - self.mainToolBar.addSeparator() - self.mainToolBar.addAction(self.cdLookupAct) - self.mainToolBar.addAction(self.analyzeAct) - self.mainToolBar.addAction(self.clusterAct) - self.mainToolBar.addAction(self.autoTagAct) + self.mainToolBar.addAction(self.submit_action) self.mainToolBar.addSeparator() + self.mainToolBar.addAction(self.cd_lookup_action) + self.mainToolBar.addAction(self.analyze_action) + self.mainToolBar.addAction(self.cluster_action) + self.mainToolBar.addAction(self.auto_tag_action) + #self.mainToolBar.addSeparator() + self.mainToolBar.addAction(self.edit_tags_action) self.mainToolBar.addAction(self.remove_action) self.mainToolBar.addSeparator() - self.mainToolBar.addAction(self.optionsAct) - self.mainToolBar.addSeparator() - self.mainToolBar.addAction(self.listenAct) + self.mainToolBar.addAction(self.options_action) + #self.mainToolBar.addSeparator() self.searchToolBar = self.addToolBar(_("Search")) self.searchToolBar.setObjectName("searchToolbar") @@ -283,7 +286,7 @@ class MainWindow(QtGui.QMainWindow): #hbox.addWidget(button, 0) self.searchToolBar.addWidget(searchPanel) - self.searchToolBar.addAction(self.searchAct) + self.searchToolBar.addAction(self.search_action) def setStatusBarMessage(self, message): """Set the status bar message.""" @@ -335,12 +338,15 @@ class MainWindow(QtGui.QMainWindow): """Tell the tagger to remove the selected objects.""" self.tagger.remove(self.selected_objects) - def listen(self): - pass - def submit(self): pass - + + def edit_tags(self, obj=None): + if not obj: + obj = self.selected_objects[0] + tagedit = TagEditor(obj.metadata, self) + tagedit.exec_() + def updateFileTreeSelection(self): if not self.ignoreSelectionChange: objs = self.fileTreeView.selected_objects() @@ -362,15 +368,21 @@ class MainWindow(QtGui.QMainWindow): def update_actions(self): can_remove = False can_save = False + can_edit_tags = False for obj in self.selected_objects: if obj.can_save(): can_save = True if obj.can_remove(): can_remove = True - if can_save and can_remove: + if obj.can_edit_tags(): + can_edit_tags = True + if can_save and can_remove and can_edit_tags: break + if len(self.selected_objects) != 1: + can_edit_tags = False self.remove_action.setEnabled(can_remove) self.save_action.setEnabled(can_save) + self.edit_tags_action.setEnabled(can_edit_tags) def updateSelection(self, objects=None): if objects is not None: @@ -412,7 +424,7 @@ class MainWindow(QtGui.QMainWindow): def showCoverArt(self): """Show/hide the cover art box.""" - if self.showCoverArtAct.isChecked(): + if self.show_cover_art_action.isChecked(): self.coverArtBox.show() else: self.coverArtBox.hide() @@ -421,3 +433,4 @@ class MainWindow(QtGui.QMainWindow): files = [obj for obj in self.selected_objects if isinstance(obj, File)] self.tagger.autoTag(files) + diff --git a/picard/ui/options/tags.py b/picard/ui/options/tags.py index 6b8228217..05ad1720f 100644 --- a/picard/ui/options/tags.py +++ b/picard/ui/options/tags.py @@ -31,7 +31,7 @@ class TagsOptionsPage(Component): BoolOption("setting", "clear_existing_tags", False), BoolOption("setting", "write_id3v1", True), BoolOption("setting", "write_id3v23", False), - TextOption("setting", "id3v2_encoding", "UTF-8"), + TextOption("setting", "id3v2_encoding", "utf-8"), ] def get_page_info(self): diff --git a/picard/worker.py b/picard/worker.py index 07fd9f7aa..9383f417c 100644 --- a/picard/worker.py +++ b/picard/worker.py @@ -102,8 +102,32 @@ class WorkerThread(QtCore.QThread): def do_save_file(self, args): file = args[1] self.log.debug("Saving file %s", file) + + file.lock_for_read() + self.emit(QtCore.SIGNAL("statusBarMessage(const QString &)"), QtCore.QString(_(u"Saving file %s ...") % file.filename)) - file.save() - self.emit(QtCore.SIGNAL("file_updated(int)"), file.id) + + saved = True + try: + file.save() + except Exception, e: + self.log.error(e) + saved = False + + if saved: + if self.config.setting["rename_files"]: + format = self.config.setting["file_naming_format"] + filename = self.tagger.evaluate_script(format, self.metadata) + filename = os.path.basename(filename) + \ + os.path.splitext(self.filename)[1] + filename = os.path.join(os.path.dirname(file.filename), + filename) + os.rename(self.filename, filename) + self.filename = filename + + file.unlock() + + self.emit(QtCore.SIGNAL("save_file_finished(PyObject*, bool)"), + file, saved)