diff --git a/libprs500/books.py b/libprs500/books.py index 7f88c901cb..c6fac5c608 100644 --- a/libprs500/books.py +++ b/libprs500/books.py @@ -16,6 +16,7 @@ from xml.dom.ext import PrettyPrint as PrettyPrint import xml.dom.minidom as dom from base64 import b64decode as decode +import time class book_metadata_field(object): def __init__(self, attr, formatter=None): @@ -27,7 +28,7 @@ class book_metadata_field(object): class Book(object): title = book_metadata_field("title") - author = book_metadata_field("author") + author = book_metadata_field("author", formatter=lambda x: x if x.strip() else "Unknown") mime = book_metadata_field("mime") rpath = book_metadata_field("path") id = book_metadata_field("id", formatter=int) diff --git a/libprs500/communicate.py b/libprs500/communicate.py index e8e73c13cd..32a2a41464 100755 --- a/libprs500/communicate.py +++ b/libprs500/communicate.py @@ -133,17 +133,22 @@ class PRS500Device(object): PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes MEDIA_XML = "/Data/database/cache/media.xml" #: Location of media.xml file on device CACHE_XML = "/Sony Reader/database/cache.xml" #: Location of cache.xml on storage card in device + + device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID) + def safe(func): """ Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly. It also calls L{open} to claim the interface and initialize the Reader if needed. - As a convenience, C{safe} automatically sends the a L{USBConnect} after calling func, unless func has + As a convenience, C{safe} automatically sends the a L{EndSession} after calling func, unless func has a keyword argument named C{end_session} set to C{False}. - An L{ArgumentError} will cause the L{USBConnect} command to be sent to the device, unless end_session is set to C{False}. + An L{ArgumentError} will cause the L{EndSession} command to be sent to the device, unless end_session is set to C{False}. An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}. + + @todo: Fix handling of timeout errors """ def run_session(*args, **kwargs): dev = args[0] @@ -153,17 +158,21 @@ class PRS500Device(object): res = func(*args, **kwargs) except ArgumentError, e: if not kwargs.has_key("end_session") or kwargs["end_session"]: - dev._send_validated_command(USBConnect()) + dev._send_validated_command(EndSession()) raise e except usb.USBError, e: if "No such device" in str(e): raise DeviceError() - elif "Connection timed out" in str(e): + elif "Connection timed out" in str(e): + dev.close() raise TimeoutError(func.__name__) + elif "Protocol error" in str(e): + dev.close() + raise ProtocolError("There was an unknown error in the protocol. Contact the developer.") dev.close() raise e if not kwargs.has_key("end_session") or kwargs["end_session"]: - dev._send_validated_command(USBConnect()) + dev._send_validated_command(EndSession()) return res return run_session @@ -174,16 +183,23 @@ class PRS500Device(object): @param: report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks If it is called with -1 that means that the task does not have any progress information """ - self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID, - PRS500Device.PRS500_PRODUCT_ID, - PRS500Device.PRS500_INTERFACE_ID) self.device = self.device_descriptor.getDevice() #: The actual device (PyUSB object) self.handle = None #: Handle that is used to communicate with device. Setup in L{open} self._log_packets = log_packets self.report_progress = report_progress - def is_connected(self): - return self.device_descriptor.getDevice() != None + def reconnect(self): + self.device = self.device_descriptor.getDevice() + self.handle = None + + @classmethod + def is_connected(cls): + """ + This method checks to see whether the device is physically connected. + It does not return any information about the validity of the software connection. You may need to call L{reconnect} if you keep + getting L{DeviceError}. + """ + return cls.device_descriptor.getDevice() != None @classmethod def _validate_response(cls, res, type=0x00, number=0x00): @@ -205,7 +221,7 @@ class PRS500Device(object): raise DeviceError() self.handle = self.device.open() self.handle.claimInterface(self.device_descriptor.interface_id) - res = self._send_validated_command(GetUSBProtocolVersion()) + res = self._send_validated_command(GetUSBProtocolVersion(), timeout=10000) # Large timeout as device mat still be initializing if res.code != 0: raise ProtocolError("Unable to get USB Protocol version.") version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version if version not in KNOWN_USB_PROTOCOL_VERSIONS: @@ -216,12 +232,12 @@ class PRS500Device(object): if res.code != 0: raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.") - def close(self): """ Release device interface """ + self.handle.reset() self.handle.releaseInterface() self.handle, self.device = None, None - + def _send_command(self, command, response_type=Response, timeout=1000): """ Send L{command} to device and return its L{response}. diff --git a/libprs500/gui/images.qrc b/libprs500/gui/images.qrc index e8c6bc113e..5c1aee6a10 100644 --- a/libprs500/gui/images.qrc +++ b/libprs500/gui/images.qrc @@ -3,6 +3,7 @@ images/cherubs.jpg images/mycomputer.png images/reader.png + images/memory_stick_unmount.png images/clear.png images/minus.png images/plus.png diff --git a/libprs500/gui/images/memory_stick_unmount.png b/libprs500/gui/images/memory_stick_unmount.png new file mode 100644 index 0000000000..f4d3a653a6 Binary files /dev/null and b/libprs500/gui/images/memory_stick_unmount.png differ diff --git a/libprs500/gui/images_rc.py b/libprs500/gui/images_rc.py index 07d281726e..87afaa5f31 100644 --- a/libprs500/gui/images_rc.py +++ b/libprs500/gui/images_rc.py @@ -1,6 +1,6 @@ # Resource object code # -# Created: Fri Nov 24 20:24:55 2006 +# Created: Sun Dec 3 17:57:59 2006 # by: The Resource Compiler for PyQt (Qt v4.1.4) # # WARNING! All changes made in this file will be lost! @@ -8,6 +8,298 @@ from PyQt4 import QtCore qt_resource_data = "\ +\x00\x00\x12\x16\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\ +\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\x11\xa8\x49\x44\x41\x54\x78\x9c\xed\ +\x9a\x79\x70\x5c\x47\x9d\xc7\x3f\xfd\xde\xdc\x33\xd2\xe8\x96\x2d\ +\xf9\x92\x7c\x61\x72\x40\x0c\x09\xc4\xd9\x84\x6c\x2a\x50\x40\x41\ +\x0c\x0b\x01\xb6\x08\x47\xc1\xd6\x2e\x57\xb1\xbb\x50\x5b\x0b\x45\ +\xed\x16\xd9\x9b\x64\xc3\x86\x2c\x09\x6c\x0e\x08\x39\x21\x4e\x48\ +\x70\x5c\xb1\xc9\x69\x12\x5b\x8b\x15\x47\x2b\x5f\xf2\x25\xd9\x3a\ +\xac\x6b\x66\xa4\x39\xde\x5c\xef\xe8\xee\xfd\x63\x24\xd9\x96\x95\ +\xd8\x49\x9c\xc8\x14\xf9\x4d\xa9\x66\x9e\xde\xf4\xaf\x7f\xdf\x6f\ +\xff\xae\xd7\x3d\x42\x6b\xcd\x1f\xb3\x18\xf3\x6d\xc0\x7c\xcb\x5b\ +\x04\xcc\xb7\x01\xf3\x2d\x6f\x11\x30\xdf\x06\xcc\xb7\xbc\x45\xc0\ +\x7c\x1b\xf0\x7a\x64\xe3\xc6\x8d\x17\x6e\xde\xbc\x39\xf8\x7a\x74\ +\x88\x3f\xc4\x3e\x60\xcb\x96\x2d\xcb\x94\x52\x77\xbb\xae\xbb\xce\ +\xf3\x3c\x03\xb7\xf0\x8b\x4f\x7c\xe6\xf3\x5f\x7e\x2d\xba\xfe\xa0\ +\x3c\xe0\xfa\xeb\xaf\x37\x9e\x7c\xf2\xc9\x6f\x2a\xa5\xf6\x29\xa5\ +\x2e\xa9\x31\x52\xfe\x4b\x0b\xb7\x9a\xae\xa7\xbe\x70\xd3\x4d\x37\ +\xbd\xfd\xb5\xe8\xf4\x9d\x6d\x23\xdf\x28\xd9\xba\x75\xeb\xdb\x2e\ +\xbd\xf4\xd2\xfb\xa4\x94\xab\x0c\x43\x47\x96\x5b\x1b\x59\x91\x7b\ +\x84\xb1\xbc\x9f\x70\xe0\x25\xb3\x2a\xb4\xf4\x3f\x80\x6b\x00\x84\ +\x10\x7e\xad\xb5\x7b\x26\x7a\xcf\xf9\x10\xd8\xba\x75\xab\x4f\x29\ +\xf5\x1d\x29\xe5\xf7\xa4\x94\x46\xdc\xed\x0f\x9c\x37\x71\x2b\x66\ +\x71\x98\xe1\x42\x94\xe6\x98\x47\xd2\x6b\xe4\x59\xff\x5f\xc9\xd1\ +\xf1\xd4\x92\xef\x7f\xff\xfb\x35\xc0\x32\xe0\xb7\x5a\x6b\x79\x3a\ +\xfd\xe7\xb4\x07\xbc\xf0\xc2\x0b\x6b\xb5\xd6\xf7\x2b\xa5\x5a\x85\ +\x76\x42\xab\x33\xbf\x64\x49\x6e\x13\x23\x85\x30\x4e\x3e\x46\x55\ +\x59\x12\xab\x55\x94\x64\x1a\x7f\xbe\xcf\xb0\x0b\xea\x0e\xe0\xce\ +\xa9\xe1\xcb\x80\xbe\xd3\xcd\x71\x4e\x12\xb0\x75\xeb\xd6\x90\x69\ +\x9a\xff\xe4\x79\xde\x37\xa5\x94\x66\xbc\x7c\xd0\xb7\x3a\x75\x07\ +\xba\x30\x42\xaf\x15\x83\x01\x81\x90\x50\xbf\x56\x22\x0c\x41\xa6\ +\x1c\x60\xa1\xf3\xa2\x98\x6c\xf8\xf0\x07\x6a\x6b\x6b\xef\x4b\xa7\ +\xd3\x65\x60\x85\x10\xe2\x88\x3e\x8d\x8b\x9f\x73\x49\xb0\xb3\xb3\ +\xf3\x72\xbf\xdf\x7f\x40\x29\xf5\x15\xbc\x72\xb0\x7d\xf2\x61\xdf\ +\x79\xe3\x3f\x26\x99\xb1\x99\x48\x47\xa1\xcf\xa0\x2a\xae\x58\xf1\ +\x5e\x07\x5f\x50\xe0\x28\x93\xbc\x6b\x50\x2f\x12\x84\xd4\xa4\xf1\ +\xa9\x4f\x7e\xfc\x63\x53\xaa\x22\x40\xeb\xe9\xe6\x3b\x67\x3c\x60\ +\xfb\xf6\xed\x55\x3e\x9f\xef\x46\xd7\x75\xbf\xe0\xba\x6e\xa0\xaa\ +\x78\xd8\x58\x36\xb9\x11\x3b\x9f\xa2\xaf\x14\xa3\x6a\xc2\xc2\xa7\ +\x05\x4b\x2e\x95\x08\x3c\x32\xe3\x26\x13\x83\x3e\xec\x2a\x1f\xd4\ +\x9a\xe8\x96\x3f\xa5\xbd\xfa\x42\xa3\xc8\xc0\x35\x81\x40\x60\x83\ +\xe3\x38\x12\x58\x09\x1c\x7b\xa5\x79\xcf\x09\x0f\xe8\xea\xea\xfa\ +\xa0\xcf\xe7\x3b\xec\xba\xee\x67\xb5\x9d\x0f\x2d\x9a\x78\xca\x58\ +\x9a\x7c\x9c\x91\xb4\x4b\x59\x47\x08\x1c\xc9\x93\xea\xb1\x31\x43\ +\x7e\x86\xf7\x07\xd8\xfb\x74\x88\x23\x9d\x01\x32\x09\x41\xb1\xb6\ +\x09\x75\xd1\x77\xd0\x6f\xfb\x22\x75\x2d\xab\x88\x04\xcd\xc0\xa7\ +\xae\xfd\xc4\xd5\x53\xaa\x6b\x84\x10\x8d\xaf\x34\xf7\xbc\x7a\x40\ +\x4f\x4f\x4f\x5d\xb1\x58\xbc\xd5\xf3\xbc\xf5\xae\xeb\x86\x22\xd6\ +\x11\xb1\x20\xdb\x49\xde\x4a\x33\x50\x0a\xb0\x28\x2e\xc8\xed\x1a\ +\xc7\x1a\x2e\x03\x30\xd2\xed\x50\xb5\xc0\x04\x2a\x61\xed\xff\x93\ +\x75\xb8\xeb\xbe\x06\x46\x10\xb4\x46\x6b\xcd\xd2\xa5\x6d\x46\xb1\ +\x64\x7f\x1a\xf8\xed\xd4\x34\x2b\x81\xe4\xcb\xd9\x30\x6f\x1e\xb0\ +\x67\xcf\x9e\x6b\xcb\xe5\x72\x9f\xe7\x79\xd7\xc8\xa2\x15\x6e\x4c\ +\xed\x14\xf5\x93\xdd\x0c\x4d\xd8\xd8\x83\x29\x9a\x95\x8b\x70\x0d\ +\x72\xfd\x59\x7c\x11\x1f\xf5\xab\xab\x59\xfe\x81\x7a\x5a\x2e\x8e\ +\x60\x86\x43\x34\x7e\xee\xeb\xc8\x2b\xbe\x8d\x30\x43\x00\x08\x21\ +\x30\x0c\x83\xc6\xd6\x36\x02\xa1\x70\xf5\xfa\xf5\xeb\x2f\x99\x9a\ +\xaa\x59\x08\x11\x7f\x39\x3b\xde\xf4\x3e\x60\xff\xfe\xfd\x0b\x5d\ +\xd7\xbd\xd3\xf3\xbc\xf7\x95\xcb\xe5\x68\x20\x37\x4a\x6d\xee\x28\ +\xd9\xdc\x24\x65\xbb\x48\x7c\x60\x1f\xa5\xc3\xbd\xc4\x56\x2e\xa6\ +\xfe\x9d\x4b\xd0\x25\x0b\x33\xe0\x61\x62\x53\x1c\xb5\x28\x7b\xad\ +\x54\xff\xd9\xf7\x18\x75\x03\x38\x8e\x73\x92\x6e\xa5\x14\x42\x08\ +\x86\x8e\x1e\xe4\xe0\x81\x9e\x91\xef\xfe\xe3\xbf\x7d\x63\xea\xd6\ +\x90\xd6\x7a\xe7\x5c\xf6\xbc\xa9\x1e\xd0\xd3\xd3\xf3\x25\xcf\xf3\ +\x0e\xd9\xb6\x7d\x55\xd9\xca\x45\x63\xc9\x41\xc2\xe9\x51\x06\x27\ +\x0a\x98\xfe\x10\xd5\x7d\x7b\x29\x1d\xee\x05\xa0\x38\x38\x86\xeb\ +\x00\xa1\x08\x4a\xf9\x18\xdf\x53\xa6\x14\xbc\x84\xa6\xaf\xdc\x4e\ +\x8a\x28\x9e\xe7\x61\x18\x06\x42\x88\xe3\x60\x8c\x0a\x9c\x96\x25\ +\x2b\x08\x46\xe2\x0b\xae\xbc\xf2\xca\xd5\x53\xb7\x16\x09\x21\xc2\ +\x73\xd9\xf4\xa6\x10\x70\xf0\xe0\xc1\xb6\xfd\xfb\xf7\x3f\xef\x79\ +\xde\x2d\x85\x42\x21\xa6\xd3\xb9\x50\xd5\xc4\x24\x99\x89\x0c\x89\ +\xbc\x43\x4b\x63\x03\x11\x21\xb0\x8f\x0d\xce\x8c\x51\xb6\x4b\xea\ +\x85\x3d\xd8\x59\x49\xc1\x5a\xc8\xc2\xaf\xdd\x49\xcb\x5f\xde\x40\ +\x22\x93\xa3\x5c\x2e\xcf\x80\x15\x42\xcc\x90\xa0\xb5\x46\x08\x81\ +\x69\x9a\x2c\x6a\x69\x36\xae\xba\xfc\x92\x2f\x4e\xa9\x13\xc0\x8a\ +\xb9\x6c\x7b\xa3\x09\x30\x7a\x7b\x7b\xff\x46\x6b\xbd\xaf\x5c\x2e\ +\x5f\x52\x48\x17\xa2\xfe\x94\x44\x67\xca\x0c\x25\xd3\x84\xc3\x31\ +\x16\xd4\x37\x81\x08\x52\xdc\xf9\x3c\xcc\x0a\xc7\x50\xfb\x6a\x6a\ +\xae\xb9\x9e\x05\x7f\x71\x33\xbe\xa6\x25\x8c\x8c\x8c\x60\x59\x16\ +\x70\x1c\x2c\x1c\x8f\xff\x13\x49\x59\xdc\xb6\x9a\x68\xbc\x61\xd5\ +\xda\xb5\x6b\xa7\x7b\x81\x65\x42\x08\xff\x29\x06\xbe\x51\xc8\x07\ +\x06\x06\xd6\xf4\xf6\xf6\xee\x74\x1c\xe7\x9f\x73\xb9\x5c\xd8\x4b\ +\x7b\xc1\x80\x15\x20\x91\xca\x90\x29\xda\xb4\x36\x2f\x22\x1c\xae\ +\x46\x0b\x3f\xde\xf0\x51\xbc\x91\x7e\x22\x57\x7c\x14\xb3\xb6\x11\ +\x21\x0c\x1a\xd6\x7f\x9e\x25\xdf\xba\x1b\x7f\x43\x1b\x9e\xe7\x31\ +\x3c\x3c\x4c\xb1\x58\x9c\x59\xe1\xe9\x95\x9f\x0d\x7c\xfa\xda\x74\ +\xd3\x34\x18\x29\xf1\xe1\xf7\xbf\xef\xba\x29\x93\x7c\x40\xdb\x6c\ +\x3b\xcf\x7a\x19\x7c\xe9\xa5\x97\xfc\xf5\xf5\xf5\xdf\x95\x52\x7e\ +\xb7\x50\x28\xf8\xec\xbc\xed\x0b\x96\x83\x14\xad\x22\xa9\x74\x8a\ +\xba\xea\x3a\xc2\x01\x03\xad\xcb\x68\xe5\x21\xf3\x59\x9c\xae\xa7\ +\x08\xbd\xff\xf3\x18\xf5\xf5\xd4\x2e\x5d\x45\xb8\xba\x99\xf8\x7b\ +\x3f\x04\x80\xeb\xba\x8c\x8e\x8e\xe2\xba\xee\x0c\xe8\xe9\x64\x37\ +\xfd\x19\x2a\xf1\xaf\xb5\x46\x4b\x49\xb9\x77\x13\xf9\xfd\x8f\x50\ +\x27\x22\xc4\xeb\x3f\x76\x71\x7b\x7b\x7b\xcd\x91\x23\x47\x32\xc0\ +\x72\x21\x44\xaf\xd6\x5a\x4d\xdb\x7b\x56\xab\xc0\xe8\xe8\xe8\xbb\ +\x6c\xdb\x7e\xc0\x71\x9c\x16\x2b\x67\xc5\x7c\x8e\x0f\xd3\x36\x19\ +\x4b\x8e\x21\xb4\xa6\x76\xe4\x25\x74\xa2\x0f\xac\x14\x58\xa3\x68\ +\x2b\x05\xa6\x89\xff\x8a\xeb\xf0\x2f\x6e\xa7\x7e\xd5\x1a\xe2\x6d\ +\x6b\x60\x0a\x5c\xb9\x5c\x66\x7c\x7c\x1c\x29\x25\xd3\x76\xea\xa9\ +\x7a\x3f\xdb\x6e\xa5\x14\xde\xe4\x21\xb2\xbf\xff\x09\xe5\xb1\x61\ +\x3c\x17\x30\x04\xe3\x8b\x3e\xa0\x0e\x8c\xa9\x27\x7f\xf8\xa3\x5b\ +\x6f\x9f\xfa\xea\xff\x69\xad\xfb\xa7\xc7\x9d\x15\x0f\xe8\xef\xef\ +\x0f\x05\x83\xc1\x7f\x71\x5d\xf7\xeb\x96\x65\x05\xbc\x92\x67\xc4\ +\x88\x61\x15\x2c\xc6\x27\xc7\x69\x88\x45\x61\xcb\x8d\xa8\x63\xbb\ +\x4e\x1e\x18\x88\x60\x5c\xfd\x55\x22\xab\x2e\x60\xc1\x05\xe7\xe3\ +\x8f\x44\x67\xe2\x3a\x9f\xcf\x33\x31\x31\x31\x03\xf4\xc4\x6c\x2f\ +\x84\x98\xf9\xbf\x52\x0a\xe5\x96\xc8\xbc\xf0\x53\x26\x3b\xb6\xe3\ +\x15\x05\x20\x40\x54\xde\x6b\xc3\xbb\x8c\x85\x2d\x1f\xba\xba\xa1\ +\xa1\xe1\xde\x54\x2a\x55\xa2\x92\x0c\x67\x08\x78\xdd\x1e\x90\x4c\ +\x26\xaf\x70\x1c\xe7\x7e\xdb\xb6\x6b\x73\xd9\x5c\x34\x44\x08\x9f\ +\xf4\x31\x32\x36\x82\xdf\xf4\x13\x37\x15\xe5\x87\xff\x01\x95\x3c\ +\x5a\x99\xb0\xf5\x02\x88\x35\xa3\x63\x0d\x88\x65\x6b\x59\x78\xc9\ +\xbb\xa9\x5b\xb2\xf0\xa4\x4c\x9e\xcd\x66\xc9\xe5\x72\x33\xd7\xd3\ +\xef\x27\xae\xfc\xf4\xe7\xd2\xb1\x9d\x24\x1e\xbf\x15\xd7\xca\x61\ +\x84\x40\x18\x0a\x27\x0b\xaa\x64\xe0\x6f\xd5\x44\x9a\xab\xe9\x0f\ +\x5e\x2e\xbb\xfa\x72\x0f\xfc\xf4\xf6\xbb\x1e\x9d\x32\xfb\xf7\x5a\ +\xeb\x51\x78\x1d\x1e\x90\x4a\xa5\xaa\xb4\xd6\x3f\x74\x1c\xe7\xba\ +\x6c\x36\x1b\x54\xae\x12\x75\xc1\x3a\x32\x99\x0c\xe3\xe9\x71\x9a\ +\x1a\x9b\x30\x9d\x12\xd6\x9d\x5f\x45\xe7\x12\x33\xe3\x44\xdb\x25\ +\xe8\x35\x1f\x22\x56\x1b\xa5\x75\x4d\x0b\x81\xf0\xf1\x3d\x4d\xd7\ +\x75\x49\xa7\xd3\xa7\xc4\xfb\x34\x60\xc3\x30\x66\xae\xbd\x62\x9a\ +\x89\xae\x5f\x92\xed\xd8\x8a\xca\x39\x04\xd7\x84\xd0\xca\x41\x39\ +\x12\x03\x08\x2e\x31\x89\xc6\x63\x04\x83\x21\x5c\x1d\xa7\xa9\x56\ +\x2e\x3d\xc1\xfc\x95\xc0\x6b\x27\x20\x9d\x4e\x7f\x58\x29\x75\x77\ +\xb1\x58\x8c\x5a\x39\x2b\x54\x15\xaa\xc2\x30\x0c\x8e\x1d\x3b\x46\ +\x24\x1c\x61\xc9\xe2\x25\x28\xa9\xd0\xa6\x1f\x73\xd1\x79\x78\x3d\ +\xc7\x09\x50\x3b\xee\xa7\xe5\x83\xd7\x52\xb7\x7c\xe9\x49\x6e\x5d\ +\x2c\x16\xc9\xe5\x72\x33\x09\x6e\x3a\xa9\x4d\x7f\xe7\xf8\x35\x64\ +\x0e\x6d\x63\x62\xef\x26\xbc\xc3\x43\xc8\x44\x09\xfc\x02\x7b\xd8\ +\x05\x34\x46\xcc\x47\x55\x4b\x98\x70\xc0\x20\x11\x58\xa3\x8f\x7a\ +\x2b\xdc\xee\xee\xee\x9f\xdc\xf3\xc0\x23\xbf\x3b\x01\x42\xbd\x10\ +\xa2\x4e\x6b\x3d\xf9\xaa\x08\xb0\x2c\xab\xde\xf3\xbc\xdb\x1c\xc7\ +\xf9\xe8\xe4\xe4\x64\xd8\x14\x26\xcd\x75\xcd\xa4\x92\x29\x72\xd9\ +\x1c\x2d\x2d\x2d\x98\x86\x89\xf4\x24\x42\x0b\x94\x54\x04\xaf\xfa\ +\x32\xde\xe1\x0e\x70\x6d\x44\xb8\x8a\x25\xdf\xbe\x95\xea\x15\xcb\ +\x8e\x13\xa2\x14\xb9\x5c\x0e\xdb\xb6\x4f\x02\x0a\x9c\xe2\x01\x4e\ +\x76\x9c\xe1\x8e\x0d\xd8\x89\x23\x08\xe1\xa1\x52\x16\x42\x80\x36\ +\x05\x98\x06\xfe\xe6\x28\xb1\x58\x00\x33\x10\xa4\x8b\x2b\xa5\x95\ +\x49\xef\xf9\xe9\xdd\x77\xdc\x3c\x38\x38\x98\x9b\x05\x45\x03\x55\ +\xc0\xe4\x19\xe7\x80\x7c\x3e\xff\x29\xcf\xf3\x6e\xcf\xe5\x72\xc1\ +\x7c\x3e\x1f\xaa\x8b\xd7\x81\x82\xa1\xa1\x21\xe2\xd5\x71\xaa\x22\ +\x55\x48\x29\x51\x52\xa1\xa4\x42\x7a\x95\xcf\xd2\x95\x78\x7b\x9e\ +\xc4\xeb\x78\x80\xb6\xef\xdd\x45\x68\xd1\x8a\x19\x70\x8e\xe3\x60\ +\x59\x16\x4a\xa9\x99\x98\x3e\x11\xf0\x34\x41\x5a\xba\x8c\xee\xfa\ +\x1d\x93\xcf\x3d\x8c\x1e\x3c\x8a\x00\xbc\x0b\x56\x62\x28\x1b\x59\ +\x2e\x21\xa2\x3e\xa2\xd1\x30\x11\xbf\x64\xd4\xb7\x4a\x0f\x7b\x2d\ +\xe5\x8e\x1d\x3b\x6f\x79\xf8\x91\xdf\xec\x98\x03\x4a\x16\xe8\xd2\ +\x5a\x67\xe0\x0c\x92\x60\xb1\x58\x6c\x51\x4a\xfd\xcc\xb6\xed\xcb\ +\x53\xa9\x54\x24\x1c\x0e\x53\x53\x5d\xc3\xe8\xc8\x28\xa5\x52\x89\ +\x85\xcd\x0b\x2b\x86\xba\x0a\xad\x34\x9e\xeb\xa1\x95\x46\xba\x12\ +\x29\x25\xe1\x58\x98\xba\xd6\x3a\x70\xcb\x98\xe1\xe8\x89\x7a\x29\ +\x97\xcb\xa7\x24\xb5\x13\x09\xd0\x5a\x93\x1d\x19\x60\xe8\x99\x5f\ +\xc1\x9e\xed\xe8\x82\x85\x16\x06\xb2\xb1\x01\x1d\xf4\x21\xe3\x61\ +\x82\xb1\x30\xb1\x88\x0f\x65\x86\xe8\x91\x17\xc8\xcc\xe4\xf8\x8b\ +\xb7\xde\xf9\xe0\x8f\x13\x89\x44\x71\x16\x14\x05\x1c\x00\x0e\x9d\ +\xb8\x4d\xf6\x8a\x21\x50\x2e\x97\xbf\xac\xb5\xfe\x51\x36\x9b\xf5\ +\x17\x0a\x85\x40\x73\x73\x33\x4a\x29\x8e\xf6\x1f\xa5\xa1\xae\x81\ +\xa6\xc6\xa6\x99\x55\x17\x42\xa0\x50\x95\xae\x5b\x80\x16\x9a\x68\ +\x6d\x94\xfa\x96\xfa\xa9\x99\x2a\x25\xce\xf3\x3c\x8a\xc5\x22\x52\ +\xca\x99\xa4\x36\xbb\xad\x05\xf0\x6c\x9b\xfe\x97\x3a\xb0\x5e\x78\ +\x14\x23\x3d\x82\x76\xbd\x4a\xdb\xaa\x15\x48\x8d\x6c\x68\xa2\xaa\ +\x26\x46\xd0\xf4\x18\x62\x89\x9a\xb0\x63\xc5\xad\xdb\xb6\xdd\xb4\ +\x71\xd3\x13\xbb\xe6\x80\x32\x41\xa5\xfe\x5b\xb3\x6f\xbc\x9c\x07\ +\xb4\x95\x4a\xa5\xfb\x1c\xc7\x79\x47\x22\x91\x88\x46\xa3\x51\x6a\ +\x6a\x6a\x18\x19\x19\x41\x4a\xc9\x82\xe6\x05\x08\x04\x9e\xeb\x4d\ +\xd5\x61\x35\x43\x84\xf4\xe4\x8c\xfb\x6b\xa5\x69\x5d\xd5\x8a\x69\ +\x9a\x00\xd8\xb6\x8d\xe3\x38\x73\xae\xf4\x74\xf2\x53\x4a\x31\xda\ +\x77\x84\x81\xee\x9d\xf8\xbb\x1e\xc7\x9f\x3c\x8a\xbd\x70\x25\xfe\ +\xd1\x5e\x64\x20\x88\xdd\xda\x8e\x7f\x51\x0b\xb1\xb0\x89\x2d\x02\ +\x1c\x76\x5b\x65\x2a\x31\xfa\xfc\x6d\x77\xdc\x73\xc7\xd4\x66\xe8\ +\x89\xe2\x01\xfb\xb4\xd6\x47\x5e\x6e\x91\x4f\x21\x40\x4a\xf9\xe7\ +\x9e\xe7\xfd\x2c\x9d\x4e\x9b\x85\x42\xc1\xdf\xd8\xd8\x88\xe3\x38\ +\x8c\x8e\x8e\xd2\xd4\xd4\x44\x2c\x16\xab\x80\x9e\x8a\x75\xcf\xf3\ +\x2a\x04\x94\x0a\x58\x2f\x6e\x21\x7c\xf1\x47\x2a\x71\xef\x79\x68\ +\xa9\x89\x37\xc6\x89\x37\xc4\x71\x1c\xa7\x32\xee\x84\x78\x3f\xb1\ +\xae\x2b\xa5\xc8\x67\xf3\x1c\xde\xb9\x97\xcc\xf8\x08\xd1\x3d\x8f\ +\xe1\x4f\x1c\x02\x04\x1a\x28\x2d\x5a\x83\xbd\xa8\x9d\x78\x75\x84\ +\x80\xe9\x31\xe4\xd5\xca\xb4\x6d\xe4\x36\x3f\xf5\xdc\x8d\xcf\x3c\ +\xf3\xcc\x81\x39\xb0\x8d\x03\xdd\x5a\xeb\xd9\xa1\x70\x92\x9c\x12\ +\x02\x52\xca\x92\x6d\xdb\xd2\xb2\xac\xd0\xe2\xc5\x8b\x19\x1c\x1c\ +\x44\x08\x41\x5b\x5b\xdb\x8c\xa1\x15\xea\xc0\xcd\xa7\xb1\x3a\x36\ +\x81\x08\x50\xe8\xdc\x8c\xb9\x60\x19\xea\x5d\x1f\x44\x4f\x85\x81\ +\x42\x91\x1a\x4f\x11\x8a\x85\xa6\x9a\xb3\xe3\xe5\x6d\x7a\xc5\xb5\ +\xd6\x48\x4f\xd2\xdf\xd3\x4f\xff\xfe\x7e\xa4\xeb\x10\x3b\xf4\x0c\ +\xa1\xe4\x61\x14\xa0\x4c\x3f\xd6\x92\x77\xe0\x6b\x7f\x3b\x75\x41\ +\x83\xa2\x86\x43\x76\x44\x8e\x8d\x0c\xff\xf6\xb6\xdb\xef\xfa\x45\ +\x3e\x9f\x9f\x7d\x02\xe4\x00\x7b\xb4\xd6\x83\x9c\x81\x9c\x42\x40\ +\x20\x10\xd8\x58\x2a\x95\xd2\x5a\xeb\xe8\x74\x69\xaa\xad\xad\x3d\ +\x09\xbc\x52\x0a\x55\xb4\x28\x74\x3c\x4e\xf8\x82\xcb\x71\xc6\x87\ +\x11\xa6\x41\xf8\x3d\x1f\xc5\xda\xf0\x03\x22\x1f\xfb\xbb\x0a\x30\ +\x25\x71\xca\x0e\x99\x54\x86\xba\xe6\xba\x93\xfa\xf7\x69\x22\xd2\ +\xa9\x34\x87\x3b\xf7\x20\xbb\x37\x53\x95\x3e\x86\xb5\x78\x1d\x32\ +\x50\x85\x36\x0c\x9c\x68\x13\xd6\xea\x2b\xa9\x6e\x6a\xc6\x34\x5c\ +\xfa\x4b\x48\xab\xec\x4e\x6e\xda\xb2\xe9\x86\xe7\x9f\x7f\x7e\xae\ +\x43\x8f\x61\x60\x97\xd6\xda\x3e\x13\xf0\xf0\x32\x39\x20\x9d\x4e\ +\x7f\xb5\x50\x28\xdc\x50\x2e\x97\x63\x4d\x4d\x4d\x0c\x0f\x0f\xd3\ +\xda\xda\x7a\x92\xdb\x66\x9f\x7e\x10\x59\xca\x63\x0f\x1c\xa2\xb4\ +\x77\x3b\xb1\xab\x3f\x87\x6f\xd9\xf9\x78\xe5\x22\xce\xbe\xdf\x23\ +\x96\x5d\x88\x5e\x71\x31\x9e\xe3\x01\xb0\xfc\xbc\xe5\x08\xe3\x78\ +\xbb\xeb\xd8\x0e\x7d\x7b\xfb\x48\x6d\xdf\x42\x68\xf7\xa3\x18\xa5\ +\x2c\x4e\x75\x0b\xe3\x6b\xbf\x84\x2b\x35\x3e\x7b\x82\x68\x5d\x2d\ +\x61\xbf\x81\xe5\x95\x18\x29\xda\x72\x68\x70\xe0\x37\xff\x73\xe7\ +\x5d\x0f\x96\x4a\xa5\xd9\x47\x5e\x65\x2a\xee\x3e\x7a\xa6\xc0\xa7\ +\x65\xce\x2a\x50\x28\x14\xee\x16\x42\xfc\x20\x97\xcb\xd1\xdc\xdc\ +\x8c\x61\x18\xb8\xae\x3b\xb3\x6a\xe5\xc3\xdd\xf8\x16\xb6\x31\x71\ +\xcb\xdf\x62\xd6\x35\xe3\x5b\xb0\x0c\x55\xb6\xc8\xde\xf7\x4f\xa8\ +\xdc\x04\x00\xe1\x0b\xaf\x42\x53\x71\x7b\xd7\x71\x99\x4c\x4c\xd2\ +\xd8\xd2\x88\x52\x8a\xe4\x48\x92\x23\xdd\xbb\x11\xdb\xee\x21\x3a\ +\xdc\x83\xf6\x6c\xca\x0b\xce\x67\x72\xf5\x47\x90\xc2\xc0\xf4\x0b\ +\xe2\x75\xed\x18\x86\xc7\xd1\xec\xb8\x57\x28\x15\x13\x8f\x6e\xdc\ +\x78\xc3\x8e\x1d\x3b\xe6\x72\xeb\x7e\x60\xef\x99\x1e\x86\x9e\x11\ +\x01\x8b\x16\x2d\x2a\x0d\x0d\x0d\xdd\x1c\x0a\x85\xbe\x35\x36\x36\ +\x16\x6d\x6a\x6a\x22\x95\x4a\x51\x5f\x5f\x8f\x52\x0a\x67\x62\x0c\ +\xff\xe2\x55\x68\xd7\xc6\x1b\xaf\xd8\xe4\x0e\x56\xf2\x90\x08\x86\ +\xf1\x5d\x78\x15\x46\xeb\x4a\xa4\xeb\xa2\xb4\x42\x69\x45\x62\x38\ +\x41\xb4\x2a\x4a\xff\xe1\x7e\x26\xf7\x77\x13\x79\xf6\x36\x8a\x6d\ +\xef\x81\xfa\x3c\xd6\xe2\xcb\x28\x34\xac\x40\x79\x8a\x78\x55\x8c\ +\xa0\x2f\xc8\x44\x61\x82\x64\x21\xe9\xf5\x1d\xe9\x7b\xe8\xae\xbb\ +\xee\xfa\xb5\xe3\x38\x6a\x96\x99\x05\x2a\xa5\xed\x65\xb7\xbc\x5f\ +\x33\x01\x00\x8e\xe3\xfc\xd8\x34\xcd\xbf\x4f\x24\x12\xb4\xb4\xb4\ +\x20\x65\x25\xb3\xcf\x3c\x8a\x06\x2b\x7b\x8c\xc2\x30\xf1\xb5\x9d\ +\x87\x88\xd5\x10\xbc\xfc\x93\xc8\xc9\x04\xe2\x6d\xeb\x2a\xa5\x50\ +\x29\xf4\xd4\xcb\x75\x5d\xf6\x74\xed\x81\xe4\x10\xd1\xa7\x6e\x06\ +\xbb\x40\xb8\xff\x45\x26\x2f\xbe\x0e\x3b\xb6\x90\x90\xe9\x23\x16\ +\x8d\xa1\xa4\xa2\x37\xd1\xeb\xe5\x8b\xf9\x63\x1b\x1e\xde\x70\x63\ +\x77\x77\xf7\x6c\xb7\xd6\x40\x2f\xb0\xff\x4c\x4e\x7f\x5f\x33\x01\ +\xcb\x97\x2f\x4f\xec\xdd\xbb\xf7\x7e\x9f\xcf\xf7\x99\xb1\xb1\xb1\ +\x70\x63\x63\x23\x99\x4c\x86\x68\x34\x8a\x51\x5d\x8f\xd3\xbf\x1f\ +\x84\xa0\xea\xd3\xdf\xc6\x5c\xd8\x8e\x3b\x39\x8e\xb2\x26\x31\xda\ +\xdf\x89\xd2\x1a\xa5\x4f\x68\x6f\x75\x85\x08\x23\x3b\x8e\x7f\xf3\ +\x7f\x22\xec\x02\x1a\x50\xca\xc3\xf0\x6c\x6a\x6b\x6b\x09\xfa\x83\ +\x0c\x4f\x0c\xab\xb1\xf4\x98\x3c\x70\xf0\xc0\x3d\x3f\xff\xf9\xcf\ +\x9f\x90\x52\xce\x4e\x50\x39\x2a\x6d\x6c\xfa\xf5\x02\x3f\x2d\x01\ +\x00\x4a\xa9\x1f\xf8\x7c\xbe\xcf\x0e\x0d\x0d\xd1\xd2\xd2\x42\x32\ +\x99\x44\x4a\x89\xd9\x76\x3e\x99\x5b\xfe\x9a\xd8\xc7\xbf\x81\xa8\ +\x69\x46\x79\x0e\x66\x4b\x3b\xce\xc1\x2e\x44\x30\x7c\xd2\xea\x4b\ +\x55\x59\x24\x85\xc2\xdf\xf9\x6b\xcc\x92\x85\x12\x50\x5e\xfa\x6e\ +\xec\xf7\x7c\x86\xba\xfa\x05\x94\xcb\x65\x76\xf7\xef\x76\x2d\xcb\ +\xea\x7b\xf0\x57\x0f\xfe\xd7\xbe\x7d\xfb\x66\xbb\xb5\x02\x0e\x52\ +\x69\x63\x67\x87\xc2\xeb\x92\xd3\x3e\x0b\x74\x76\x76\x6e\xb1\x2c\ +\xeb\xaa\xf6\xf6\x76\x7f\x34\x1a\xc5\xb2\x2c\xfc\x7e\x3f\x85\xa7\ +\x1f\x00\xc3\x44\xfb\xfc\x68\x61\x60\x54\xd5\x21\x56\xbe\xbb\xd2\ +\x18\x79\x0a\xd7\x75\x2b\x8d\x92\xeb\x21\x3d\x89\xe7\x7a\xb8\xe5\ +\x12\xbe\xae\x4d\x78\xf1\x16\xc2\xe7\xbf\x8f\x80\x3f\x40\xdf\x60\ +\x9f\x1c\x4e\x0e\xcb\xdd\xbb\x77\xdf\x71\xef\xbd\xf7\x3e\x33\x87\ +\x09\x93\x54\x56\xfd\x94\x36\xf6\x4d\x21\xa0\xa3\xa3\xe3\x0a\xd7\ +\x75\x37\xbb\xae\x1b\xb9\xec\xb2\xcb\x18\x18\x18\x20\x18\x0c\xa2\ +\x95\x44\x69\x90\xae\x06\xa1\x51\xba\xb2\x6f\x37\x03\xf8\x04\x22\ +\x66\x08\x70\x5c\x22\xe1\x08\x55\xb1\x2a\xf2\xf9\x3c\x5d\x3d\x5d\ +\x6e\x26\x93\xd9\x7f\xdf\xfd\xf7\xfd\xa8\xb7\xb7\x77\xb6\x5b\x4b\ +\x60\x1f\x70\xda\x33\xfe\x37\x94\x00\x80\x67\x9f\x7d\x76\x97\x65\ +\x59\x17\x5c\x74\xd1\x45\xc2\xef\xf7\x63\x59\xd6\x4c\x32\x9c\xd8\ +\xd6\x83\xeb\x4a\xea\x2e\x5b\x8d\x52\xc7\x9f\x09\x5c\xd7\x45\xba\ +\xb2\xe2\x09\x9e\x42\x20\xa8\xa9\xa9\x21\xe4\x0f\xb1\xab\x67\x97\ +\x37\x3c\x36\xec\x76\xbe\xd8\x79\xdb\x43\x0f\x3d\xb4\x7d\x8e\x29\ +\x13\x54\x32\xfc\x2b\xb6\xb1\x67\x43\xce\x68\x43\x44\x29\xf5\xaf\ +\x3e\x9f\xef\x8e\x43\x87\x0e\x55\xaf\x5b\xb7\x6e\x66\xe7\x46\x29\ +\x85\xb3\xaf\x03\x99\xcb\xa2\x2e\x5d\x59\x01\xaf\x14\xae\xe7\xce\ +\xdc\x47\x43\x3c\x1e\x27\x16\x8b\x91\xc9\x64\xd8\xda\xb1\xd5\xcb\ +\xa4\x33\x2f\xdd\xfd\x8b\xbb\x7f\x32\xc7\x46\x85\x0b\xec\x3e\xd3\ +\x36\xf6\x6c\xc8\x19\x11\x90\x4e\xa7\x1f\x09\x06\x83\x37\x27\x12\ +\x89\xea\x6c\x36\x4b\x24\x12\xa9\xec\xe2\x58\x0e\x66\xff\xff\x12\ +\x92\xe3\x4c\x76\x7d\x84\xe8\x79\x0b\x66\x1e\x76\xa4\x94\x18\xa6\ +\x41\x43\x43\x03\xe1\x60\x98\x8e\x1d\x1d\xee\xe8\xd8\xa8\xbd\x6d\ +\xfb\xb6\xff\x7e\xec\xb1\xc7\x5e\x9c\x63\x9a\x57\xdd\xc6\x9e\x0d\ +\x39\xa3\x93\xa1\x6b\xaf\xbd\x56\x2a\xa5\x7e\x60\x9a\x66\xba\xa7\ +\xa7\x87\xba\xba\x3a\x4c\xd3\x24\xbd\x63\x3f\x41\x35\x8c\x29\x5c\ +\xdc\xce\xa7\xf1\x3c\x0f\xcf\xf3\x90\x52\x12\xaf\x89\xb3\x78\xc9\ +\x62\xf2\x85\x3c\x8f\x6e\x7a\xd4\xdb\xdb\xb3\xb7\xe3\x86\x1b\x6f\ +\xf8\xca\x1c\xe0\xcb\xc0\x0e\xad\x75\xe7\x9b\x0d\x1e\x5e\xc5\xb6\ +\xf8\x86\x0d\x1b\x62\x9e\xe7\x8d\x16\x8b\xc5\xd8\xfa\xf5\xeb\x29\ +\x16\x8b\x24\xc6\x93\x58\x7d\x29\xe4\xc8\x00\x85\xb1\x71\xe4\xda\ +\x0b\x21\x50\x09\x83\x42\xa1\xe0\x64\xb3\x59\x1c\xc7\x29\x3e\xf7\ +\xdc\x73\x37\x3f\xf1\xc4\x13\xbb\xe7\x50\x3b\x40\xe5\xc9\xed\x35\ +\xb5\xb1\x67\x43\x5e\xd5\xb9\xc0\xbd\xf7\xde\xfb\xef\xae\xeb\x7e\ +\xa9\xb1\xb1\x31\x57\x53\x53\xb3\x62\x78\x78\x58\x67\x32\x19\xaf\ +\x58\x2c\xfa\xa6\xea\x73\xce\xb6\xed\xd1\xf1\xf1\xf1\xfd\xc9\x64\ +\x72\x68\x64\x64\x64\xa4\xbb\xbb\x7b\x30\x9b\xcd\x3a\xb3\x54\x9d\ +\x95\x36\xf6\x6c\xc8\xab\xda\x15\xb6\x6d\xfb\x66\x80\x91\x91\x91\ +\x45\xc9\x64\x72\x5b\x2e\x97\xdb\x27\xa5\xdc\xb7\x61\xc3\x06\x6f\ +\xc7\x8e\x1d\xb1\xd3\x0c\x97\x54\x3a\xb9\x24\x70\xe0\x6c\xb4\xb1\ +\x67\x43\xce\xca\xd9\xa0\x10\x22\x0a\xbc\x9f\xca\x8e\xa0\x0b\x58\ +\x27\xfc\xe5\x00\xeb\xcd\x28\x69\xaf\x45\xce\xda\xe1\xa8\x10\xa2\ +\x16\x28\x6b\xad\x4b\x67\x45\xe1\x9b\x24\xe7\xfc\x6f\x85\xdf\x68\ +\x39\x27\x7e\x27\x38\x9f\xf2\x16\x01\xf3\x6d\xc0\x7c\xcb\x5b\x04\ +\xcc\xb7\x01\xf3\x2d\x6f\x11\x30\xdf\x06\xcc\xb7\xfc\xd1\x13\xf0\ +\xff\xdb\x1a\x8f\x55\x76\x8a\x2b\x80\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ \x00\x00\x07\x8d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1778,6 +2070,10 @@ qt_resource_data = "\ " qt_resource_name = "\ +\x00\x04\ +\x00\x06\x98\x84\ +\x00\x63\ +\x00\x61\x00\x72\x00\x64\ \x00\x06\ \x07\x8b\x7a\xc2\ \x00\x72\ @@ -1817,16 +2113,17 @@ qt_resource_name = "\ " qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x01\ -\x00\x00\x00\x24\x00\x00\x00\x00\x00\x01\x00\x00\x07\x91\ -\x00\x00\x00\x12\x00\x02\x00\x00\x00\x05\x00\x00\x00\x05\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x38\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xd8\ -\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x68\xd3\ -\x00\x00\x00\x58\x00\x00\x00\x00\x00\x01\x00\x00\x59\x89\ -\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x68\x02\ -\x00\x00\x00\x70\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xa1\ -\x00\x00\x00\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x64\x5d\ +\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x19\xab\ +\x00\x00\x00\x20\x00\x02\x00\x00\x00\x05\x00\x00\x00\x06\ +\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x12\x1a\ +\x00\x00\x00\x46\x00\x00\x00\x00\x00\x01\x00\x00\x1f\xf2\ +\x00\x00\x00\xca\x00\x00\x00\x00\x00\x01\x00\x00\x7a\xed\ +\x00\x00\x00\x66\x00\x00\x00\x00\x00\x01\x00\x00\x6b\xa3\ +\x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x7a\x1c\ +\x00\x00\x00\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x6d\xbb\ +\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x76\x77\ " def qInitResources(): diff --git a/libprs500/gui/main.py b/libprs500/gui/main.py index a16f8a1cb9..5d12f90051 100644 --- a/libprs500/gui/main.py +++ b/libprs500/gui/main.py @@ -20,7 +20,7 @@ from database import LibraryDatabase from editbook import EditBookDialog from PyQt4.QtCore import Qt, SIGNAL -from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \ +from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QTreeWidgetItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \ QAbstractItemView, QImage, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage, QDialog, QSpinBox,\ QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\ qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical @@ -31,7 +31,7 @@ from tempfile import TemporaryFile, NamedTemporaryFile from exceptions import Exception as Exception import xml.dom.minidom as dom from xml.dom.ext import PrettyPrint as PrettyPrint -from operator import itemgetter +from operator import itemgetter, attrgetter from math import sin, cos, pi DEFAULT_BOOK_COVER = None @@ -309,18 +309,15 @@ class LibraryBooksModel(QAbstractTableModel): self.db.commit() class DeviceBooksModel(QAbstractTableModel): - TIME_READ_FMT = "%a, %d %b %Y %H:%M:%S %Z" - def __init__(self, parent, data): - QAbstractTableModel.__init__(self, parent) - self._data = data - self._orig_data = data - self.image_file = None - - def set_data(self, data): + def __init__(self, parent): + QAbstractTableModel.__init__(self, parent) + self._data = [] + self._orig_data = [] + + def set_data(self, book_list): self.emit(SIGNAL("layoutAboutToBeChanged()")) - self._data = data - self._orig_data = data - self.sort(0, Qt.DescendingOrder) + self._data = book_list + self._orig_data = book_list def rowCount(self, parent): return len(self._data) def columnCount(self, parent): return 4 @@ -341,10 +338,10 @@ class DeviceBooksModel(QAbstractTableModel): if role == Qt.DisplayRole: row, col = index.row(), index.column() book = self._data[row] - if col == 0: text = book["title"] - elif col == 1: text = book["author"] - elif col == 2: text = human_readable(int(book["size"])) - elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(book["date"], self.TIME_READ_FMT)) + if col == 0: text = wrap(book.title, width=40) + elif col == 1: text = re.sub("&\s*","\n", book.author) + elif col == 2: text = human_readable(book.size) + elif col == 3: text = time.strftime(TIME_WRITE_FMT, book.datetime) return QVariant(text) elif role == Qt.TextAlignmentRole and index.column() in [2,3]: return QVariant(Qt.AlignRight) @@ -353,7 +350,7 @@ class DeviceBooksModel(QAbstractTableModel): def info(self, row): row = self._data[row] try: - cover = row["thumbnail"] + cover = row.thumbnail pix = QPixmap() self.image_file = NamedTemporaryFile() self.image_file.write(cover) @@ -363,14 +360,14 @@ class DeviceBooksModel(QAbstractTableModel): except Exception, e: self.image_file = None cover = DEFAULT_BOOK_COVER - return row["title"], row["author"], human_readable(int(row["size"])), row["mime"], cover + return row.title, row.author, human_readable(row.size), row.mime, cover def sort(self, col, order): - def getter(key, func): return lambda x : func(itemgetter(key)(x)) + def getter(key, func): return lambda x : func(attrgetter(key)(x)) if col == 0: key, func = "title", string.lower - if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower() + if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower() if col == 2: key, func = "size", int - if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT)) + if col == 3: key, func = "datetime", lambda x: x descending = order != Qt.AscendingOrder self.emit(SIGNAL("layoutAboutToBeChanged()")) self._data.sort(key=getter(key, func)) @@ -385,7 +382,7 @@ class DeviceBooksModel(QAbstractTableModel): for book in self._orig_data: match = True for q in queries: - if q in book["title"].lower() or q in book["author"].lower(): continue + if q in book.title.lower() or q in book.author.lower(): continue else: match = False break @@ -425,43 +422,34 @@ class MainWindow(QObject, Ui_MainWindow): self.current_view = self.device_view else: self.device_view.hide(), self.library_view.show() - self.current_view = self.device_view + self.current_view = self.library_view + self.current_view.sortByColumn(3, Qt.DescendingOrder) - def tree_clicked(self, index): - show_device = self.show_device - item = self.tree.itemFromIndex(index) - text = str(item.text()) - if text == "Library": - show_device(False) - elif text == "SONY Reader": - show_device(True) - self.set_device_data(self.main_books + self.card_books) - elif text == "Main Memory": - show_device(True) - self.set_device_data(self.main_books) - elif text == "Storage Card": - show_device(True) - self.set_device_data(self.card_books) - elif text == "Books": - text = str(item.parent().text()) - if text == "Library": - show_device(False) - elif text == "Main Memory": - show_device(True) - self.set_device_data(self.main_books) - elif text == "Storage Card": - show_device(True) - self.set_data(self.card_books) + def tree_clicked(self, item, col): + if item: + text = str(item.text(0)) + if text == "Books": text = str(item.parent().text(0)) + if "Library" in text: + self.show_device(False) + elif "SONY Reader" in text: + self.device_view.setModel(self.reader_model) + QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book) + self.show_device(True) + elif "Storage Card" in text: + self.device_view.setModel(self.card_model) + QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book) + self.show_device(True) + def set_device_data(self, data): - self.device_model.set_data(data) + model.set_data(data) self.device_view.resizeColumnsToContents() def model_modified(self): if self.library_view.isVisible(): view = self.library_view else: view = self.device_view - view.clearSelection() + view.clearSelection() view.resizeColumnsToContents() self.book_cover.hide() self.book_info.hide() @@ -518,7 +506,7 @@ class MainWindow(QObject, Ui_MainWindow): else: self.cache_xml = file for path in paths: self.dev.del_file(path) - self.device_model.delete_by_path(path) + model.delete_by_path(path) self.cache_xml.seek(0) self.main_xml.seek(0) self.status("Files deleted. Updating media list on device") @@ -588,11 +576,11 @@ class MainWindow(QObject, Ui_MainWindow): def show_error(self, e, msg): QErrorMessage(self.window).showMessage(msg+"
Error: "+str(e)+"

Traceback:
"+traceback.format_exc(e)) - def __init__(self, window): + def __init__(self, window, log_packets): QObject.__init__(self) Ui_MainWindow.__init__(self) - self.dev = device(report_progress=self.progress) + self.dev = device(report_progress=self.progress, log_packets=log_packets) self.is_connected = False self.setupUi(window) self.card = None @@ -618,52 +606,58 @@ class MainWindow(QObject, Ui_MainWindow): QObject.connect(self.library_model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns) self.library_view.resizeColumnsToContents() - # Create Device list - self.tree = QStandardItemModel() - library = QStandardItem(QString("Library")) - library.setIcon(QIcon(":/library")) - font = library.font() - font.setBold(True) - self.tree.appendRow(library) - library.setFont(font) - library.appendRow(QStandardItem(QString("Books"))) - blank = QStandardItem(" ") - blank.setEnabled(False) - self.tree.appendRow(blank) - self.reader = QStandardItem(QString("SONY Reader")) - mm = QStandardItem(QString("Main Memory")) - mm.appendRow(QStandardItem(QString("Books"))) - self.reader.appendRow(mm) - mc = QStandardItem(QString("Storage Card")) - mc.appendRow(QStandardItem(QString("Books"))) - self.reader.appendRow(mc) - self.reader.setIcon(QIcon(":/reader")) - self.tree.appendRow(self.reader) - self.reader.setFont(font) - self.treeView.setModel(self.tree) - self.treeView.header().hide() - self.treeView.setExpanded(library.index(), True) - self.treeView.setExpanded(self.reader.index(), True) - self.treeView.setExpanded(mm.index(), True) - self.treeView.setExpanded(mc.index(), True) - self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True) - QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"), self.tree_clicked) - QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"), self.tree_clicked) + # Create Device tree + QObject.connect(self.device_tree, SIGNAL("itemClicked ( QTreeWidgetItem *, int )"), self.tree_clicked) + QObject.connect(self.device_tree, SIGNAL("itemActivated ( QTreeWidgetItem *, int )"), self.tree_clicked) + self.device_tree.header().hide() + library = QTreeWidgetItem(self.device_tree, QTreeWidgetItem.Type) + library.setData(0, Qt.DisplayRole, QVariant("Library")) + library.setData(0, Qt.DecorationRole, QVariant(QIcon(":/library"))) + books =QTreeWidgetItem(library, QTreeWidgetItem.Type) + books.setData(0, Qt.DisplayRole, QVariant("Books")) + self.device_tree.expandItem(library) + buffer = QTreeWidgetItem(self.device_tree, QTreeWidgetItem.Type) + buffer.setFlags(Qt.ItemFlags()) + library = QTreeWidgetItem(self.device_tree, QTreeWidgetItem.Type) + library.setData(0, Qt.DisplayRole, QVariant("SONY Reader")) + library.setData(0, Qt.DecorationRole, QVariant(QIcon(":/reader"))) + books =QTreeWidgetItem(library, QTreeWidgetItem.Type) + books.setData(0, Qt.DisplayRole, QVariant("Books")) + self.device_tree.expandItem(library) + buffer = QTreeWidgetItem(self.device_tree, QTreeWidgetItem.Type) + buffer.setFlags(Qt.ItemFlags()) + library = QTreeWidgetItem(self.device_tree, QTreeWidgetItem.Type) + library.setData(0, Qt.DisplayRole, QVariant("Storage Card")) + library.setData(0, Qt.DecorationRole, QVariant(QIcon(":/card"))) + books =QTreeWidgetItem(library, QTreeWidgetItem.Type) + books.setData(0, Qt.DisplayRole, QVariant("Books")) + self.device_tree.expandItem(library) + self.device_tree.reader = self.device_tree.topLevelItem(2) + self.device_tree.card = self.device_tree.topLevelItem(4) + def hider(i): + def do(s, x): s.topLevelItem(i).setHidden(x), s.topLevelItem(i+1).setHidden(x) + return do + self.device_tree.hide_reader = hider(1) + self.device_tree.hide_card = hider(3) + self.device_tree.hide_reader(self.device_tree, True) + self.device_tree.hide_card(self.device_tree, True) + # Create Device Book list - self.device_model = DeviceBooksModel(window, []) - self.device_view.setModel(self.device_model) + self.reader_model = DeviceBooksModel(window) + self.card_model = DeviceBooksModel(window) + self.device_view.setModel(self.reader_model) self.device_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.device_view.setSortingEnabled(True) self.device_view.contextMenuEvent = self.list_context_event - QObject.connect(self.device_model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents) QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book) - QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.device_model.search) - QObject.connect(self.device_model, SIGNAL("sorted()"), self.model_modified) - QObject.connect(self.device_model, SIGNAL("searched()"), self.model_modified) - QObject.connect(self.device_model, SIGNAL("deleted()"), self.model_modified) - QObject.connect(self.device_model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns) - self.device_view.hide() + for model in (self.reader_model, self. card_model): + QObject.connect(model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents) + QObject.connect(self.search, SIGNAL("textChanged(QString)"), model.search) + QObject.connect(model, SIGNAL("sorted()"), self.model_modified) + QObject.connect(model, SIGNAL("searched()"), self.model_modified) + QObject.connect(model, SIGNAL("deleted()"), self.model_modified) + QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns) # Setup book display self.BOOK_TEMPLATE = self.book_info.text() @@ -679,6 +673,7 @@ class MainWindow(QObject, Ui_MainWindow): self.device_detector = self.startTimer(1000) self.splitter.setStretchFactor(1,100) self.search.setFocus(Qt.OtherFocusReason) + self.show_device(False) window.show() def timerEvent(self, e): @@ -692,13 +687,13 @@ class MainWindow(QObject, Ui_MainWindow): def device_removed(self, timeout=False): """ @todo: only reset stuff if library is not shown """ self.is_connected = False - self.df.setText("Main memory:

Storage card:") - self.card = None - self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True) - self.device_model.set_data([]) + self.df.setText("SONY Reader:

Storage card:") + self.device_tree.hide_reader(self.device_tree, True) + self.device_tree.hide_card(self.device_tree, True) self.book_cover.hide() self.book_info.hide() self.device_view.hide() + self.library_view.show() def timeout_error(self): @@ -715,6 +710,7 @@ class MainWindow(QObject, Ui_MainWindow): self.progress_bar.setMaximum(100) self.progress_bar.reset() self.progress_bar.setFormat(msg + ": %p%") + self.progress(0) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def establish_connection(self): @@ -722,39 +718,38 @@ class MainWindow(QObject, Ui_MainWindow): self.status("Connecting to device") try: space = self.dev.available_space() - except TimeoutError: - c = 0 - self.status("Waiting for device to initialize") - while c < 100: # Delay for 10s while device is initializing - if c % 10 == c/10: - self.progress(c) - QThread.currentThread().msleep(100) - c += 1 - space = self.dev.available_space() + except DeviceError: + self.dev.reconnect() + return + except ProtocolError, e: + traceback.print_exc(e) + print >> sys.stderr, "Unable to connect device. Please try unplugiing and reconnecting it" + qFatal("Unable to connect device. Please try unplugiing and reconnecting it") + sc = space[1][1] if space[1][1] else space[2][1] - self.df.setText("Main memory: " + human_readable(space[0][1]) + "

Storage card: " + human_readable(sc)) + self.df.setText("SONY Reader: " + human_readable(space[0][1]) + "

Storage card: " + human_readable(sc)) self.is_connected = True - if space[1][2] > 0: self.card = "a:" - elif space[2][2] > 0: self.card = "b:" - else: self.card = None - if self.card: self.treeView.setRowHidden(1, self.reader.index(), False) - else: self.treeView.setRowHidden(1, self.reader.index(), True) - self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False) - self.status("Loading media list from device") - mb, cb, mx, cx = self.dev.books() - - for x in (mb, cb): - for book in x: - if "&" in book["author"]: - book["author"] = re.sub(r"&\s*", r"\n", book["author"]) - - self.main_books = mb - self.card_books = cb - self.main_xml = mx - self.cache_xml = cx + if space[1][2] > 0: card = "a:" + elif space[2][2] > 0: card = "b:" + else: card = None + if card: self.device_tree.hide_card(self.device_tree, False) + else: self.device_tree.hide_card(self.device_tree, True) + self.device_tree.hide_reader(self.device_tree, False) + self.status("Loading media list from SONY Reader") + self.reader_model.set_data(self.dev.books()) + if card: self.status("Loading media list from Storage Card") + self.card_model.set_data(self.dev.books(oncard=True)) + self.progress(100) self.window.setCursor(Qt.ArrowCursor) def main(): + from optparse import OptionParser + from libprs500 import __version__ as VERSION + parser = OptionParser(usage="usage: %prog [options]", version=VERSION) + parser.add_option("--log-packets", help="print out packet stream to stdout. "+\ + "The numbers in the left column are byte offsets that allow the packet size to be read off easily.", \ + dest="log_packets", action="store_true", default=False) + options, args = parser.parse_args() from PyQt4.Qt import QApplication, QMainWindow app = QApplication(sys.argv) global DEFAULT_BOOK_COVER @@ -771,7 +766,7 @@ def main(): handler = QErrorMessage.qtHandler() handler.resize(600, 400) handler.setModal(True) - gui = MainWindow(window) + gui = MainWindow(window, options.log_packets) ret = app.exec_() return ret diff --git a/libprs500/gui/main.ui b/libprs500/gui/main.ui index aa03ac7d31..cd4011a208 100644 --- a/libprs500/gui/main.ui +++ b/libprs500/gui/main.ui @@ -53,36 +53,30 @@ 6 - - - - 1 - 7 - 0 - 0 - - - - true - - - QFrame::Plain - - - Qt::ScrollBarAsNeeded - - - false + + + QAbstractItemView::DropOnly true - - 15 + + false - + true + + true + + + 1 + + + + 1 + + @@ -299,7 +293,7 @@ Add files to Library - a + A false @@ -313,7 +307,7 @@ Delete selected items - Delete + Del @@ -324,7 +318,7 @@ Edit meta-information for the currently selected items - e + E false diff --git a/libprs500/prstypes.py b/libprs500/prstypes.py index b297518a08..c64d7858fe 100755 --- a/libprs500/prstypes.py +++ b/libprs500/prstypes.py @@ -204,7 +204,7 @@ class Command(TransferBuffer): Command numbers are: 0 GetUsbProtocolVersion - 1 ReqUsbConnect + 1 ReqEndSession 10 FskFileOpen 11 FskFileClose @@ -343,11 +343,11 @@ class DirClose(ShortCommand): """ @param id: The identifier returned as a result of a L{DirOpen} command """ ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id) -class USBConnect(ShortCommand): +class EndSession(ShortCommand): """ Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """ NUMBER=0x1 #: Command number def __init__(self): - ShortCommand.__init__(self, number=USBConnect.NUMBER, type=0x01, command=0x00) + ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00) class GetUSBProtocolVersion(ShortCommand): """ Get USB Protocol version used by device """