mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05:00 
			
		
		
		
	lit2lrf
Basic GUI2 job control
This commit is contained in:
		
							parent
							
								
									6c63fc0ec7
								
							
						
					
					
						commit
						8989b541f1
					
				@ -13,7 +13,7 @@
 | 
				
			|||||||
##    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
					##    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
				
			||||||
##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
					##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
				
			||||||
''' E-book management software'''
 | 
					''' E-book management software'''
 | 
				
			||||||
__version__   = "0.3.54"
 | 
					__version__   = "0.3.55"
 | 
				
			||||||
__docformat__ = "epytext"
 | 
					__docformat__ = "epytext"
 | 
				
			||||||
__author__    = "Kovid Goyal <kovid@kovidgoyal.net>"
 | 
					__author__    = "Kovid Goyal <kovid@kovidgoyal.net>"
 | 
				
			||||||
__appname__   = 'libprs500'
 | 
					__appname__   = 'libprs500'
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,7 @@ class Device(object):
 | 
				
			|||||||
    # Ordered list of supported formats
 | 
					    # Ordered list of supported formats
 | 
				
			||||||
    FORMATS     = ["lrf", "rtf", "pdf", "txt"]
 | 
					    FORMATS     = ["lrf", "rtf", "pdf", "txt"]
 | 
				
			||||||
    VENDOR_ID   = 0x0000
 | 
					    VENDOR_ID   = 0x0000
 | 
				
			||||||
    PRODICT_ID  = 0x0000 
 | 
					    PRODUCT_ID  = 0x0000 
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def __init__(self, key='-1', log_packets=False, report_progress=None) :
 | 
					    def __init__(self, key='-1', log_packets=False, report_progress=None) :
 | 
				
			||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
@ -49,6 +49,15 @@ class Device(object):
 | 
				
			|||||||
        '''Return True iff the device is physically connected to the computer'''
 | 
					        '''Return True iff the device is physically connected to the computer'''
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    def set_progress_reporter(self, report_progress):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        @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
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def get_device_information(self, end_session=True):
 | 
					    def get_device_information(self, end_session=True):
 | 
				
			||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
        Ask device for device information. See L{DeviceInfoQuery}. 
 | 
					        Ask device for device information. See L{DeviceInfoQuery}. 
 | 
				
			||||||
@ -56,6 +65,12 @@ class Device(object):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    def card_prefix(self, end_session=True):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Return prefix to paths on the card or None if no cards present.
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def total_space(self, end_session=True):
 | 
					    def total_space(self, end_session=True):
 | 
				
			||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
        Get total space available on the mountpoints:
 | 
					        Get total space available on the mountpoints:
 | 
				
			||||||
@ -72,11 +87,11 @@ class Device(object):
 | 
				
			|||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
        Get free space available on the mountpoints:
 | 
					        Get free space available on the mountpoints:
 | 
				
			||||||
          1. Main memory
 | 
					          1. Main memory
 | 
				
			||||||
          2. Memory Stick
 | 
					          2. Card A
 | 
				
			||||||
          3. SD Card
 | 
					          3. Card B
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @return: A 3 element list with free space in bytes of (1, 2, 3). If a
 | 
					        @return: A 3 element list with free space in bytes of (1, 2, 3). If a
 | 
				
			||||||
        particular device doesn't have any of these locations it should return 0.
 | 
					        particular device doesn't have any of these locations it should return -1.
 | 
				
			||||||
        """    
 | 
					        """    
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -84,8 +99,8 @@ class Device(object):
 | 
				
			|||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
        Return a list of ebooks on the device.
 | 
					        Return a list of ebooks on the device.
 | 
				
			||||||
        @param oncard: If True return a list of ebooks on the storage card, 
 | 
					        @param oncard: If True return a list of ebooks on the storage card, 
 | 
				
			||||||
                            otherwise return list of ebooks in main memory of device
 | 
					                       otherwise return list of ebooks in main memory of device.
 | 
				
			||||||
 | 
					                       If True and no books on card return empty list. 
 | 
				
			||||||
        @return: A list of Books. Each Book object must have the fields:
 | 
					        @return: A list of Books. Each Book object must have the fields:
 | 
				
			||||||
        title, author, size, datetime (a time tuple), path, thumbnail (can be None).
 | 
					        title, author, size, datetime (a time tuple), path, thumbnail (can be None).
 | 
				
			||||||
        """    
 | 
					        """    
 | 
				
			||||||
 | 
				
			|||||||
@ -224,6 +224,8 @@ class PRS500(Device):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return get_device_by_id(cls.VENDOR_ID, cls.PRODUCT_ID) != None
 | 
					        return get_device_by_id(cls.VENDOR_ID, cls.PRODUCT_ID) != None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_progress_reporter(self, report_progress):
 | 
				
			||||||
 | 
					        self.report_progress = report_progress
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def open(self) :
 | 
					    def open(self) :
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -585,6 +587,21 @@ class PRS500(Device):
 | 
				
			|||||||
            data.append( pkt.total )    
 | 
					            data.append( pkt.total )    
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @safe
 | 
				
			||||||
 | 
					    def card_prefix(self, end_session=True):
 | 
				
			||||||
 | 
					        '''Return prefix of path to card or None if no cards present'''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            path = 'a:/'
 | 
				
			||||||
 | 
					            self.path_properties(path, end_session=False)
 | 
				
			||||||
 | 
					            return path
 | 
				
			||||||
 | 
					        except PathError:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                path = 'b:/'
 | 
				
			||||||
 | 
					                self.path_properties(path, end_session=False)
 | 
				
			||||||
 | 
					                return path
 | 
				
			||||||
 | 
					            except PathError:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    @safe
 | 
					    @safe
 | 
				
			||||||
    def free_space(self, end_session=True):
 | 
					    def free_space(self, end_session=True):
 | 
				
			||||||
        """ 
 | 
					        """ 
 | 
				
			||||||
 | 
				
			|||||||
@ -91,7 +91,7 @@ def option_parser(usage):
 | 
				
			|||||||
                      ''' Supported profiles: '''+', '.join(profiles))
 | 
					                      ''' Supported profiles: '''+', '.join(profiles))
 | 
				
			||||||
    page.add_option('--left-margin', default=20, dest='left_margin', type='int',
 | 
					    page.add_option('--left-margin', default=20, dest='left_margin', type='int',
 | 
				
			||||||
                    help='''Left margin of page. Default is %default px.''')
 | 
					                    help='''Left margin of page. Default is %default px.''')
 | 
				
			||||||
    page.add_option('--right-margin', default=5, dest='right_margin', type='int',
 | 
					    page.add_option('--right-margin', default=20, dest='right_margin', type='int',
 | 
				
			||||||
                    help='''Right margin of page. Default is %default px.''')
 | 
					                    help='''Right margin of page. Default is %default px.''')
 | 
				
			||||||
    page.add_option('--top-margin', default=10, dest='top_margin', type='int',
 | 
					    page.add_option('--top-margin', default=10, dest='top_margin', type='int',
 | 
				
			||||||
                    help='''Top margin of page. Default is %default px.''')
 | 
					                    help='''Top margin of page. Default is %default px.''')
 | 
				
			||||||
 | 
				
			|||||||
@ -113,15 +113,15 @@ class Span(_Span):
 | 
				
			|||||||
            else:
 | 
					            else:
 | 
				
			||||||
                if "xx-small" in val:
 | 
					                if "xx-small" in val:
 | 
				
			||||||
                    ans = 40
 | 
					                    ans = 40
 | 
				
			||||||
                elif "x-small" in val >= 0:
 | 
					                elif "x-small" in val:
 | 
				
			||||||
                    ans = 60
 | 
					                    ans = 60
 | 
				
			||||||
                elif "small" in val:
 | 
					                elif "small" in val:
 | 
				
			||||||
                    ans = 80
 | 
					                    ans = 80
 | 
				
			||||||
                elif "xx-large" in val:
 | 
					                elif "xx-large" in val:
 | 
				
			||||||
                    ans = 180
 | 
					                    ans = 180
 | 
				
			||||||
                elif "x-large" in val >= 0:
 | 
					                elif "x-large" in val:
 | 
				
			||||||
                    ans = 140
 | 
					                    ans = 140
 | 
				
			||||||
                elif "large" in val >= 0:
 | 
					                elif "large" in val:
 | 
				
			||||||
                    ans = 120
 | 
					                    ans = 120
 | 
				
			||||||
            if ans is not None: 
 | 
					            if ans is not None: 
 | 
				
			||||||
                ans += int(font_delta * 20)
 | 
					                ans += int(font_delta * 20)
 | 
				
			||||||
@ -262,20 +262,21 @@ class HTMLConverter(object):
 | 
				
			|||||||
        '''
 | 
					        '''
 | 
				
			||||||
        # Defaults for various formatting tags        
 | 
					        # Defaults for various formatting tags        
 | 
				
			||||||
        self.css = dict(
 | 
					        self.css = dict(
 | 
				
			||||||
            h1     = {"font-size"   :"xx-large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
					            h1     = {"font-size"   : "xx-large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
				
			||||||
            h2     = {"font-size"   :"x-large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
					            h2     = {"font-size"   : "x-large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
				
			||||||
            h3     = {"font-size"   :"large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
					            h3     = {"font-size"   : "large", "font-weight":"bold", 'text-indent':'0pt'},
 | 
				
			||||||
            h4     = {"font-size"   :"large", 'text-indent':'0pt'},
 | 
					            h4     = {"font-size"   : "large", 'text-indent':'0pt'},
 | 
				
			||||||
            h5     = {"font-weight" :"bold", 'text-indent':'0pt'},
 | 
					            h5     = {"font-weight" : "bold", 'text-indent':'0pt'},
 | 
				
			||||||
            b      = {"font-weight" :"bold"},
 | 
					            b      = {"font-weight" : "bold"},
 | 
				
			||||||
            strong = {"font-weight" :"bold"},
 | 
					            strong = {"font-weight" : "bold"},
 | 
				
			||||||
            i      = {"font-style"  :"italic"},
 | 
					            i      = {"font-style"  : "italic"},
 | 
				
			||||||
            em     = {"font-style"  :"italic"},
 | 
					            em     = {"font-style"  : "italic"},
 | 
				
			||||||
            small  = {'font-size'   :'small'},
 | 
					            small  = {'font-size'   : 'small'},
 | 
				
			||||||
            pre    = {'font-family' :'monospace' },
 | 
					            pre    = {'font-family' : 'monospace' },
 | 
				
			||||||
            tt     = {'font-family' :'monospace'},
 | 
					            tt     = {'font-family' : 'monospace'},
 | 
				
			||||||
            center = {'text-align'  : 'center'},
 | 
					            center = {'text-align'  : 'center'},
 | 
				
			||||||
            th     = {'font-size':'large', 'font-weight':'bold'},
 | 
					            th     = {'font-size'   : 'large', 'font-weight':'bold'},
 | 
				
			||||||
 | 
					            big    = {'font-size'   : 'large', 'font-weight':'bold'},
 | 
				
			||||||
            )        
 | 
					            )        
 | 
				
			||||||
        self.profile     = profile #: Defines the geometry of the display device
 | 
					        self.profile     = profile #: Defines the geometry of the display device
 | 
				
			||||||
        self.chapter_detection = chapter_detection #: Flag to toggle chapter detection
 | 
					        self.chapter_detection = chapter_detection #: Flag to toggle chapter detection
 | 
				
			||||||
@ -389,6 +390,8 @@ class HTMLConverter(object):
 | 
				
			|||||||
            prop.update(temp)
 | 
					            prop.update(temp)
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
        prop = dict()
 | 
					        prop = dict()
 | 
				
			||||||
 | 
					        if parent_css:
 | 
				
			||||||
 | 
					            merge_parent_css(prop, parent_css)
 | 
				
			||||||
        if tag.has_key("align"):
 | 
					        if tag.has_key("align"):
 | 
				
			||||||
            prop["text-align"] = tag["align"]
 | 
					            prop["text-align"] = tag["align"]
 | 
				
			||||||
        if self.css.has_key(tag.name):
 | 
					        if self.css.has_key(tag.name):
 | 
				
			||||||
@ -398,8 +401,6 @@ class HTMLConverter(object):
 | 
				
			|||||||
            for classname in ["."+cls, tag.name+"."+cls]:
 | 
					            for classname in ["."+cls, tag.name+"."+cls]:
 | 
				
			||||||
                if self.css.has_key(classname):
 | 
					                if self.css.has_key(classname):
 | 
				
			||||||
                    prop.update(self.css[classname])
 | 
					                    prop.update(self.css[classname])
 | 
				
			||||||
        if parent_css:
 | 
					 | 
				
			||||||
            merge_parent_css(prop, parent_css)
 | 
					 | 
				
			||||||
        if tag.has_key("style"):
 | 
					        if tag.has_key("style"):
 | 
				
			||||||
            prop.update(self.parse_style_properties(tag["style"]))    
 | 
					            prop.update(self.parse_style_properties(tag["style"]))    
 | 
				
			||||||
        return prop
 | 
					        return prop
 | 
				
			||||||
@ -1043,7 +1044,7 @@ class HTMLConverter(object):
 | 
				
			|||||||
            self.end_current_para()
 | 
					            self.end_current_para()
 | 
				
			||||||
            if tagname.startswith('h'):
 | 
					            if tagname.startswith('h'):
 | 
				
			||||||
                self.current_block.append(CR())
 | 
					                self.current_block.append(CR())
 | 
				
			||||||
        elif tagname in ['b', 'strong', 'i', 'em', 'span', 'tt']:
 | 
					        elif tagname in ['b', 'strong', 'i', 'em', 'span', 'tt', 'big']:
 | 
				
			||||||
            self.process_children(tag, tag_css)
 | 
					            self.process_children(tag, tag_css)
 | 
				
			||||||
        elif tagname == 'font':
 | 
					        elif tagname == 'font':
 | 
				
			||||||
            if tag.has_key('face'):
 | 
					            if tag.has_key('face'):
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								src/libprs500/ebooks/lrf/lit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/libprs500/ebooks/lrf/lit/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
 | 
				
			||||||
 | 
					##    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/libprs500/ebooks/lrf/lit/convert_from.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/libprs500/ebooks/lrf/lit/convert_from.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
 | 
				
			||||||
 | 
					##    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.
 | 
				
			||||||
 | 
					import os, sys, shutil, glob
 | 
				
			||||||
 | 
					from tempfile import mkdtemp
 | 
				
			||||||
 | 
					from subprocess import Popen, PIPE
 | 
				
			||||||
 | 
					from libprs500.ebooks.lrf import option_parser, ConversionError
 | 
				
			||||||
 | 
					from libprs500.ebooks.lrf.html.convert_from import parse_options as html_parse_options
 | 
				
			||||||
 | 
					from libprs500.ebooks.lrf.html.convert_from import process_file
 | 
				
			||||||
 | 
					from libprs500 import isosx
 | 
				
			||||||
 | 
					CLIT = 'clit'
 | 
				
			||||||
 | 
					if isosx and hasattr(sys, 'frameworks_dir'):
 | 
				
			||||||
 | 
					    CLIT = os.path.join(sys.frameworks_dir, CLIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_options(cli=True):
 | 
				
			||||||
 | 
					    """ CLI for lit -> lrf conversions """
 | 
				
			||||||
 | 
					    parser = option_parser(
 | 
				
			||||||
 | 
					        """usage: %prog [options] mybook.lit
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        %prog converts mybook.lit to mybook.lrf
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    options, args = parser.parse_args()
 | 
				
			||||||
 | 
					    if len(args) != 1:
 | 
				
			||||||
 | 
					        if cli:
 | 
				
			||||||
 | 
					            parser.print_help()
 | 
				
			||||||
 | 
					        raise ConversionError, 'no filename specified'
 | 
				
			||||||
 | 
					    return options, args, parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_html(pathtolit):
 | 
				
			||||||
 | 
					    if not os.access(pathtolit, os.R_OK):
 | 
				
			||||||
 | 
					        raise ConversionError, 'Cannot read from ' + pathtolit
 | 
				
			||||||
 | 
					    tdir = mkdtemp(prefix='libprs500_lit2lrf_')
 | 
				
			||||||
 | 
					    cmd = ' '.join([CLIT, '"'+pathtolit+'"', tdir])
 | 
				
			||||||
 | 
					    p = Popen(cmd, shell=True, stderr=PIPE)
 | 
				
			||||||
 | 
					    ret = p.wait()
 | 
				
			||||||
 | 
					    if ret != 0:
 | 
				
			||||||
 | 
					        shutil.rmtree(tdir)
 | 
				
			||||||
 | 
					        err = p.stderr.read()
 | 
				
			||||||
 | 
					        raise ConversionError, err
 | 
				
			||||||
 | 
					    return tdir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        options, args, parser = parse_options()
 | 
				
			||||||
 | 
					        lit = os.path.abspath(os.path.expanduser(args[0]))        
 | 
				
			||||||
 | 
					        tdir = generate_html(lit)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            l = glob.glob(os.path.join(tdir, '*toc*.htm*'))
 | 
				
			||||||
 | 
					            if not l:
 | 
				
			||||||
 | 
					                l = glob.glob(os.path.join(tdir, '*top*.htm*'))
 | 
				
			||||||
 | 
					            if not l:
 | 
				
			||||||
 | 
					                raise ConversionError, 'Conversion of lit to html failed.'
 | 
				
			||||||
 | 
					            htmlfile = l[0]
 | 
				
			||||||
 | 
					            for i in range(1, len(sys.argv)):
 | 
				
			||||||
 | 
					                if sys.argv[i] == args[0]:
 | 
				
			||||||
 | 
					                    sys.argv.remove(sys.argv[i])
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            sys.argv.append(htmlfile)
 | 
				
			||||||
 | 
					            o_spec = False
 | 
				
			||||||
 | 
					            for arg in sys.argv[1:]:
 | 
				
			||||||
 | 
					                arg = arg.lstrip()
 | 
				
			||||||
 | 
					                if arg.startswith('-o') or arg.startswith('--output'):
 | 
				
			||||||
 | 
					                    o_spec = True
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            ext = '.lrf'
 | 
				
			||||||
 | 
					            for arg in sys.argv[1:]:
 | 
				
			||||||
 | 
					                if arg.strip() == '--lrs':
 | 
				
			||||||
 | 
					                    ext = '.lrs'
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            if not o_spec:
 | 
				
			||||||
 | 
					                sys.argv.append('-o')
 | 
				
			||||||
 | 
					                sys.argv.append(os.path.splitext(os.path.basename(lit))[0]+ext)
 | 
				
			||||||
 | 
					            options, args, parser = html_parse_options(parser=parser)
 | 
				
			||||||
 | 
					            process_file(htmlfile, options)         
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            shutil.rmtree(tdir)
 | 
				
			||||||
 | 
					    except ConversionError, err:
 | 
				
			||||||
 | 
					        print >>sys.stderr, err
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
@ -15,13 +15,10 @@
 | 
				
			|||||||
""" The GUI for libprs500. """
 | 
					""" The GUI for libprs500. """
 | 
				
			||||||
import sys, os, re, StringIO, traceback
 | 
					import sys, os, re, StringIO, traceback
 | 
				
			||||||
from PyQt4.QtCore import QVariant
 | 
					from PyQt4.QtCore import QVariant
 | 
				
			||||||
 | 
					from libprs500 import __appname__ as APP_TITLE
 | 
				
			||||||
__docformat__ = "epytext"
 | 
					from libprs500 import __author__
 | 
				
			||||||
__author__    = "Kovid Goyal <kovid@kovidgoyal.net>"
 | 
					 | 
				
			||||||
APP_TITLE     = "libprs500"
 | 
					 | 
				
			||||||
NONE = QVariant() #: Null value to return from the data function of item models
 | 
					NONE = QVariant() #: Null value to return from the data function of item models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
error_dialog = None
 | 
					error_dialog = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extension(path):
 | 
					def extension(path):
 | 
				
			||||||
@ -50,3 +47,18 @@ def Error(msg, e):
 | 
				
			|||||||
        error_dialog.showMessage(msg)
 | 
					        error_dialog.showMessage(msg)
 | 
				
			||||||
        error_dialog.show()
 | 
					        error_dialog.show()
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					def human_readable(cls, size):
 | 
				
			||||||
 | 
					    """ Convert a size in bytes into a human readable form """
 | 
				
			||||||
 | 
					    if size < 1024: 
 | 
				
			||||||
 | 
					        divisor, suffix = 1, "B"
 | 
				
			||||||
 | 
					    elif size < 1024*1024: 
 | 
				
			||||||
 | 
					        divisor, suffix = 1024., "KB"
 | 
				
			||||||
 | 
					    elif size < 1024*1024*1024: 
 | 
				
			||||||
 | 
					        divisor, suffix = 1024*1024, "MB"
 | 
				
			||||||
 | 
					    elif size < 1024*1024*1024*1024: 
 | 
				
			||||||
 | 
					        divisor, suffix = 1024*1024, "GB"
 | 
				
			||||||
 | 
					    size = str(size/divisor)
 | 
				
			||||||
 | 
					    if size.find(".") > -1: 
 | 
				
			||||||
 | 
					        size = size[:size.find(".")+2]
 | 
				
			||||||
 | 
					    return size + " " + suffix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,11 +12,18 @@
 | 
				
			|||||||
##    You should have received a copy of the GNU General Public License along
 | 
					##    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.,
 | 
					##    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
				
			||||||
##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
 | 
					##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
 | 
				
			||||||
from PyQt4.QtCore import QThread, SIGNAL
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from PyQt4.QtCore import QThread, SIGNAL, QObject, Qt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from libprs500.devices.prs500.driver import PRS500
 | 
					from libprs500.devices.prs500.driver import PRS500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DeviceDetector(QThread):
 | 
					class DeviceDetector(QThread):
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Worker thread that polls the USB ports for devices. Emits the
 | 
				
			||||||
 | 
					    signal connected(PyQt_PyObject, PyQt_PyObject) on connection and
 | 
				
			||||||
 | 
					    disconnection events.
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
    def __init__(self, sleep_time=2000):
 | 
					    def __init__(self, sleep_time=2000):
 | 
				
			||||||
        '''        
 | 
					        '''        
 | 
				
			||||||
        @param sleep_time: Time to sleep between device probes in millisecs
 | 
					        @param sleep_time: Time to sleep between device probes in millisecs
 | 
				
			||||||
@ -37,3 +44,64 @@ class DeviceDetector(QThread):
 | 
				
			|||||||
                    self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), device[0], False)
 | 
					                    self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), device[0], False)
 | 
				
			||||||
                    device[1] ^= True
 | 
					                    device[1] ^= True
 | 
				
			||||||
            self.msleep(self.sleep_time)
 | 
					            self.msleep(self.sleep_time)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					class DeviceJob(QThread):
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Worker thread that communicates with device.
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    def __init__(self, id, mutex, func, *args, **kwargs):
 | 
				
			||||||
 | 
					        QThread.__init__(self)
 | 
				
			||||||
 | 
					        self.id = id
 | 
				
			||||||
 | 
					        self.func = func
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
 | 
					        self.kwargs = kwargs
 | 
				
			||||||
 | 
					        self.mutex = mutex
 | 
				
			||||||
 | 
					        self.result = None
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        if self.mutex != None:
 | 
				
			||||||
 | 
					            self.mutex.lock()
 | 
				
			||||||
 | 
					        last_traceback, exception = None, None
 | 
				
			||||||
 | 
					        try:            
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self.result = self.func(self.progress_update, *self.args, **self.kwargs)
 | 
				
			||||||
 | 
					            except Exception, err:
 | 
				
			||||||
 | 
					                exception = err
 | 
				
			||||||
 | 
					                last_traceback = traceback.format_exc()            
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            if self.mutex != None:
 | 
				
			||||||
 | 
					                self.mutex.unlock()
 | 
				
			||||||
 | 
					            self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), 
 | 
				
			||||||
 | 
					                      self.id, self.result, exception, last_traceback)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					    def progress_update(self, val):
 | 
				
			||||||
 | 
					        print val
 | 
				
			||||||
 | 
					        self.emit(SIGNAL('status_update(int)'), int(val), Qt.QueuedConnection)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					class DeviceManager(QObject):
 | 
				
			||||||
 | 
					    def __init__(self, device_class):
 | 
				
			||||||
 | 
					        QObject.__init__(self)
 | 
				
			||||||
 | 
					        self.device_class = device_class
 | 
				
			||||||
 | 
					        self.device = device_class()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def get_info_func(self):
 | 
				
			||||||
 | 
					        ''' Return callable that returns device information and free space on device'''
 | 
				
			||||||
 | 
					        def get_device_information(updater):
 | 
				
			||||||
 | 
					            self.device.set_updater(updater)
 | 
				
			||||||
 | 
					            info = self.device.get_device_information(end_session=False)
 | 
				
			||||||
 | 
					            info = {'name':info[0], 'version':info[1], 'swversion':[2], 'mimetype':info[3]}
 | 
				
			||||||
 | 
					            cp = self.device.card_prefix(end_session=False)
 | 
				
			||||||
 | 
					            fs = self.device.free_space()
 | 
				
			||||||
 | 
					            fs = {'main':fs[0], 'carda':fs[1], 'cardb':fs[2]}
 | 
				
			||||||
 | 
					            return info, cp, fs
 | 
				
			||||||
 | 
					        return get_device_information
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def books_func(self):
 | 
				
			||||||
 | 
					        '''Return callable that returns the list of books on device as two booklists'''
 | 
				
			||||||
 | 
					        def books(updater):
 | 
				
			||||||
 | 
					            self.device.set_updater(updater)
 | 
				
			||||||
 | 
					            mainlist = self.device.books(oncard=False, end_session=False)
 | 
				
			||||||
 | 
					            cardlist = self.device.books(oncard=True)
 | 
				
			||||||
 | 
					            return mainlist, cardlist
 | 
				
			||||||
 | 
					        return books
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/jobs-animated.mng
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/jobs-animated.mng
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										84
									
								
								src/libprs500/gui2/jobs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/libprs500/gui2/jobs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
 | 
				
			||||||
 | 
					##    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.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from libprs500.gui2.device import DeviceJob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class JobException(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class JobManager(QAbstractTableModel):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        QAbstractTableModel.__init__(self)
 | 
				
			||||||
 | 
					        self.jobs = {}
 | 
				
			||||||
 | 
					        self.next_id = 0
 | 
				
			||||||
 | 
					        self.job_create_lock = QMutex()
 | 
				
			||||||
 | 
					        self.job_remove_lock = QMutex()
 | 
				
			||||||
 | 
					        self.device_lock = QMutex()
 | 
				
			||||||
 | 
					        self.cleanup_lock = QMutex()
 | 
				
			||||||
 | 
					        self.cleanup = {}
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def create_job(self, job_class, lock, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.job_create_lock.lock()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.next_id += 1
 | 
				
			||||||
 | 
					            job = job_class(self.next_id, lock, *args, **kwargs)
 | 
				
			||||||
 | 
					            QObject.connect(job, SIGNAL('finished()'), self.cleanup_jobs)
 | 
				
			||||||
 | 
					            self.jobs[self.next_id] = job
 | 
				
			||||||
 | 
					            return job
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.job_create_lock.unlock()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def run_device_job(self, slot, callable, *args, **kwargs):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Run a job to communicate with the device.
 | 
				
			||||||
 | 
					        @param slot: The function to call with the job result. It is called with
 | 
				
			||||||
 | 
					        the parameters id, result, exception, formatted_traceback
 | 
				
			||||||
 | 
					        @param callable: The function to call to communicate with the device.
 | 
				
			||||||
 | 
					        @param args: The arguments to pass to callable
 | 
				
			||||||
 | 
					        @param kwargs: The keyword arguments to pass to callable
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        job = self.create_job(DeviceJob, self.device_lock, callable, *args, **kwargs)        
 | 
				
			||||||
 | 
					        QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
 | 
				
			||||||
 | 
					                        self.job_done)
 | 
				
			||||||
 | 
					        QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
 | 
				
			||||||
 | 
					                        slot)
 | 
				
			||||||
 | 
					        job.start()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def job_done(self, id, *args, **kwargs):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Slot that is called when a job is completed.
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        self.job_remove_lock.lock()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            job = self.jobs.pop(id)
 | 
				
			||||||
 | 
					            self.cleanup_lock.lock()
 | 
				
			||||||
 | 
					            self.cleanup[id] = job            
 | 
				
			||||||
 | 
					            self.cleanup_lock.unlock()
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.job_remove_lock.unlock()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def cleanup_jobs(self):
 | 
				
			||||||
 | 
					        self.cleanup_lock.lock()
 | 
				
			||||||
 | 
					        toast = []
 | 
				
			||||||
 | 
					        for id in self.cleanup.keys():
 | 
				
			||||||
 | 
					            if not self.cleanup[id].isRunning():
 | 
				
			||||||
 | 
					                toast.append(id)
 | 
				
			||||||
 | 
					        for id in toast:
 | 
				
			||||||
 | 
					            self.cleanup.pop(id)
 | 
				
			||||||
 | 
					        self.cleanup_lock.unlock()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
@ -21,10 +21,12 @@ from PyQt4.QtGui import QPixmap, QErrorMessage, QLineEdit, \
 | 
				
			|||||||
                        QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
 | 
					                        QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
 | 
				
			||||||
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
 | 
					from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from libprs500 import __version__ as VERSION
 | 
				
			||||||
from libprs500.gui2 import APP_TITLE, installErrorHandler
 | 
					from libprs500.gui2 import APP_TITLE, installErrorHandler
 | 
				
			||||||
from libprs500.gui2.main_ui import Ui_MainWindow
 | 
					from libprs500.gui2.main_ui import Ui_MainWindow
 | 
				
			||||||
from libprs500.gui2.device import DeviceDetector
 | 
					from libprs500.gui2.device import DeviceDetector, DeviceManager
 | 
				
			||||||
from libprs500.gui2.status import StatusBar
 | 
					from libprs500.gui2.status import StatusBar
 | 
				
			||||||
 | 
					from libprs500.gui2.jobs import JobManager, JobException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Main(QObject, Ui_MainWindow):
 | 
					class Main(QObject, Ui_MainWindow):
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -34,6 +36,10 @@ class Main(QObject, Ui_MainWindow):
 | 
				
			|||||||
        self.window = window
 | 
					        self.window = window
 | 
				
			||||||
        self.setupUi(window)
 | 
					        self.setupUi(window)
 | 
				
			||||||
        self.read_settings()
 | 
					        self.read_settings()
 | 
				
			||||||
 | 
					        self.job_manager = JobManager()
 | 
				
			||||||
 | 
					        self.device_manager = None
 | 
				
			||||||
 | 
					        self.temporary_slots = {}
 | 
				
			||||||
 | 
					        self.df.setText(self.df.text().arg(VERSION))
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        ####################### Status Bar #####################
 | 
					        ####################### Status Bar #####################
 | 
				
			||||||
        self.status_bar = StatusBar()
 | 
					        self.status_bar = StatusBar()
 | 
				
			||||||
@ -54,13 +60,23 @@ class Main(QObject, Ui_MainWindow):
 | 
				
			|||||||
        ####################### Setup device detection ########################
 | 
					        ####################### Setup device detection ########################
 | 
				
			||||||
        self.detector = DeviceDetector(sleep_time=2000)
 | 
					        self.detector = DeviceDetector(sleep_time=2000)
 | 
				
			||||||
        QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), 
 | 
					        QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), 
 | 
				
			||||||
                        self.device_connected, Qt.QueuedConnection)
 | 
					                        self.device_detected, Qt.QueuedConnection)
 | 
				
			||||||
        self.detector.start(QThread.InheritPriority)
 | 
					        self.detector.start(QThread.InheritPriority)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    def device_connected(self, cls, connected):
 | 
					    def device_detected(self, cls, connected):
 | 
				
			||||||
        print cls, connected
 | 
					        if connected:
 | 
				
			||||||
 | 
					            def info_read(id, result, exception, formatted_traceback):
 | 
				
			||||||
 | 
					                if exception:
 | 
				
			||||||
 | 
					                    pass #TODO: Handle error
 | 
				
			||||||
 | 
					                info, cp, fs = result
 | 
				
			||||||
 | 
					                print self, id, result, exception, formatted_traceback
 | 
				
			||||||
 | 
					            self.temporary_slots['device_info_read'] = info_read
 | 
				
			||||||
 | 
					            self.device_manager = DeviceManager(cls)
 | 
				
			||||||
 | 
					            func = self.device_manager.get_info_func()
 | 
				
			||||||
 | 
					            self.job_manager.run_device_job(info_read, func)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def read_settings(self):
 | 
					    def read_settings(self):
 | 
				
			||||||
        settings = QSettings()
 | 
					        settings = QSettings()
 | 
				
			||||||
@ -95,8 +111,16 @@ def main():
 | 
				
			|||||||
    installErrorHandler(QErrorMessage(window))
 | 
					    installErrorHandler(QErrorMessage(window))
 | 
				
			||||||
    QCoreApplication.setOrganizationName("KovidsBrain")
 | 
					    QCoreApplication.setOrganizationName("KovidsBrain")
 | 
				
			||||||
    QCoreApplication.setApplicationName(APP_TITLE)
 | 
					    QCoreApplication.setApplicationName(APP_TITLE)
 | 
				
			||||||
    Main(window) 
 | 
					    main = Main(window)
 | 
				
			||||||
 | 
					    def unhandled_exception(type, value, tb):
 | 
				
			||||||
 | 
					        import traceback
 | 
				
			||||||
 | 
					        traceback.print_exception(type, value, tb, file=sys.stderr)
 | 
				
			||||||
 | 
					        if type == KeyboardInterrupt:
 | 
				
			||||||
 | 
					            QCoreApplication.exit(1)
 | 
				
			||||||
 | 
					    sys.excepthook = unhandled_exception
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return app.exec_()
 | 
					    return app.exec_()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    main()
 | 
					    main()
 | 
				
			||||||
@ -42,7 +42,7 @@
 | 
				
			|||||||
       <number>0</number>
 | 
					       <number>0</number>
 | 
				
			||||||
      </property>
 | 
					      </property>
 | 
				
			||||||
      <item>
 | 
					      <item>
 | 
				
			||||||
       <widget class="DeviceView" name="device_tree" >
 | 
					       <widget class="LocationView" name="device_tree" >
 | 
				
			||||||
        <property name="sizePolicy" >
 | 
					        <property name="sizePolicy" >
 | 
				
			||||||
         <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
 | 
					         <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
 | 
				
			||||||
          <horstretch>0</horstretch>
 | 
					          <horstretch>0</horstretch>
 | 
				
			||||||
@ -90,7 +90,7 @@
 | 
				
			|||||||
         </size>
 | 
					         </size>
 | 
				
			||||||
        </property>
 | 
					        </property>
 | 
				
			||||||
        <property name="text" >
 | 
					        <property name="text" >
 | 
				
			||||||
         <string>For help visit <a href="https://libprs500.kovidgoyal.net/wiki/GuiUsage">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> &copy; 2006<br>%2 %3 %4</string>
 | 
					         <string>For help visit <a href="https://libprs500.kovidgoyal.net/wiki/GuiUsage">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> &copy; 2007<br>%2 %3 %4</string>
 | 
				
			||||||
        </property>
 | 
					        </property>
 | 
				
			||||||
        <property name="textFormat" >
 | 
					        <property name="textFormat" >
 | 
				
			||||||
         <enum>Qt::RichText</enum>
 | 
					         <enum>Qt::RichText</enum>
 | 
				
			||||||
@ -332,11 +332,6 @@
 | 
				
			|||||||
  </action>
 | 
					  </action>
 | 
				
			||||||
 </widget>
 | 
					 </widget>
 | 
				
			||||||
 <customwidgets>
 | 
					 <customwidgets>
 | 
				
			||||||
  <customwidget>
 | 
					 | 
				
			||||||
   <class>DeviceView</class>
 | 
					 | 
				
			||||||
   <extends>QListView</extends>
 | 
					 | 
				
			||||||
   <header>widgets.h</header>
 | 
					 | 
				
			||||||
  </customwidget>
 | 
					 | 
				
			||||||
  <customwidget>
 | 
					  <customwidget>
 | 
				
			||||||
   <class>BooksView</class>
 | 
					   <class>BooksView</class>
 | 
				
			||||||
   <extends>QTableView</extends>
 | 
					   <extends>QTableView</extends>
 | 
				
			||||||
@ -347,6 +342,11 @@
 | 
				
			|||||||
   <extends>QLineEdit</extends>
 | 
					   <extends>QLineEdit</extends>
 | 
				
			||||||
   <header>library.h</header>
 | 
					   <header>library.h</header>
 | 
				
			||||||
  </customwidget>
 | 
					  </customwidget>
 | 
				
			||||||
 | 
					  <customwidget>
 | 
				
			||||||
 | 
					   <class>LocationView</class>
 | 
				
			||||||
 | 
					   <extends>QListView</extends>
 | 
				
			||||||
 | 
					   <header>widgets.h</header>
 | 
				
			||||||
 | 
					  </customwidget>
 | 
				
			||||||
 </customwidgets>
 | 
					 </customwidgets>
 | 
				
			||||||
 <resources>
 | 
					 <resources>
 | 
				
			||||||
  <include location="images.qrc" />
 | 
					  <include location="images.qrc" />
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Form implementation generated from reading ui file 'main.ui'
 | 
					# Form implementation generated from reading ui file 'main.ui'
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Created: Tue Jun 19 11:07:30 2007
 | 
					# Created: Thu Jun 21 20:31:43 2007
 | 
				
			||||||
#      by: PyQt4 UI code generator 4-snapshot-20070606
 | 
					#      by: PyQt4 UI code generator 4-snapshot-20070606
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# WARNING! All changes made in this file will be lost!
 | 
					# WARNING! All changes made in this file will be lost!
 | 
				
			||||||
@ -32,7 +32,7 @@ class Ui_MainWindow(object):
 | 
				
			|||||||
        self.hboxlayout.setMargin(0)
 | 
					        self.hboxlayout.setMargin(0)
 | 
				
			||||||
        self.hboxlayout.setObjectName("hboxlayout")
 | 
					        self.hboxlayout.setObjectName("hboxlayout")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.device_tree = DeviceView(self.centralwidget)
 | 
					        self.device_tree = LocationView(self.centralwidget)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred)
 | 
					        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred)
 | 
				
			||||||
        sizePolicy.setHorizontalStretch(0)
 | 
					        sizePolicy.setHorizontalStretch(0)
 | 
				
			||||||
@ -184,7 +184,7 @@ class Ui_MainWindow(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def retranslateUi(self, MainWindow):
 | 
					    def retranslateUi(self, MainWindow):
 | 
				
			||||||
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "libprs500", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "libprs500", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
        self.df.setText(QtGui.QApplication.translate("MainWindow", "For help visit <a href=\"https://libprs500.kovidgoyal.net/wiki/GuiUsage\">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> © 2006<br>%2 %3 %4", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.df.setText(QtGui.QApplication.translate("MainWindow", "For help visit <a href=\"https://libprs500.kovidgoyal.net/wiki/GuiUsage\">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> © 2007<br>%2 %3 %4", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
        self.label.setText(QtGui.QApplication.translate("MainWindow", "&Search:", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.label.setText(QtGui.QApplication.translate("MainWindow", "&Search:", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
        self.search.setToolTip(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.search.setToolTip(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
        self.search.setWhatsThis(QtGui.QApplication.translate("MainWindow", "Search the list of books by title, author, publisher, tags and comments<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.search.setWhatsThis(QtGui.QApplication.translate("MainWindow", "Search the list of books by title, author, publisher, tags and comments<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
@ -197,6 +197,6 @@ class Ui_MainWindow(object):
 | 
				
			|||||||
        self.action_edit.setText(QtGui.QApplication.translate("MainWindow", "Edit meta-information", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.action_edit.setText(QtGui.QApplication.translate("MainWindow", "Edit meta-information", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
        self.action_edit.setShortcut(QtGui.QApplication.translate("MainWindow", "E", None, QtGui.QApplication.UnicodeUTF8))
 | 
					        self.action_edit.setShortcut(QtGui.QApplication.translate("MainWindow", "E", None, QtGui.QApplication.UnicodeUTF8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from widgets import DeviceView
 | 
					from widgets import LocationView
 | 
				
			||||||
from library import BooksView, SearchBox
 | 
					from library import BooksView, SearchBox
 | 
				
			||||||
import images_rc
 | 
					import images_rc
 | 
				
			||||||
 | 
				
			|||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
 | 
					 | 
				
			||||||
##    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.QtGui import QIconEngine, QTabWidget, QPixmap, QIcon, QPainter, QColor
 | 
					 | 
				
			||||||
from PyQt4.QtCore import QTimeLine, QObject, SIGNAL
 | 
					 | 
				
			||||||
from PyQt4.QtSvg import QSvgRenderer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RotatingIconEngine(QIconEngine):
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def create_pixmaps(path, size=16, delta=20):
 | 
					 | 
				
			||||||
        r = QSvgRenderer(path)
 | 
					 | 
				
			||||||
        if not r.isValid():
 | 
					 | 
				
			||||||
            raise Exception(path + ' not valid svg')
 | 
					 | 
				
			||||||
        pixmaps = []
 | 
					 | 
				
			||||||
        for angle in range(0, 360, delta):
 | 
					 | 
				
			||||||
            pm = QPixmap(size, size)
 | 
					 | 
				
			||||||
            pm.fill(QColor(0,0,0,0))
 | 
					 | 
				
			||||||
            p = QPainter(pm)
 | 
					 | 
				
			||||||
            p.translate(size/2., size/2.)
 | 
					 | 
				
			||||||
            p.rotate(angle)
 | 
					 | 
				
			||||||
            p.translate(-size/2., -size/2.)
 | 
					 | 
				
			||||||
            r.render(p)
 | 
					 | 
				
			||||||
            p.end()
 | 
					 | 
				
			||||||
            pixmaps.append(pm)
 | 
					 | 
				
			||||||
        return pixmaps
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def __init__(self, path, size=16):
 | 
					 | 
				
			||||||
        self.pixmaps = self.__class__.create_pixmaps(path, size)
 | 
					 | 
				
			||||||
        self.current = 0
 | 
					 | 
				
			||||||
        QIconEngine.__init__(self)
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def next(self):
 | 
					 | 
				
			||||||
        self.current += 1
 | 
					 | 
				
			||||||
        self.current %= len(self.pixmaps)
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def reset(self):
 | 
					 | 
				
			||||||
        self.current = 0
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def pixmap(self, size, mode, state):
 | 
					 | 
				
			||||||
        return self.pixmaps[self.current]
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
class AnimatedTabWidget(QTabWidget):
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        self.animated_tab = 1
 | 
					 | 
				
			||||||
        self.ri = RotatingIconEngine(':/images/jobs.svg')        
 | 
					 | 
				
			||||||
        QTabWidget.__init__(self, parent)
 | 
					 | 
				
			||||||
        self.timeline = QTimeLine(4000, self)
 | 
					 | 
				
			||||||
        self.timeline.setLoopCount(0)
 | 
					 | 
				
			||||||
        self.timeline.setCurveShape(QTimeLine.LinearCurve)
 | 
					 | 
				
			||||||
        self.timeline.setFrameRange(0, len(self.ri.pixmaps))
 | 
					 | 
				
			||||||
        QObject.connect(self.timeline, SIGNAL('frameChanged(int)'), self.next)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def setup(self):
 | 
					 | 
				
			||||||
        self.setTabIcon(self.animated_tab, QIcon(self.ri))
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def animate(self):
 | 
					 | 
				
			||||||
        self.timeline.start()
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def update_animated_tab(self):
 | 
					 | 
				
			||||||
        tb = self.tabBar()
 | 
					 | 
				
			||||||
        rect = tb.tabRect(self.animated_tab)
 | 
					 | 
				
			||||||
        tb.update(rect)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def stop(self):
 | 
					 | 
				
			||||||
        self.timeline.stop()
 | 
					 | 
				
			||||||
        self.ri.reset()
 | 
					 | 
				
			||||||
        self.update_animated_tab()
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    def next(self, frame):
 | 
					 | 
				
			||||||
        self.ri.next()
 | 
					 | 
				
			||||||
        self.update_animated_tab()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
##    Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
 | 
					##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
 | 
				
			||||||
##    This program is free software; you can redistribute it and/or modify
 | 
					##    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
 | 
					##    it under the terms of the GNU General Public License as published by
 | 
				
			||||||
##    the Free Software Foundation; either version 2 of the License, or
 | 
					##    the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
@ -12,829 +12,37 @@
 | 
				
			|||||||
##    You should have received a copy of the GNU General Public License along
 | 
					##    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.,
 | 
					##    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
				
			||||||
##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
					##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
				
			||||||
import re
 | 
					'''
 | 
				
			||||||
import os
 | 
					Miscellanous widgets used in the GUI
 | 
				
			||||||
import textwrap
 | 
					'''
 | 
				
			||||||
import time 
 | 
					from PyQt4.QtGui import QListView, QIcon, QFont
 | 
				
			||||||
import traceback
 | 
					from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QSize, SIGNAL
 | 
				
			||||||
from operator import itemgetter, attrgetter
 | 
					 | 
				
			||||||
from socket import gethostname
 | 
					 | 
				
			||||||
from urlparse import urlparse, urlunparse
 | 
					 | 
				
			||||||
from urllib import quote, unquote
 | 
					 | 
				
			||||||
from math import sin, cos, pi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from libprs500.gui import Error, _Warning
 | 
					from libprs500.gui2 import human_readable, NONE
 | 
				
			||||||
from libprs500.ptempfile import PersistentTemporaryFile
 | 
					 | 
				
			||||||
from libprs500 import iswindows
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from PyQt4.QtCore import Qt, SIGNAL
 | 
					 | 
				
			||||||
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, \
 | 
					 | 
				
			||||||
                     QVariant, QAbstractTableModel, QTableView, QListView, \
 | 
					 | 
				
			||||||
                     QLabel, QAbstractItemView, QPixmap, QIcon, QSize, \
 | 
					 | 
				
			||||||
                     QSpinBox, QPoint, QPainterPath, QItemDelegate, QPainter, \
 | 
					 | 
				
			||||||
                     QPen, QColor, QLinearGradient, QBrush, QStyle, \
 | 
					 | 
				
			||||||
                     QByteArray, QBuffer, QMimeData, \
 | 
					 | 
				
			||||||
                     QDrag, QRect                     
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
NONE = QVariant() #: Null value to return from the data function of item models
 | 
					 | 
				
			||||||
TIME_WRITE_FMT  = "%d %b %Y" #: The display format used to show dates
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class FileDragAndDrop(object):
 | 
					 | 
				
			||||||
    _drag_start_position = QPoint()
 | 
					 | 
				
			||||||
    _dragged_files = []
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def _bytes_to_string(cls, qba):
 | 
					 | 
				
			||||||
        """ 
 | 
					 | 
				
			||||||
        Assumes qba is encoded in ASCII which is usually fine, since
 | 
					 | 
				
			||||||
        this method is used mainly for escaped URIs.
 | 
					 | 
				
			||||||
        @type qba: QByteArray 
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        return str(QString.fromAscii(qba.data())).strip()        
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def _get_r_ok_files(cls, event):
 | 
					 | 
				
			||||||
        """ 
 | 
					 | 
				
			||||||
        Return list of paths from event that point to files to
 | 
					 | 
				
			||||||
        which the user has read permission.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        files = []
 | 
					 | 
				
			||||||
        md = event.mimeData()
 | 
					 | 
				
			||||||
        if md.hasFormat("text/uri-list"):
 | 
					 | 
				
			||||||
            candidates = cls._bytes_to_string(md.data("text/uri-list")).split()
 | 
					 | 
				
			||||||
            for url in candidates:
 | 
					 | 
				
			||||||
                o = urlparse(url)        
 | 
					 | 
				
			||||||
                if o.scheme and o.scheme != 'file':
 | 
					 | 
				
			||||||
                    _Warning(o.scheme +  " not supported in drop events", None)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                path = unquote(o.path)
 | 
					 | 
				
			||||||
                if iswindows and path.startswith('/'):
 | 
					 | 
				
			||||||
                    path = path[1:]
 | 
					 | 
				
			||||||
                if not os.access(path, os.R_OK):
 | 
					 | 
				
			||||||
                    _Warning("You do not have read permission for: " + path, None)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                if os.path.isdir(path):
 | 
					 | 
				
			||||||
                    root, dirs, files2 = os.walk(path)
 | 
					 | 
				
			||||||
                    for _file in files2:
 | 
					 | 
				
			||||||
                        path = root + _file
 | 
					 | 
				
			||||||
                        if os.access(path, os.R_OK): 
 | 
					 | 
				
			||||||
                            files.append(path)
 | 
					 | 
				
			||||||
                else: 
 | 
					 | 
				
			||||||
                    files.append(path)
 | 
					 | 
				
			||||||
        return files
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def __init__(self, QtBaseClass, enable_drag=True):
 | 
					 | 
				
			||||||
        self.QtBaseClass = QtBaseClass
 | 
					 | 
				
			||||||
        self.enable_drag = enable_drag
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def mousePressEvent(self, event):
 | 
					 | 
				
			||||||
        self.QtBaseClass.mousePressEvent(self, event)
 | 
					 | 
				
			||||||
        if self.enable_drag:
 | 
					 | 
				
			||||||
            if event.button == Qt.LeftButton:
 | 
					 | 
				
			||||||
                self._drag_start_position = event.pos()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def mouseMoveEvent(self, event):
 | 
					 | 
				
			||||||
        self.QtBaseClass.mousePressEvent(self, event)
 | 
					 | 
				
			||||||
        if self.enable_drag:
 | 
					 | 
				
			||||||
            if event.buttons() & Qt.LeftButton != Qt.LeftButton: 
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            if (event.pos() - self._drag_start_position).manhattanLength() < \
 | 
					 | 
				
			||||||
                                            QApplication.startDragDistance(): 
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            self.start_drag(self._drag_start_position)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def start_drag(self, pos): 
 | 
					 | 
				
			||||||
        raise NotImplementedError()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def dragEnterEvent(self, event):
 | 
					 | 
				
			||||||
        if event.mimeData().hasFormat("text/uri-list"): 
 | 
					 | 
				
			||||||
            event.acceptProposedAction()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def dragMoveEvent(self, event):
 | 
					 | 
				
			||||||
        event.acceptProposedAction()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def dropEvent(self, event):
 | 
					 | 
				
			||||||
        files = self._get_r_ok_files(event)
 | 
					 | 
				
			||||||
        if files:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                event.setDropAction(Qt.CopyAction)
 | 
					 | 
				
			||||||
                if self.files_dropped(files, event): 
 | 
					 | 
				
			||||||
                    event.accept()
 | 
					 | 
				
			||||||
            except Exception, e:        
 | 
					 | 
				
			||||||
                Error("There was an error processing the dropped files.", e)
 | 
					 | 
				
			||||||
                raise e
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def files_dropped(self, files, event): 
 | 
					 | 
				
			||||||
        raise NotImplementedError()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def drag_object_from_files(self, files):
 | 
					 | 
				
			||||||
        if files:
 | 
					 | 
				
			||||||
            drag = QDrag(self)
 | 
					 | 
				
			||||||
            mime_data = QMimeData()
 | 
					 | 
				
			||||||
            self._dragged_files, urls = [], []
 | 
					 | 
				
			||||||
            for _file in files:        
 | 
					 | 
				
			||||||
                urls.append(urlunparse(('file', quote(gethostname()), \
 | 
					 | 
				
			||||||
                                quote(_file.name.encode('utf-8')), '', '', '')))
 | 
					 | 
				
			||||||
                self._dragged_files.append(_file)      
 | 
					 | 
				
			||||||
            mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
 | 
					 | 
				
			||||||
            user = os.getenv('USER')
 | 
					 | 
				
			||||||
            if user: 
 | 
					 | 
				
			||||||
                mime_data.setData("text/x-xdnd-username", QByteArray(user))
 | 
					 | 
				
			||||||
            drag.setMimeData(mime_data)
 | 
					 | 
				
			||||||
            return drag
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def drag_object(self, extensions):
 | 
					 | 
				
			||||||
        if extensions:
 | 
					 | 
				
			||||||
            files = []
 | 
					 | 
				
			||||||
            for ext in extensions:
 | 
					 | 
				
			||||||
                f = PersistentTemporaryFile(suffix="."+ext)
 | 
					 | 
				
			||||||
                files.append(f)
 | 
					 | 
				
			||||||
            return self.drag_object_from_files(files), self._dragged_files
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TableView(FileDragAndDrop, QTableView):
 | 
					 | 
				
			||||||
    wrapper = textwrap.TextWrapper(width=20)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LocationModel(QAbstractListModel):
 | 
				
			||||||
    def __init__(self, parent):
 | 
					    def __init__(self, parent):
 | 
				
			||||||
        FileDragAndDrop.__init__(self, QTableView)
 | 
					        QAbstractListModel.__init__(self, parent)
 | 
				
			||||||
        QTableView.__init__(self, parent)
 | 
					        self.icons = [QVariant(QIcon(':/library')),
 | 
				
			||||||
    
 | 
					                      QVariant(QIcon(':/reader')),
 | 
				
			||||||
    @classmethod
 | 
					                      QVariant(QIcon(':/card'))]
 | 
				
			||||||
    def wrap(cls, s, width=20):         
 | 
					        self.text = ['Library',
 | 
				
			||||||
        cls.wrapper.width = width
 | 
					                     'Reader\n%s available',
 | 
				
			||||||
        return cls.wrapper.fill(s) 
 | 
					                     'Card\n%s available']
 | 
				
			||||||
    
 | 
					        self.free = [-1, -1]            
 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def human_readable(cls, size):
 | 
					 | 
				
			||||||
        """ Convert a size in bytes into a human readable form """
 | 
					 | 
				
			||||||
        if size < 1024: 
 | 
					 | 
				
			||||||
            divisor, suffix = 1, "B"
 | 
					 | 
				
			||||||
        elif size < 1024*1024: 
 | 
					 | 
				
			||||||
            divisor, suffix = 1024., "KB"
 | 
					 | 
				
			||||||
        elif size < 1024*1024*1024: 
 | 
					 | 
				
			||||||
            divisor, suffix = 1024*1024, "MB"
 | 
					 | 
				
			||||||
        elif size < 1024*1024*1024*1024: 
 | 
					 | 
				
			||||||
            divisor, suffix = 1024*1024, "GB"
 | 
					 | 
				
			||||||
        size = str(size/divisor)
 | 
					 | 
				
			||||||
        if size.find(".") > -1: 
 | 
					 | 
				
			||||||
            size = size[:size.find(".")+2]
 | 
					 | 
				
			||||||
        return size + " " + suffix
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def render_to_pixmap(self, indices):
 | 
					 | 
				
			||||||
        rect = self.visualRect(indices[0])
 | 
					 | 
				
			||||||
        rects = []
 | 
					 | 
				
			||||||
        for i in range(len(indices)):
 | 
					 | 
				
			||||||
            rects.append(self.visualRect(indices[i]))
 | 
					 | 
				
			||||||
            rect |= rects[i]
 | 
					 | 
				
			||||||
        rect = rect.intersected(self.viewport().rect())
 | 
					 | 
				
			||||||
        pixmap = QPixmap(rect.size())
 | 
					 | 
				
			||||||
        pixmap.fill(self.palette().base().color())
 | 
					 | 
				
			||||||
        painter = QPainter(pixmap)
 | 
					 | 
				
			||||||
        option = self.viewOptions()
 | 
					 | 
				
			||||||
        option.state |= QStyle.State_Selected
 | 
					 | 
				
			||||||
        for j in range(len(indices)):
 | 
					 | 
				
			||||||
            option.rect = QRect(rects[j].topLeft() - rect.topLeft(), \
 | 
					 | 
				
			||||||
                                    rects[j].size())
 | 
					 | 
				
			||||||
            self.itemDelegate(indices[j]).paint(painter, option, indices[j])
 | 
					 | 
				
			||||||
        painter.end()
 | 
					 | 
				
			||||||
        return pixmap
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def drag_object_from_files(self, files):
 | 
					 | 
				
			||||||
        drag = FileDragAndDrop.drag_object_from_files(self, files)
 | 
					 | 
				
			||||||
        drag.setPixmap(self.render_to_pixmap(self.selectedIndexes()))
 | 
					 | 
				
			||||||
        return drag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CoverDisplay(FileDragAndDrop, QLabel):
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        FileDragAndDrop.__init__(self, QLabel)
 | 
					 | 
				
			||||||
        QLabel.__init__(self, parent)
 | 
					 | 
				
			||||||
    def files_dropped(self, files, event):
 | 
					 | 
				
			||||||
        pix = QPixmap()
 | 
					 | 
				
			||||||
        for _file in files:
 | 
					 | 
				
			||||||
            pix = QPixmap(_file)
 | 
					 | 
				
			||||||
            if not pix.isNull(): break
 | 
					 | 
				
			||||||
        if not pix.isNull():      
 | 
					 | 
				
			||||||
            self.emit(SIGNAL("cover_received(QPixmap)"), pix)
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def start_drag(self, event):
 | 
					 | 
				
			||||||
        drag, files = self.drag_object(["jpeg"])
 | 
					 | 
				
			||||||
        if drag and files:
 | 
					 | 
				
			||||||
            _file = files[0]
 | 
					 | 
				
			||||||
            _file.close()
 | 
					 | 
				
			||||||
            drag.setPixmap(self.pixmap().scaledToHeight(68, \
 | 
					 | 
				
			||||||
                                                    Qt.SmoothTransformation))
 | 
					 | 
				
			||||||
            self.pixmap().save(os.path.abspath(_file.name))
 | 
					 | 
				
			||||||
            drag.start(Qt.MoveAction)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DeviceView(FileDragAndDrop, QListView):
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        FileDragAndDrop.__init__(self, QListView, enable_drag=False)
 | 
					 | 
				
			||||||
        QListView.__init__(self, parent)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def hide_reader(self, x):
 | 
					 | 
				
			||||||
        self.model().update_devices(reader=not x)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def hide_card(self, x):
 | 
					 | 
				
			||||||
        self.model().update_devices(card=not x)
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
    def files_dropped(self, files, event):
 | 
					 | 
				
			||||||
        ids = []
 | 
					 | 
				
			||||||
        md = event.mimeData()
 | 
					 | 
				
			||||||
        if md.hasFormat("application/x-libprs500-id"):
 | 
					 | 
				
			||||||
            ids = [ int(id) for id in FileDragAndDrop._bytes_to_string(\
 | 
					 | 
				
			||||||
                                md.data("application/x-libprs500-id")).split()]
 | 
					 | 
				
			||||||
        index = self.indexAt(event.pos())
 | 
					 | 
				
			||||||
        if index.isValid():
 | 
					 | 
				
			||||||
            return self.model().files_dropped(files, index, ids)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DeviceBooksView(TableView):
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        TableView.__init__(self, parent)
 | 
					 | 
				
			||||||
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
 | 
					 | 
				
			||||||
        self.setSortingEnabled(True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LibraryBooksView(TableView):
 | 
					 | 
				
			||||||
    def __init__(self, parent):    
 | 
					 | 
				
			||||||
        TableView.__init__(self, parent)
 | 
					 | 
				
			||||||
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
 | 
					 | 
				
			||||||
        self.setSortingEnabled(True)
 | 
					 | 
				
			||||||
        self.setItemDelegate(LibraryDelegate(self, rating_column=4))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def dragEnterEvent(self, event):
 | 
					 | 
				
			||||||
        if not event.mimeData().hasFormat("application/x-libprs500-id"):
 | 
					 | 
				
			||||||
            FileDragAndDrop.dragEnterEvent(self, event)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def start_drag(self, pos):    
 | 
					 | 
				
			||||||
        index = self.indexAt(pos)
 | 
					 | 
				
			||||||
        if index.isValid():
 | 
					 | 
				
			||||||
            rows = frozenset([ index.row() for index in self.selectedIndexes()])
 | 
					 | 
				
			||||||
            files = self.model().extract_formats(rows)            
 | 
					 | 
				
			||||||
            drag = self.drag_object_from_files(files)
 | 
					 | 
				
			||||||
            if drag:
 | 
					 | 
				
			||||||
                ids = [ str(self.model().id_from_row(row)) for row in rows ]
 | 
					 | 
				
			||||||
                drag.mimeData().setData("application/x-libprs500-id", \
 | 
					 | 
				
			||||||
                        QByteArray("\n".join(ids)))
 | 
					 | 
				
			||||||
                drag.start()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def files_dropped(self, files, event):
 | 
					 | 
				
			||||||
        if not files: return
 | 
					 | 
				
			||||||
        index = self.indexAt(event.pos())    
 | 
					 | 
				
			||||||
        if index.isValid():
 | 
					 | 
				
			||||||
            self.model().add_formats(files, index)      
 | 
					 | 
				
			||||||
        else: self.emit(SIGNAL('books_dropped'), files)      
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LibraryDelegate(QItemDelegate):
 | 
					 | 
				
			||||||
    COLOR = QColor("blue")
 | 
					 | 
				
			||||||
    SIZE     = 16
 | 
					 | 
				
			||||||
    PEN      = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def __init__(self, parent, rating_column=-1):
 | 
					 | 
				
			||||||
        QItemDelegate.__init__(self, parent)
 | 
					 | 
				
			||||||
        self.rating_column = rating_column
 | 
					 | 
				
			||||||
        self.star_path = QPainterPath()
 | 
					 | 
				
			||||||
        self.star_path.moveTo(90, 50)
 | 
					 | 
				
			||||||
        for i in range(1, 5):
 | 
					 | 
				
			||||||
            self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
 | 
					 | 
				
			||||||
                                  50 + 40 * sin(0.8 * i * pi))
 | 
					 | 
				
			||||||
        self.star_path.closeSubpath()
 | 
					 | 
				
			||||||
        self.star_path.setFillRule(Qt.WindingFill)
 | 
					 | 
				
			||||||
        gradient = QLinearGradient(0, 0, 0, 100)
 | 
					 | 
				
			||||||
        gradient.setColorAt(0.0, self.COLOR)
 | 
					 | 
				
			||||||
        gradient.setColorAt(1.0, self.COLOR)
 | 
					 | 
				
			||||||
        self. brush = QBrush(gradient)
 | 
					 | 
				
			||||||
        self.factor = self.SIZE/100.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def sizeHint(self, option, index):
 | 
					 | 
				
			||||||
        if index.column() != self.rating_column:
 | 
					 | 
				
			||||||
            return QItemDelegate.sizeHint(self, option, index)
 | 
					 | 
				
			||||||
        num = index.model().data(index, Qt.DisplayRole).toInt()[0]
 | 
					 | 
				
			||||||
        return QSize(num*(self.SIZE), self.SIZE+4)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def paint(self, painter, option, index):
 | 
					 | 
				
			||||||
        if index.column() != self.rating_column:
 | 
					 | 
				
			||||||
            return QItemDelegate.paint(self, painter, option, index)
 | 
					 | 
				
			||||||
        num = index.model().data(index, Qt.DisplayRole).toInt()[0]
 | 
					 | 
				
			||||||
        def draw_star(): 
 | 
					 | 
				
			||||||
            painter.save()
 | 
					 | 
				
			||||||
            painter.scale(self.factor, self.factor)
 | 
					 | 
				
			||||||
            painter.translate(50.0, 50.0)
 | 
					 | 
				
			||||||
            painter.rotate(-20)
 | 
					 | 
				
			||||||
            painter.translate(-50.0, -50.0)
 | 
					 | 
				
			||||||
            painter.drawPath(self.star_path)
 | 
					 | 
				
			||||||
            painter.restore()
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        painter.save()
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            if option.state & QStyle.State_Selected:
 | 
					 | 
				
			||||||
                painter.fillRect(option.rect, option.palette.highlight())
 | 
					 | 
				
			||||||
            painter.setRenderHint(QPainter.Antialiasing)
 | 
					 | 
				
			||||||
            y = option.rect.center().y()-self.SIZE/2. 
 | 
					 | 
				
			||||||
            x = option.rect.right()  - self.SIZE
 | 
					 | 
				
			||||||
            painter.setPen(self.PEN)      
 | 
					 | 
				
			||||||
            painter.setBrush(self.brush)      
 | 
					 | 
				
			||||||
            painter.translate(x, y)
 | 
					 | 
				
			||||||
            for i in range(num):
 | 
					 | 
				
			||||||
                draw_star()
 | 
					 | 
				
			||||||
                painter.translate(-self.SIZE, 0)
 | 
					 | 
				
			||||||
        except Exception, e:
 | 
					 | 
				
			||||||
            traceback.print_exc(e)
 | 
					 | 
				
			||||||
        painter.restore()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def createEditor(self, parent, option, index):
 | 
					 | 
				
			||||||
        if index.column() != 4:
 | 
					 | 
				
			||||||
            return QItemDelegate.createEditor(self, parent, option, index)
 | 
					 | 
				
			||||||
        editor = QSpinBox(parent)
 | 
					 | 
				
			||||||
        editor.setSuffix(" stars")
 | 
					 | 
				
			||||||
        editor.setMinimum(0)
 | 
					 | 
				
			||||||
        editor.setMaximum(5)
 | 
					 | 
				
			||||||
        editor.installEventFilter(self)
 | 
					 | 
				
			||||||
        return editor
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def setEditorData(self, editor, index):
 | 
					 | 
				
			||||||
        if index.column() != 4:
 | 
					 | 
				
			||||||
            return QItemDelegate.setEditorData(self, editor, index)
 | 
					 | 
				
			||||||
        val = index.model()._data[index.row()]["rating"]
 | 
					 | 
				
			||||||
        if not val: val = 0
 | 
					 | 
				
			||||||
        editor.setValue(val)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def setModelData(self, editor, model, index):
 | 
					 | 
				
			||||||
        if index.column() != 4:
 | 
					 | 
				
			||||||
            return QItemDelegate.setModelData(self, editor, model, index)
 | 
					 | 
				
			||||||
        editor.interpretText()
 | 
					 | 
				
			||||||
        index.model().setData(index, QVariant(editor.value()), Qt.EditRole)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def updateEditorGeometry(self, editor, option, index):
 | 
					 | 
				
			||||||
        if index.column() != 4:
 | 
					 | 
				
			||||||
            return QItemDelegate.updateEditorGeometry(self, editor, option, index)
 | 
					 | 
				
			||||||
        editor.setGeometry(option.rect)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LibraryBooksModel(QAbstractTableModel):
 | 
					 | 
				
			||||||
    FIELDS = ["id", "title", "authors", "size", "date", "rating", "publisher", \
 | 
					 | 
				
			||||||
              "tags", "comments"]  
 | 
					 | 
				
			||||||
    TIME_READ_FMT = "%Y-%m-%d %H:%M:%S"
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        QAbstractTableModel.__init__(self, parent)
 | 
					 | 
				
			||||||
        self.db    = None 
 | 
					 | 
				
			||||||
        self._data = None
 | 
					 | 
				
			||||||
        self._orig_data = None
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def extract_formats(self, rows):
 | 
					 | 
				
			||||||
        files = []
 | 
					 | 
				
			||||||
        for row in rows:      
 | 
					 | 
				
			||||||
            _id = self.id_from_row(row)
 | 
					 | 
				
			||||||
            au = self._data[row]["authors"] if self._data[row]["authors"] \
 | 
					 | 
				
			||||||
                                            else "Unknown"
 | 
					 | 
				
			||||||
            basename = re.sub("\n", "", "_"+str(_id)+"_"+\
 | 
					 | 
				
			||||||
                                self._data[row]["title"]+" by "+ au)
 | 
					 | 
				
			||||||
            exts = self.db.get_extensions(_id)
 | 
					 | 
				
			||||||
            for ext in exts:
 | 
					 | 
				
			||||||
                fmt = self.db.get_format(_id, ext)
 | 
					 | 
				
			||||||
                if not ext: 
 | 
					 | 
				
			||||||
                    ext =""
 | 
					 | 
				
			||||||
                else: 
 | 
					 | 
				
			||||||
                    ext = "."+ext
 | 
					 | 
				
			||||||
                name = basename+ext                
 | 
					 | 
				
			||||||
                file = PersistentTemporaryFile(suffix=name)
 | 
					 | 
				
			||||||
                if not fmt: 
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                file.write(fmt)
 | 
					 | 
				
			||||||
                file.close() 
 | 
					 | 
				
			||||||
                files.append(file)
 | 
					 | 
				
			||||||
        return files
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def update_cover(self, index, pix):
 | 
					 | 
				
			||||||
        spix = pix.scaledToHeight(68, Qt.SmoothTransformation)
 | 
					 | 
				
			||||||
        _id = self.id_from_index(index)
 | 
					 | 
				
			||||||
        qb, sqb = QBuffer(), QBuffer()
 | 
					 | 
				
			||||||
        qb.open(QBuffer.ReadWrite)
 | 
					 | 
				
			||||||
        sqb.open(QBuffer.ReadWrite)
 | 
					 | 
				
			||||||
        pix.save(qb, "JPG")
 | 
					 | 
				
			||||||
        spix.save(sqb, "JPG")
 | 
					 | 
				
			||||||
        data = str(qb.data())
 | 
					 | 
				
			||||||
        sdata = str(sqb.data())
 | 
					 | 
				
			||||||
        qb.close()
 | 
					 | 
				
			||||||
        sqb.close()
 | 
					 | 
				
			||||||
        self.db.update_cover(_id, data, scaled=sdata)
 | 
					 | 
				
			||||||
        self.refresh_row(index.row())
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def add_formats(self, paths, index):
 | 
					 | 
				
			||||||
        for path in paths:
 | 
					 | 
				
			||||||
            f = open(path, "rb")      
 | 
					 | 
				
			||||||
            title = os.path.basename(path)
 | 
					 | 
				
			||||||
            ext = title[title.rfind(".")+1:].lower() if "." in title > -1 else None
 | 
					 | 
				
			||||||
            _id = self.id_from_index(index)
 | 
					 | 
				
			||||||
            self.db.add_format(_id, ext, f)
 | 
					 | 
				
			||||||
            f.close()
 | 
					 | 
				
			||||||
        self.refresh_row(index.row())
 | 
					 | 
				
			||||||
        self.emit(SIGNAL('formats_added'), index)
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    def rowCount(self, parent):
 | 
					    def rowCount(self, parent):
 | 
				
			||||||
        return len(self._data)
 | 
					        return 1 + sum([1 for i in self.free if i >= 0])
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def columnCount(self, parent): 
 | 
					 | 
				
			||||||
        return len(self.FIELDS)-3
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def setData(self, index, value, role):
 | 
					 | 
				
			||||||
        done = False
 | 
					 | 
				
			||||||
        if role == Qt.EditRole:
 | 
					 | 
				
			||||||
            row = index.row()
 | 
					 | 
				
			||||||
            _id = self._data[row]["id"]
 | 
					 | 
				
			||||||
            col = index.column()
 | 
					 | 
				
			||||||
            val = unicode(value.toString().toUtf8(), 'utf-8').strip()
 | 
					 | 
				
			||||||
            if col == 0: 
 | 
					 | 
				
			||||||
                col = "title"
 | 
					 | 
				
			||||||
            elif col == 1: 
 | 
					 | 
				
			||||||
                col = "authors"
 | 
					 | 
				
			||||||
            elif col == 2: 
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            elif col == 3: 
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            elif col == 4: 
 | 
					 | 
				
			||||||
                col, val = "rating", int(value.toInt()[0])
 | 
					 | 
				
			||||||
                if val < 0:  
 | 
					 | 
				
			||||||
                    val = 0
 | 
					 | 
				
			||||||
                if val > 5: 
 | 
					 | 
				
			||||||
                    val = 5
 | 
					 | 
				
			||||||
            elif col == 5: 
 | 
					 | 
				
			||||||
                col = "publisher"
 | 
					 | 
				
			||||||
            else: 
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            self.db.set_metadata_item(_id, col, val)
 | 
					 | 
				
			||||||
            self._data[row][col] = val      
 | 
					 | 
				
			||||||
            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
 | 
					 | 
				
			||||||
                                index, index)
 | 
					 | 
				
			||||||
            for i in range(len(self._orig_data)):
 | 
					 | 
				
			||||||
                if self._orig_data[i]["id"] == self._data[row]["id"]:
 | 
					 | 
				
			||||||
                    self._orig_data[i][col] = self._data[row][col]
 | 
					 | 
				
			||||||
                    break      
 | 
					 | 
				
			||||||
            done = True
 | 
					 | 
				
			||||||
        return done
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def update_tags_and_comments(self, index, tags, comments):
 | 
					 | 
				
			||||||
        _id = self.id_from_index(index)
 | 
					 | 
				
			||||||
        self.db.set_metadata_item(_id, "tags", tags)
 | 
					 | 
				
			||||||
        self.db.set_metadata_item(_id, "comments", comments)
 | 
					 | 
				
			||||||
        self.refresh_row(index.row())
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def flags(self, index):
 | 
					 | 
				
			||||||
        flags = QAbstractTableModel.flags(self, index)
 | 
					 | 
				
			||||||
        if index.isValid():       
 | 
					 | 
				
			||||||
            if index.column() not in [2, 3]:  
 | 
					 | 
				
			||||||
                flags |= Qt.ItemIsEditable    
 | 
					 | 
				
			||||||
        return flags
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def set_data(self, db):    
 | 
					 | 
				
			||||||
        self.db    = db
 | 
					 | 
				
			||||||
        self._data = self.db.get_table(self.FIELDS)    
 | 
					 | 
				
			||||||
        self._orig_data = self._data
 | 
					 | 
				
			||||||
        self.sort(0, Qt.DescendingOrder)
 | 
					 | 
				
			||||||
        self.reset()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def headerData(self, section, orientation, role):    
 | 
					 | 
				
			||||||
        if role != Qt.DisplayRole:
 | 
					 | 
				
			||||||
            return NONE
 | 
					 | 
				
			||||||
        text = ""
 | 
					 | 
				
			||||||
        if orientation == Qt.Horizontal:      
 | 
					 | 
				
			||||||
            if   section == 0: text = "Title"
 | 
					 | 
				
			||||||
            elif section == 1: text = "Author(s)"
 | 
					 | 
				
			||||||
            elif section == 2: text = "Size"
 | 
					 | 
				
			||||||
            elif section == 3: text = "Date"
 | 
					 | 
				
			||||||
            elif section == 4: text = "Rating"
 | 
					 | 
				
			||||||
            elif section == 5: text = "Publisher"
 | 
					 | 
				
			||||||
            return QVariant(self.trUtf8(text))
 | 
					 | 
				
			||||||
        else: return QVariant(str(1+section))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def info(self, row):
 | 
					 | 
				
			||||||
        row = self._data[row]
 | 
					 | 
				
			||||||
        cover = self.db.get_cover(row["id"])
 | 
					 | 
				
			||||||
        exts = ",".join(self.db.get_extensions(row["id"]))    
 | 
					 | 
				
			||||||
        if cover:
 | 
					 | 
				
			||||||
            pix = QPixmap()
 | 
					 | 
				
			||||||
            pix.loadFromData(cover, "", Qt.AutoColor)
 | 
					 | 
				
			||||||
            cover = None if pix.isNull() else pix      
 | 
					 | 
				
			||||||
        tags = row["tags"]
 | 
					 | 
				
			||||||
        if not tags: tags = ""
 | 
					 | 
				
			||||||
        comments = row["comments"]
 | 
					 | 
				
			||||||
        if not comments: 
 | 
					 | 
				
			||||||
            comments = ""
 | 
					 | 
				
			||||||
        comments = TableView.wrap(comments, width=80)
 | 
					 | 
				
			||||||
        return exts, tags, comments, cover
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def id_from_index(self, index): return self._data[index.row()]["id"]
 | 
					 | 
				
			||||||
    def id_from_row(self, row): return self._data[row]["id"]
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def refresh_row(self, row):
 | 
					 | 
				
			||||||
        datum = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS)
 | 
					 | 
				
			||||||
        self._data[row:row+1] = [datum]
 | 
					 | 
				
			||||||
        for i in range(len(self._orig_data)):
 | 
					 | 
				
			||||||
            if self._orig_data[i]["id"] == datum["id"]:
 | 
					 | 
				
			||||||
                self._orig_data[i:i+1] = [datum]
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
 | 
					 | 
				
			||||||
                    self.index(row, 0), self.index(row, self.columnCount(0)-1))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def book_info(self, _id):
 | 
					 | 
				
			||||||
        """ Return title, authors and cover in a dict """
 | 
					 | 
				
			||||||
        cover = self.db.get_cover(_id)
 | 
					 | 
				
			||||||
        info = self.db.get_row_by_id(_id, ["title", "authors"])
 | 
					 | 
				
			||||||
        info["cover"] = cover
 | 
					 | 
				
			||||||
        return info
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def data(self, index, role):
 | 
					 | 
				
			||||||
        if role == Qt.DisplayRole or role == Qt.EditRole:      
 | 
					 | 
				
			||||||
            row, col = index.row(), index.column()
 | 
					 | 
				
			||||||
            text = None
 | 
					 | 
				
			||||||
            row = self._data[row]
 | 
					 | 
				
			||||||
            if col == 4: 
 | 
					 | 
				
			||||||
                r = row["rating"] if row["rating"] else 0
 | 
					 | 
				
			||||||
                if r < 0: 
 | 
					 | 
				
			||||||
                    r = 0
 | 
					 | 
				
			||||||
                if r > 5: 
 | 
					 | 
				
			||||||
                    r = 5
 | 
					 | 
				
			||||||
                return QVariant(r)
 | 
					 | 
				
			||||||
            if   col == 0: 
 | 
					 | 
				
			||||||
                text = TableView.wrap(row["title"], width=35)
 | 
					 | 
				
			||||||
            elif col == 1: 
 | 
					 | 
				
			||||||
                au = row["authors"]
 | 
					 | 
				
			||||||
                if au:
 | 
					 | 
				
			||||||
                    au = au.split("&")
 | 
					 | 
				
			||||||
                    jau = [ TableView.wrap(a, width=30).strip() for a in au ]
 | 
					 | 
				
			||||||
                    text = "\n".join(jau)
 | 
					 | 
				
			||||||
            elif col == 2: 
 | 
					 | 
				
			||||||
                text = TableView.human_readable(row["size"])
 | 
					 | 
				
			||||||
            elif col == 3: 
 | 
					 | 
				
			||||||
                text = time.strftime(TIME_WRITE_FMT, \
 | 
					 | 
				
			||||||
                                time.strptime(row["date"], self.TIME_READ_FMT))
 | 
					 | 
				
			||||||
            elif col == 5: 
 | 
					 | 
				
			||||||
                pub = row["publisher"]
 | 
					 | 
				
			||||||
                if pub: 
 | 
					 | 
				
			||||||
                    text = TableView.wrap(pub, 20)
 | 
					 | 
				
			||||||
            if text == None: 
 | 
					 | 
				
			||||||
                text = "Unknown"
 | 
					 | 
				
			||||||
            return QVariant(text)
 | 
					 | 
				
			||||||
        elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]:
 | 
					 | 
				
			||||||
            return QVariant(Qt.AlignRight | Qt.AlignVCenter)
 | 
					 | 
				
			||||||
        elif role == Qt.ToolTipRole and index.isValid():
 | 
					 | 
				
			||||||
            if index.column() in [0, 1, 4, 5]:
 | 
					 | 
				
			||||||
                edit = "Double click to <b>edit</b> me<br><br>"
 | 
					 | 
				
			||||||
            else: 
 | 
					 | 
				
			||||||
                edit = ""
 | 
					 | 
				
			||||||
            return QVariant(edit + "You can <b>drag and drop</b> me to the \
 | 
					 | 
				
			||||||
                            desktop to save all my formats to your hard disk.")
 | 
					 | 
				
			||||||
        return NONE
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def sort(self, col, order):
 | 
					 | 
				
			||||||
        descending = order != Qt.AscendingOrder
 | 
					 | 
				
			||||||
        def getter(key, func):  
 | 
					 | 
				
			||||||
            return lambda x : func(itemgetter(key)(x))
 | 
					 | 
				
			||||||
        if col == 0: key, func = "title", lambda x : x.lower()
 | 
					 | 
				
			||||||
        if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower()\
 | 
					 | 
				
			||||||
                                                       if x else ""
 | 
					 | 
				
			||||||
        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 == 4: key, func = "rating", lambda x: x if x else 0
 | 
					 | 
				
			||||||
        if col == 5: key, func = "publisher", lambda x : x.lower() if x else ""
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        self._data.sort(key=getter(key, func))
 | 
					 | 
				
			||||||
        if descending: self._data.reverse()
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("sorted()"))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def search(self, query):
 | 
					 | 
				
			||||||
        def query_in(book, q):
 | 
					 | 
				
			||||||
            au = book["authors"]
 | 
					 | 
				
			||||||
            if not au : au = "unknown"
 | 
					 | 
				
			||||||
            pub = book["publisher"]
 | 
					 | 
				
			||||||
            if not pub : pub = "unknown"
 | 
					 | 
				
			||||||
            return q in book["title"].lower() or q in au.lower() or \
 | 
					 | 
				
			||||||
                                                                q in pub.lower()
 | 
					 | 
				
			||||||
        queries = unicode(query, 'utf-8').lower().split()
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        self._data = []
 | 
					 | 
				
			||||||
        for book in self._orig_data:
 | 
					 | 
				
			||||||
            match = True
 | 
					 | 
				
			||||||
            for q in queries:
 | 
					 | 
				
			||||||
                if query_in(book, q) : continue
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    match = False
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
            if match: self._data.append(book)
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("searched()"))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def delete(self, indices):
 | 
					 | 
				
			||||||
        if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        items = [ self._data[index.row()] for index in indices ]    
 | 
					 | 
				
			||||||
        for item in items:
 | 
					 | 
				
			||||||
            _id = item["id"]
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self._data.remove(item)
 | 
					 | 
				
			||||||
            except ValueError: continue
 | 
					 | 
				
			||||||
            self.db.delete_by_id(_id)
 | 
					 | 
				
			||||||
            for x in self._orig_data:
 | 
					 | 
				
			||||||
                if x["id"] == _id: self._orig_data.remove(x)
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("deleted()"))
 | 
					 | 
				
			||||||
        self.db.commit()    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def add_book(self, path):
 | 
					 | 
				
			||||||
        """ Must call search and sort on this models view after this """
 | 
					 | 
				
			||||||
        _id = self.db.add_book(path)    
 | 
					 | 
				
			||||||
        self._orig_data.append(self.db.get_row_by_id(_id, self.FIELDS))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DeviceBooksModel(QAbstractTableModel):
 | 
					 | 
				
			||||||
    @apply
 | 
					 | 
				
			||||||
    def booklist(): 
 | 
					 | 
				
			||||||
        doc = """ The booklist this model is based on """
 | 
					 | 
				
			||||||
        def fget(self):
 | 
					 | 
				
			||||||
            return self._orig_data
 | 
					 | 
				
			||||||
        return property(doc=doc, fget=fget)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def __init__(self, parent):
 | 
					 | 
				
			||||||
        QAbstractTableModel.__init__(self, parent)  
 | 
					 | 
				
			||||||
        self._data = []
 | 
					 | 
				
			||||||
        self._orig_data = []
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def set_data(self, book_list):    
 | 
					 | 
				
			||||||
        self._data = book_list
 | 
					 | 
				
			||||||
        self._orig_data = book_list
 | 
					 | 
				
			||||||
        self.reset()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def rowCount(self, parent): 
 | 
					 | 
				
			||||||
        return len(self._data)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def columnCount(self, parent): 
 | 
					 | 
				
			||||||
        return 4
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def headerData(self, section, orientation, role):
 | 
					 | 
				
			||||||
        if role != Qt.DisplayRole:
 | 
					 | 
				
			||||||
            return NONE
 | 
					 | 
				
			||||||
        text = ""
 | 
					 | 
				
			||||||
        if orientation == Qt.Horizontal:      
 | 
					 | 
				
			||||||
            if section == 0: text = "Title"
 | 
					 | 
				
			||||||
            elif section == 1: text = "Author(s)"
 | 
					 | 
				
			||||||
            elif section == 2: text = "Size"
 | 
					 | 
				
			||||||
            elif section == 3: text = "Date"
 | 
					 | 
				
			||||||
            return QVariant(self.trUtf8(text))
 | 
					 | 
				
			||||||
        else: return QVariant(str(1+section))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def data(self, index, role):    
 | 
					 | 
				
			||||||
        if role == Qt.DisplayRole:
 | 
					 | 
				
			||||||
            row, col = index.row(), index.column()
 | 
					 | 
				
			||||||
            book = self._data[row]
 | 
					 | 
				
			||||||
            if col == 0: 
 | 
					 | 
				
			||||||
                text = TableView.wrap(book.title, width=40)
 | 
					 | 
				
			||||||
            elif col == 1:
 | 
					 | 
				
			||||||
                au = book.author
 | 
					 | 
				
			||||||
                au = au.split("&")
 | 
					 | 
				
			||||||
                jau = [ TableView.wrap(a, width=25).strip() for a in au ]
 | 
					 | 
				
			||||||
                text = "\n".join(jau)                
 | 
					 | 
				
			||||||
            elif col == 2: 
 | 
					 | 
				
			||||||
                text = TableView.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 | Qt.AlignVCenter)
 | 
					 | 
				
			||||||
        return NONE
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def info(self, row):
 | 
					 | 
				
			||||||
        row = self._data[row]
 | 
					 | 
				
			||||||
        cover = None
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            cover = row.thumbnail
 | 
					 | 
				
			||||||
            pix = QPixmap()
 | 
					 | 
				
			||||||
            pix.loadFromData(cover, "", Qt.AutoColor)
 | 
					 | 
				
			||||||
            cover = None if pix.isNull() else pix
 | 
					 | 
				
			||||||
        except: 
 | 
					 | 
				
			||||||
            traceback.print_exc()
 | 
					 | 
				
			||||||
        au = row.author if row.author else "Unknown"
 | 
					 | 
				
			||||||
        return row.title, au, TableView.human_readable(row.size), row.mime, cover
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def sort(self, col, order):
 | 
					 | 
				
			||||||
        def getter(key, func): 
 | 
					 | 
				
			||||||
            return lambda x : func(attrgetter(key)(x))
 | 
					 | 
				
			||||||
        if col == 0: key, func = "title", lambda x : x.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 = "datetime", lambda x: x
 | 
					 | 
				
			||||||
        descending = order != Qt.AscendingOrder
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        self._data.sort(key=getter(key, func))
 | 
					 | 
				
			||||||
        if descending: self._data.reverse()
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("sorted()"))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def search(self, query):
 | 
					 | 
				
			||||||
        queries = unicode(query, 'utf-8').lower().split()
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        self._data = []
 | 
					 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    match = False
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
            if match: self._data.append(book)
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("searched()"))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def delete(self, indices):
 | 
					 | 
				
			||||||
        paths = []
 | 
					 | 
				
			||||||
        rows = [ index.row() for index in indices ]
 | 
					 | 
				
			||||||
        if not rows: 
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					 | 
				
			||||||
        elems = [ self._data[row] for row in rows ]
 | 
					 | 
				
			||||||
        for e in elems:
 | 
					 | 
				
			||||||
            _id = e.id
 | 
					 | 
				
			||||||
            paths.append(e.path)
 | 
					 | 
				
			||||||
            self._orig_data.delete_book(_id)
 | 
					 | 
				
			||||||
            try: 
 | 
					 | 
				
			||||||
                self._data.remove(e)
 | 
					 | 
				
			||||||
            except ValueError: 
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
        return paths
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def path(self, index):  
 | 
					 | 
				
			||||||
        return self._data[index.row()].path
 | 
					 | 
				
			||||||
    def title(self, index): 
 | 
					 | 
				
			||||||
        return self._data[index.row()].title
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DeviceModel(QAbstractListModel):
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    memory_free      = 0
 | 
					 | 
				
			||||||
    card_free        = 0
 | 
					 | 
				
			||||||
    show_reader      = False
 | 
					 | 
				
			||||||
    show_card        = False
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def update_devices(self, reader=None, card=None):
 | 
					 | 
				
			||||||
        if reader != None: 
 | 
					 | 
				
			||||||
            self.show_reader = reader
 | 
					 | 
				
			||||||
        if card != None: 
 | 
					 | 
				
			||||||
            self.show_card = card
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("layoutChanged()"))
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def rowCount(self, parent):
 | 
					 | 
				
			||||||
        base = 1
 | 
					 | 
				
			||||||
        if self.show_reader:
 | 
					 | 
				
			||||||
            base += 1
 | 
					 | 
				
			||||||
        if self.show_card:
 | 
					 | 
				
			||||||
            base += 1
 | 
					 | 
				
			||||||
        return base
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def update_free_space(self, reader, card):
 | 
					 | 
				
			||||||
        self.memory_free = reader
 | 
					 | 
				
			||||||
        self.card_free = card
 | 
					 | 
				
			||||||
        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
 | 
					 | 
				
			||||||
                        self.index(1), self.index(2))
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def data(self, index, role):
 | 
					    def data(self, index, role):
 | 
				
			||||||
        row = index.row()    
 | 
					        row = index.row()    
 | 
				
			||||||
        data = NONE
 | 
					        data = NONE
 | 
				
			||||||
        if role == Qt.DisplayRole:
 | 
					        if role == Qt.DisplayRole:
 | 
				
			||||||
            text = None
 | 
					            text = self.text[row]%(human_readable(self.free[row-1])) if row > 0 \
 | 
				
			||||||
            if row == 0: 
 | 
					                            else self.text[row]
 | 
				
			||||||
                text = "Library"  
 | 
					 | 
				
			||||||
            if row == 1 and self.show_reader: 
 | 
					 | 
				
			||||||
                text = "Reader\n" + TableView.human_readable(self.memory_free) \
 | 
					 | 
				
			||||||
                                  + " available"        
 | 
					 | 
				
			||||||
            elif row == 2 and self.show_card: 
 | 
					 | 
				
			||||||
                text = "Card\n" + TableView.human_readable(self.card_free) \
 | 
					 | 
				
			||||||
                                + " available"
 | 
					 | 
				
			||||||
            if text: 
 | 
					 | 
				
			||||||
            data = QVariant(text)
 | 
					            data = QVariant(text)
 | 
				
			||||||
        elif role == Qt.DecorationRole:                
 | 
					        elif role == Qt.DecorationRole:                
 | 
				
			||||||
            icon = None
 | 
					            data = self.icons[row]
 | 
				
			||||||
            if row == 0: 
 | 
					 | 
				
			||||||
                icon = QIcon(":/library")
 | 
					 | 
				
			||||||
            elif row == 1 and self.show_reader: 
 | 
					 | 
				
			||||||
                icon =  QIcon(":/reader")
 | 
					 | 
				
			||||||
            elif self.show_card: 
 | 
					 | 
				
			||||||
                icon = QIcon(":/card")
 | 
					 | 
				
			||||||
            if icon: 
 | 
					 | 
				
			||||||
                data = QVariant(icon)
 | 
					 | 
				
			||||||
        elif role == Qt.SizeHintRole:
 | 
					        elif role == Qt.SizeHintRole:
 | 
				
			||||||
            if row == 1: 
 | 
					            if row == 1: 
 | 
				
			||||||
                return QVariant(QSize(150, 70))
 | 
					                return QVariant(QSize(150, 70))
 | 
				
			||||||
@ -844,20 +52,21 @@ class DeviceModel(QAbstractListModel):
 | 
				
			|||||||
            data =  QVariant(font)
 | 
					            data =  QVariant(font)
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def is_library(self, index): 
 | 
					    def headerData(self, section, orientation, role):
 | 
				
			||||||
        return index.row() == 0
 | 
					        return NONE
 | 
				
			||||||
    def is_reader(self, index): 
 | 
					    
 | 
				
			||||||
        return index.row() == 1
 | 
					    def update_devices(self, cp, fs):
 | 
				
			||||||
    def is_card(self, index): 
 | 
					        self.free[0] = fs[0]
 | 
				
			||||||
        return index.row() == 2
 | 
					        self.free[1] = max(fs[1:])
 | 
				
			||||||
 | 
					        if cp == None:
 | 
				
			||||||
 | 
					            self.free[1] = -1
 | 
				
			||||||
 | 
					        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), 
 | 
				
			||||||
 | 
					                  self.index(1), self.index(2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LocationView(QListView):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def __init__(self, parent):
 | 
				
			||||||
 | 
					        QListView.__init__(self, parent)
 | 
				
			||||||
 | 
					        self.setModel(LocationModel(self))
 | 
				
			||||||
 | 
					        self.reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def files_dropped(self, files, index, ids):    
 | 
					 | 
				
			||||||
        ret = False
 | 
					 | 
				
			||||||
        if self.is_library(index) and not ids: 
 | 
					 | 
				
			||||||
            self.emit(SIGNAL("books_dropped"), files)
 | 
					 | 
				
			||||||
            ret = True
 | 
					 | 
				
			||||||
        elif self.is_reader(index):
 | 
					 | 
				
			||||||
            self.emit(SIGNAL("upload_books"), "reader", files, ids) 
 | 
					 | 
				
			||||||
        elif self.is_card(index):
 | 
					 | 
				
			||||||
            self.emit(SIGNAL("upload_books"), "card", files, ids) 
 | 
					 | 
				
			||||||
        return ret
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user