def get_backend(self, uri, client): '''Given the filename of an image, returns (fname, backend) where fname is the filename to be used (could be the same as filename, or something different if the image had to be converted or is missing), and backend is an Image class that can handle fname.''' backend = defaultimage # Extract all the information from the URI filename, extension, options = self.split_uri(uri) # If the image doesn't exist, we use a 'missing' image if not os.path.exists(filename): log.error("Missing image file: %s", filename) filename = missing if extension in ['svg', 'svgz']: if SVGImage.available(): log.info('Backend for %s is SVGIMage' % filename) backend = SVGImage else: filename = missing elif extension in [ 'ai', 'ccx', 'cdr', 'cgm', 'cmx', 'fig', 'sk1', 'sk', 'xml', 'wmf' ]: log.info('Backend for %s is VectorImage' % filename) backend = VectorImage elif extension in ['pdf']: if VectorPdf is not None and filename is not missing: backend = VectorPdf filename = uri # PDF images are implemented by converting via PythonMagick # w,h are in pixels. I need to set the density # of the image to the right dpi so this # looks decent elif LazyImports.PMImage or LazyImports.gfx: filename = self.raster(filename, client) else: log.warning("Minimal PDF image support "\ "requires PythonMagick [%s]", filename) filename = missing elif extension != 'jpg' and not LazyImports.PILImage: if LazyImports.PMImage: # Need to convert to JPG via PythonMagick filename = self.raster(filename) else: # No way to make this work log.error('To use a %s image you need PIL installed [%s]', extension, filename) filename = missing return filename, backend
def parse(self, str): """Parse equation object string representation.""" str = str.rstrip("\r\n") l = str.split(';') if len(l) != 5: _logger.error(_('Equation.parse() string invalid (%s)'), str) return False if l[2].startswith("<svg>"): l[2] = SVGImage(data=base64.b64decode(l[2][5:])) # Should figure out how to use MathLib directly in a non-hacky way else: try: l[2] = Decimal(l[2]) except Exception, inst: pass
def parse(self, str): """Parse equation object string representation.""" str = str.rstrip("\r\n") k = str.split(';') if len(k) != 5: _logger.error(_('Equation.parse() string invalid (%s)'), str) return False if k[2].startswith("<svg>"): k[2] = SVGImage(data=base64.b64decode(k[2][5:])) # Should figure out how to use MathLib directly in a non-hacky way else: try: k[2] = Decimal(k[2]) except Exception: pass self.set(k[0], k[1], k[2], XoColor(color_string=k[3]), k[4])
def process(self): """Parse the equation entered and show the result.""" s = _s(self.text_entry.get_text()) label = self.label_entry.get_text() _logger.debug('process(): parsing %r, label: %r', s, label) try: tree = self.parser.parse(s) res = self.parser.evaluate(tree) except ParserError as e: res = e self.showing_error = True if isinstance(res, str) and res.find('</svg>') > -1: res = SVGImage(data=res) _logger.debug('Result: %r', res) # Check whether assigning this label would cause recursion if not isinstance(res, ParserError) and len(label) > 0: lastpos = self.parser.get_var_used_ofs(label) if lastpos is not None: res = RuntimeError( _('Can not assign label: will cause recursion'), lastpos) # If parsing went ok, see if we have to replace the previous answer # to get a (more) exact result if self.ans_inserted and not isinstance(res, ParserError) \ and not isinstance(res, SVGImage): ansvar = self.format_insert_ans() pos = s.find(ansvar) if len(ansvar) > 6 and pos != -1: s2 = s.replace(ansvar, 'LastEqn') _logger.debug( 'process(): replacing previous answer %r: %r', ansvar, s2) tree = self.parser.parse(s2) res = self.parser.evaluate(tree) if isinstance(res, ParserError): eqn = Equation(label, _n(s), res, self.color, self.get_owner_id(), ml=self.ml) self.set_error_equation(eqn) else: eqn = Equation(label, _n(s), _n(str(res)), self.color, self.get_owner_id(), ml=self.ml) self.add_equation(eqn, drawlasteq=True, tree=tree) self.send_message("add_eq", value=str(eqn)) self.parser.set_var('Ans', eqn.result) # Setting LastEqn to the parse tree would certainly be faster, # however, it introduces recursion problems self.parser.set_var('LastEqn', eqn.result) self.showing_error = False self.ans_inserted = False self.text_entry.set_text('') self.label_entry.set_text('') return res is not None
def size_for_node(self, node, client): '''Given a docutils image node, returns the size the image should have in the PDF document, and what 'kind' of size that is. That involves lots of guesswork''' uri = os.path.join(client.basedir, str(node.get("uri"))) srcinfo = client, uri # Extract all the information from the URI imgname, extension, options = self.split_uri(uri) if not os.path.isfile(imgname): imgname = missing scale = float(node.get('scale', 100)) / 100 size_known = False # Figuring out the size to display of an image is ... annoying. # If the user provides a size with a unit, it's simple, adjustUnits # will return it in points and we're done. # However, often the unit wil be "%" (specially if it's meant for # HTML originally. In which case, we will use a percentage of # the containing frame. # Find the image size in pixels: kind = 'direct' xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi extension = imgname.split('.')[-1].lower() if extension in ['svg', 'svgz'] and SVGImage.available(): iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension in [ "ai", "ccx", "cdr", "cgm", "cmx", "sk1", "sk", "xml", "wmf", "fig" ] and VectorImage.available(): iw, ih = VectorImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension == 'pdf': if VectorPdf is not None: box = VectorPdf.load_xobj(srcinfo).BBox else: pdf = LazyImports.pdfinfo if pdf is None: log.warning( 'PDF images are not supported without pyPdf or pdfrw [%s]', nodeid(node)) return 0, 0, 'direct' reader = pdf.PdfFileReader(open(imgname, 'rb')) box = [float(x) for x in reader.getPage(0)['/MediaBox']] x1, y1, x2, y2 = box # These are in pt, so convert to px iw = float((x2 - x1) * xdpi / 72) ih = float((y2 - y1) * ydpi / 72) size_known = True # Assume size from original PDF is OK else: keeptrying = True if LazyImports.PILImage: try: img = LazyImports.PILImage.open(imgname) img.load() iw, ih = img.size xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi)) keeptrying = False except IOError: # PIL throws this when it's a broken/unknown image pass if keeptrying and LazyImports.PMImage: img = LazyImports.PMImage(imgname) iw = img.size().width() ih = img.size().height() density = img.density() # The density is in pixelspercentimeter (!?) xdpi = density.width() * 2.54 ydpi = density.height() * 2.54 keeptrying = False if keeptrying: log.error( "The image (%s, %s) is broken or in an unknown format", imgname, nodeid(node)) raise ValueError # Try to get the print resolution from the image itself via PIL. # If it fails, assume a DPI of 300, which is pretty much made up, # and then a 100% size would be iw*inch/300, so we pass # that as the second parameter to adjustUnits # # Some say the default DPI should be 72. That would mean # the largest printable image in A4 paper would be something # like 480x640. That would be awful. # w = node.get('width') h = node.get('height') if h is None and w is None: # Nothing specified # Guess from iw, ih log.warning( "Using image %s without specifying size." "Calculating based on image size at %ddpi [%s]", imgname, xdpi, nodeid(node)) w = iw * inch / xdpi h = ih * inch / ydpi elif w is not None: # Node specifies only w # In this particular case, we want the default unit # to be pixels so we work like rst2html if w[-1] == '%': kind = 'percentage_of_container' w = int(w[:-1]) else: # This uses default DPI setting because we # are not using the image's "natural size" # this is what LaTeX does, according to the # docutils mailing list discussion w = client.styles.adjustUnits(w, client.styles.tw, default_unit='px') if h is None: # h is set from w with right aspect ratio h = w * ih / iw else: h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px') elif h is not None and w is None: if h[-1] != '%': h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px') # w is set from h with right aspect ratio w = h * iw / ih else: log.error('Setting height as a percentage does **not** work. '\ 'ignoring height parameter [%s]', nodeid(node)) # Set both from image data w = iw * inch / xdpi h = ih * inch / ydpi # Apply scale factor w = w * scale h = h * scale # And now we have this probably completely bogus size! log.info("Image %s size calculated: %fcm by %fcm [%s]", imgname, w / cm, h / cm, nodeid(node)) return w, h, kind
def process(self): """Parse the equation entered and show the result""" s = unicode(self.text_entry.get_text()) label = unicode(self.label_entry.get_text()) _logger.debug('process(): parsing %r, label: %r', s, label) try: tree = self.parser.parse(s) res = self.parser.evaluate(tree) except ParserError, e: res = e self.showing_error = True if isinstance(res, str) and res.find('</svg>') > -1: res = SVGImage(data=res) _logger.debug('Result: %r', res) # Check whether assigning this label would cause recursion if not isinstance(res, ParserError) and len(label) > 0: lastpos = self.parser.get_var_used_ofs(label) if lastpos is not None: res = RuntimeError( _('Can not assign label: will cause recursion'), lastpos) # If parsing went ok, see if we have to replace the previous answer # to get a (more) exact result if self.ans_inserted and not isinstance(res, ParserError) \ and not isinstance(res, SVGImage): ansvar = self.format_insert_ans()
def get_backend(self, uri, client): '''Given the filename of an image, returns (fname, backend) where fname is the filename to be used (could be the same as filename, or something different if the image had to be converted or is missing), and backend is an Image class that can handle fname. If uri ensd with '.*' then the returned filename will be the best quality supported at the moment. That means: PDF > SVG > anything else ''' backend = defaultimage # Extract all the information from the URI filename, extension, options = self.split_uri(uri) if '*' in filename: preferred = ['gif', 'jpg', 'png'] if SVGImage.available(): preferred.append('svg') preferred.append('pdf') # Find out what images are available available = glob.glob(filename) cfn = available[0] cv = -10 for fn in available: ext = fn.split('.')[-1] if ext in preferred: v = preferred.index(ext) else: v = -1 if v > cv: cv = v cfn = fn # cfn should have our favourite type of # those available filename = cfn extension = cfn.split('.')[-1] uri = filename # If the image doesn't exist, we use a 'missing' image if not os.path.exists(filename): log.error("Missing image file: %s", filename) filename = missing if extension in ['svg', 'svgz']: if SVGImage.available(): log.info('Backend for %s is SVGIMage' % filename) backend = SVGImage else: filename = missing elif extension in ['pdf']: if VectorPdf is not None and filename is not missing: backend = VectorPdf filename = uri # PDF images are implemented by converting via PythonMagick # w,h are in pixels. I need to set the density # of the image to the right dpi so this # looks decent elif LazyImports.PMImage or LazyImports.gfx: filename = self.raster(filename, client) else: log.warning("Minimal PDF image support "\ "requires PythonMagick or the vectorpdf extension [%s]", filename) filename = missing elif extension != 'jpg' and not LazyImports.PILImage: if LazyImports.PMImage: # Need to convert to JPG via PythonMagick filename = self.raster(filename, client) else: # No way to make this work log.error('To use a %s image you need PIL installed [%s]', extension, filename) filename = missing return filename, backend
def size_for_node(self, node, client): '''Given a docutils image node, returns the size the image should have in the PDF document, and what 'kind' of size that is. That involves lots of guesswork''' uri = str(node.get("uri")) if uri.split("://")[0].lower() not in ('http','ftp','https'): uri = os.path.join(client.basedir,uri) else: uri, _ = urllib.urlretrieve(uri) client.to_unlink.append(uri) srcinfo = client, uri # Extract all the information from the URI imgname, extension, options = self.split_uri(uri) if not os.path.isfile(imgname): imgname = missing scale = float(node.get('scale', 100))/100 size_known = False # Figuring out the size to display of an image is ... annoying. # If the user provides a size with a unit, it's simple, adjustUnits # will return it in points and we're done. # However, often the unit wil be "%" (specially if it's meant for # HTML originally. In which case, we will use a percentage of # the containing frame. # Find the image size in pixels: kind = 'direct' xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi extension = imgname.split('.')[-1].lower() if extension in ['svg','svgz'] and SVGImage.available(): iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension in [ "ai", "ccx", "cdr", "cgm", "cmx", "sk1", "sk", "xml", "wmf", "fig"] and VectorImage.available(): iw, ih = VectorImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension == 'pdf': if VectorPdf is not None: box = VectorPdf.load_xobj(srcinfo).BBox else: pdf = LazyImports.pdfinfo if pdf is None: log.warning('PDF images are not supported without pyPdf or pdfrw [%s]', nodeid(node)) return 0, 0, 'direct' reader = pdf.PdfFileReader(open(imgname, 'rb')) box = [float(x) for x in reader.getPage(0)['/MediaBox']] x1, y1, x2, y2 = box # These are in pt, so convert to px iw = float((x2-x1) * xdpi / 72) ih = float((y2-y1) * ydpi / 72) size_known = True # Assume size from original PDF is OK else: keeptrying = True if sys.platform[0:4] == 'java': try: from javax.imageio import ImageIO from java.io import File iis = ImageIO.createImageInputStream(File(imgname)) readers = ImageIO.getImageReaders(iis) if readers.hasNext(): reader = readers.next() reader.setInput(iis, True) metadata = reader.getImageMetadata(0) iw = reader.getWidth(0) ih = reader.getHeight(0) xdpi = None ydpi = None if metadata.getNativeMetadataFormatName() == 'javax_imageio_png_1.0': png_metadata = metadata.getAsTree(metadata.getNativeMetadataFormatName()) if png_metadata is not None: phys_metadata = png_metadata.getElementsByTagName('pHYs') if phys_metadata.getLength() == 1: xdpi = phys_metadata.item(0).getAttribute('pixelsPerUnitXAxis') if xdpi is not None: xdpi = float(xdpi) ydpi = phys_metadata.item(0).getAttribute('pixelsPerUnitYAxis') if ydpi is not None: ydpi = float(ydpi) elif metadata.getNativeMetadataFormatName() == 'javax_imageio_jpeg_image_1.0': jpg_metadata = metadata.getAsTree(metadata.getNativeMetadataFormatName()) if jpg_metadata is not None: jfif_metadata = jpg_metadata.getElementsByTagName('app0JFIF') if jfif_metadata.getLength() == 1: resUnits = jfif_metadata.item(0).getAttribute('resUnits') # 0 = plain aspect ratio, 1 = dots per inch, 2 = dots per cm if resUnits is not None and (resUnits == '1' or resUnits == '2'): xdpi = jfif_metadata.item(0).getAttribute('pixelsPerUnitXAxis') if xdpi is not None: xdpi = float(xdpi) if resUnits == '2': xdpi = xdpi * 2.54 ydpi = jfif_metadata.item(0).getAttribute('pixelsPerUnitYAxis') if ydpi is not None: ydpi = float(ydpi) if resUnits == '2': ydpi = ydpi * 2.54 if xdpi is None: xdpi = 300 if ydpi is None: ydpi = 300 keeptrying = False except Exception, err: log.error('Error %s', str(err)) pass if LazyImports.PILImage: try: img = LazyImports.PILImage.open(imgname) img.load() iw, ih = img.size xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi)) keeptrying = False except IOError: # PIL throws this when it's a broken/unknown image pass if keeptrying and LazyImports.PMImage: img = LazyImports.PMImage(imgname) iw = img.size().width() ih = img.size().height() density=img.density() # The density is in pixelspercentimeter (!?) xdpi=density.width()*2.54 ydpi=density.height()*2.54 keeptrying = False if keeptrying: if extension not in ['jpg', 'jpeg']: log.error("The image (%s, %s) is broken or in an unknown format" , imgname, nodeid(node)) raise ValueError else: # Can be handled by reportlab log.warning("Can't figure out size of the image (%s, %s). Install PIL for better results." , imgname, nodeid(node)) iw = 1000 ih = 1000
def get_backend(self, uri, client): '''Given the filename of an image, returns (fname, backend) where fname is the filename to be used (could be the same as filename, or something different if the image had to be converted or is missing), and backend is an Image class that can handle fname. If uri ensd with '.*' then the returned filename will be the best quality supported at the moment. That means: PDF > SVG > anything else ''' backend = defaultimage # Extract all the information from the URI filename, extension, options = self.split_uri(uri) if '*' in filename: preferred=['gif','jpg','png'] if SVGImage.available(): preferred.append('svg') preferred.append('pdf') # Find out what images are available available = glob.glob(filename) cfn=available[0] cv=-10 for fn in available: ext=fn.split('.')[-1] if ext in preferred: v=preferred.index(ext) else: v=-1 if v > cv: cv=v cfn=fn # cfn should have our favourite type of # those available filename = cfn extension = cfn.split('.')[-1] uri = filename # If the image doesn't exist, we use a 'missing' image if not os.path.exists(filename): log.error("Missing image file: %s",filename) filename = missing if extension in ['svg','svgz']: if SVGImage.available(): log.info('Backend for %s is SVGIMage'%filename) backend=SVGImage else: filename = missing elif extension in ['ai', 'ccx', 'cdr', 'cgm', 'cmx', 'fig', 'sk1', 'sk', 'xml', 'wmf']: log.info('Backend for %s is VectorImage'%filename) backend=VectorImage elif extension in ['pdf']: if VectorPdf is not None and filename is not missing: backend = VectorPdf filename = uri # PDF images are implemented by converting via PythonMagick # w,h are in pixels. I need to set the density # of the image to the right dpi so this # looks decent elif LazyImports.PMImage or LazyImports.gfx: filename=self.raster(filename, client) else: log.warning("Minimal PDF image support "\ "requires PythonMagick or the vectorpdf extension [%s]", filename) filename = missing elif extension != 'jpg' and not LazyImports.PILImage and not sys.platform[0:4] == 'java': if LazyImports.PMImage: # Need to convert to JPG via PythonMagick filename=self.raster(filename) else: # No way to make this work log.error('To use a %s image you need PIL installed [%s]',extension,filename) filename=missing return filename, backend
def size_for_node(self, node, client): '''Given a docutils image node, returns the size the image should have in the PDF document, and what 'kind' of size that is. That involves lots of guesswork''' uri = str(node.get("uri")) if uri.split("://")[0].lower() not in ('http','ftp','https'): uri = os.path.join(client.basedir,uri) else: uri, _ = urllib.urlretrieve(uri) client.to_unlink.append(uri) srcinfo = client, uri # Extract all the information from the URI imgname, extension, options = self.split_uri(uri) if not os.path.isfile(imgname): imgname = missing scale = float(node.get('scale', 100))/100 size_known = False # Figuring out the size to display of an image is ... annoying. # If the user provides a size with a unit, it's simple, adjustUnits # will return it in points and we're done. # However, often the unit wil be "%" (specially if it's meant for # HTML originally. In which case, we will use a percentage of # the containing frame. # Find the image size in pixels: kind = 'direct' xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi extension = imgname.split('.')[-1].lower() if extension in ['svg','svgz'] and SVGImage.available(): iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension in [ "ai", "ccx", "cdr", "cgm", "cmx", "sk1", "sk", "xml", "wmf", "fig"] and VectorImage.available(): iw, ih = VectorImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 ih = ih * ydpi / 72 elif extension == 'pdf': if VectorPdf is not None: box = VectorPdf.load_xobj(srcinfo).BBox else: pdf = LazyImports.pdfinfo if pdf is None: log.warning('PDF images are not supported without pyPdf or pdfrw [%s]', nodeid(node)) return 0, 0, 'direct' reader = pdf.PdfFileReader(open(imgname, 'rb')) box = [float(x) for x in reader.getPage(0)['/MediaBox']] x1, y1, x2, y2 = box # These are in pt, so convert to px iw = float((x2-x1) * xdpi / 72) ih = float((y2-y1) * ydpi / 72) size_known = True # Assume size from original PDF is OK else: keeptrying = True if LazyImports.PILImage: try: img = LazyImports.PILImage.open(imgname) img.load() iw, ih = img.size xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi)) keeptrying = False except IOError: # PIL throws this when it's a broken/unknown image pass if keeptrying and LazyImports.PMImage: img = LazyImports.PMImage(imgname) iw = img.size().width() ih = img.size().height() density=img.density() # The density is in pixelspercentimeter (!?) xdpi=density.width()*2.54 ydpi=density.height()*2.54 keeptrying = False if keeptrying: if extension not in ['jpg', 'jpeg']: log.error("The image (%s, %s) is broken or in an unknown format" , imgname, nodeid(node)) raise ValueError else: # Can be handled by reportlab log.warning("Can't figure out size of the image (%s, %s). Install PIL for better results." , imgname, nodeid(node)) iw = 1000 ih = 1000 # Try to get the print resolution from the image itself via PIL. # If it fails, assume a DPI of 300, which is pretty much made up, # and then a 100% size would be iw*inch/300, so we pass # that as the second parameter to adjustUnits # # Some say the default DPI should be 72. That would mean # the largest printable image in A4 paper would be something # like 480x640. That would be awful. # w = node.get('width') h = node.get('height') if h is None and w is None: # Nothing specified # Guess from iw, ih log.debug("Using image %s without specifying size." "Calculating based on image size at %ddpi [%s]", imgname, xdpi, nodeid(node)) w = iw*inch/xdpi h = ih*inch/ydpi elif w is not None: # Node specifies only w # In this particular case, we want the default unit # to be pixels so we work like rst2html if w[-1] == '%': kind = 'percentage_of_container' w=int(w[:-1]) else: # This uses default DPI setting because we # are not using the image's "natural size" # this is what LaTeX does, according to the # docutils mailing list discussion w = client.styles.adjustUnits(w, client.styles.tw, default_unit='px') if h is None: # h is set from w with right aspect ratio h = w*ih/iw else: h = client.styles.adjustUnits(h, ih*inch/ydpi, default_unit='px') elif h is not None and w is None: if h[-1] != '%': h = client.styles.adjustUnits(h, ih*inch/ydpi, default_unit='px') # w is set from h with right aspect ratio w = h*iw/ih else: log.error('Setting height as a percentage does **not** work. '\ 'ignoring height parameter [%s]', nodeid(node)) # Set both from image data w = iw*inch/xdpi h = ih*inch/ydpi # Apply scale factor w = w*scale h = h*scale # And now we have this probably completely bogus size! log.info("Image %s size calculated: %fcm by %fcm [%s]", imgname, w/cm, h/cm, nodeid(node)) return w, h, kind
class Calculate(ShareableActivity): TYPE_FUNCTION = 1 TYPE_OP_PRE = 2 TYPE_OP_POST = 3 TYPE_TEXT = 4 SELECT_NONE = 0 SELECT_SELECT = 1 SELECT_TAB = 2 KEYMAP = { 'Return': lambda o: o.process(), 'period': '.', 'equal': '=', 'plus': '+', 'minus': '-', 'asterisk': '*', 'multiply': '×', 'divide': '÷', 'slash': '/', 'BackSpace': lambda o: o.remove_character(-1), 'Delete': lambda o: o.remove_character(1), 'parenleft': '(', 'parenright': ')', 'exclam': '!', 'ampersand': '&', 'bar': '|', 'asciicircum': '^', 'less': '<', 'greater': '>', 'percent': '%', 'comma': ',', 'underscore': '_', 'Left': lambda o: o.move_left(), 'Right': lambda o: o.move_right(), 'Up': lambda o: o.get_older(), 'Down': lambda o: o.get_newer(), 'colon': lambda o: o.label_entered(), 'Home': lambda o: o.text_entry.set_position(0), 'End': lambda o: o.text_entry.set_position(len(o.text_entry.get_text())), 'Tab': lambda o: o.tab_complete(), } CTRL_KEYMAP = { 'c': lambda o: o.text_copy(), 'v': lambda o: o.text_paste(), 'x': lambda o: o.text_cut(), 'q': lambda o: o.close(), 'a': lambda o: o.text_select_all(), } SHIFT_KEYMAP = { 'Left': lambda o: o.expand_selection(-1), 'Right': lambda o: o.expand_selection(1), 'Home': lambda o: o.expand_selection(-1000), 'End': lambda o: o.expand_selection(1000), } IDENTIFIER_CHARS = \ u"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_ " def __init__(self, handle): ShareableActivity.__init__(self, handle) self.old_eqs = [] self.ml = MathLib() self.parser = AstParser(self.ml) # These will result in 'Ans <operator character>' being inserted self._chars_ans_diadic = [ op[0] for op in self.parser.get_diadic_operators() ] try: self._chars_ans_diadic.remove('-') except: pass self.KEYMAP['multiply'] = self.ml.mul_sym self.KEYMAP['divide'] = self.ml.div_sym self.KEYMAP['equal'] = self.ml.equ_sym self.clipboard = gtk.Clipboard() self.select_reason = self.SELECT_SELECT self.buffer = u"" self.showing_version = 0 self.showing_error = False self.ans_inserted = False self.show_vars = False self.connect("key_press_event", self.keypress_cb) self.connect("destroy", self.cleanup_cb) self.color = sugar.profile.get_color() self.layout = CalcLayout(self) self.label_entry = self.layout.label_entry self.text_entry = self.layout.text_entry self.last_eq_sig = None self.last_eqn_textview = None self.reset() self.layout.show_it() self.connect('joined', self._joined_cb) self.parser.log_debug_info() def ignore_key_cb(self, widget, event): return True def cleanup_cb(self, arg): _logger.debug('Cleaning up...') def equation_pressed_cb(self, eqn): """Callback for when an equation box is clicked""" if isinstance(eqn.result, SVGImage): return True if len(eqn.label) > 0: text = eqn.label else: # don't insert plain text if type(eqn.result) in (types.StringType, types.UnicodeType): text = '' else: text = self.parser.ml.format_number(eqn.result) self.button_pressed(self.TYPE_TEXT, text) return True def set_last_equation(self, eqn): """Set the 'last equation' TextView""" if self.last_eq_sig is not None: self.layout.last_eq.disconnect(self.last_eq_sig) self.last_eq_sig = None if not isinstance(eqn.result, ParserError): self.last_eq_sig = self.layout.last_eq.connect( 'button-press-event', lambda a1, a2, e: self.equation_pressed_cb(e), eqn) self.layout.last_eq.set_buffer(eqn.create_lasteq_textbuf()) def set_error_equation(self, eqn): """Set equation with error markers. Since set_last_equation implements this we can just forward the call.""" self.set_last_equation(eqn) def clear_equations(self): """Clear the list of old equations.""" self.old_eqs = [] self.showing_version = 0 def add_equation(self, eq, prepend=False, drawlasteq=False, tree=None): """ Insert equation in the history list and set variable if assignment. Input: eq: the equation object prepend: if True, prepend to list, else append drawlasteq: if True, draw in 'last equation' textbox and queue the buffer to be added to the history next time an equation is added. tree: the parsed tree, this will be used to set the label variable so that the equation can be used symbolicaly. """ if eq.equation is not None and len(eq.equation) > 0: if prepend: self.old_eqs.insert(0, eq) else: self.old_eqs.append(eq) self.showing_version = len(self.old_eqs) if self.last_eqn_textview is not None and drawlasteq: # Prepending here should be the opposite: prepend -> eqn on top. # We always own this equation self.layout.add_equation(self.last_eqn_textview, True, prepend=not prepend) self.last_eqn_textview = None own = (eq.owner == self.get_owner_id()) w = eq.create_history_object() w.connect('button-press-event', lambda w, e: self.equation_pressed_cb(eq)) if drawlasteq: self.set_last_equation(eq) # SVG images can't be plotted in last equation window if isinstance(eq.result, SVGImage): self.layout.add_equation(w, own, prepend=not prepend) else: self.last_eqn_textview = w else: self.layout.add_equation(w, own, prepend=not prepend) if eq.label is not None and len(eq.label) > 0: w = self.create_var_textview(eq.label, eq.result) if w is not None: self.layout.add_variable(eq.label, w) if tree is None: tree = self.parser.parse(eq.equation) self.parser.set_var(eq.label, tree) # FIXME: to be implemented def process_async(self, eqn): """Parse and process an equation asynchronously.""" def process(self): """Parse the equation entered and show the result""" s = unicode(self.text_entry.get_text()) label = unicode(self.label_entry.get_text()) _logger.debug('process(): parsing %r, label: %r', s, label) try: tree = self.parser.parse(s) res = self.parser.evaluate(tree) except ParserError, e: res = e self.showing_error = True if isinstance(res, str) and res.find('</svg>') > -1: res = SVGImage(data=res) _logger.debug('Result: %r', res) # Check whether assigning this label would cause recursion if not isinstance(res, ParserError) and len(label) > 0: lastpos = self.parser.get_var_used_ofs(label) if lastpos is not None: res = RuntimeError( _('Can not assign label: will cause recursion'), lastpos) # If parsing went ok, see if we have to replace the previous answer # to get a (more) exact result if self.ans_inserted and not isinstance(res, ParserError) \ and not isinstance(res, SVGImage): ansvar = self.format_insert_ans() pos = s.find(ansvar) if len(ansvar) > 6 and pos != -1: s2 = s.replace(ansvar, 'LastEqn') _logger.debug('process(): replacing previous answer %r: %r', ansvar, s2) tree = self.parser.parse(s2) res = self.parser.evaluate(tree) eqn = Equation(label, s, res, self.color, self.get_owner_id(), ml=self.ml) if isinstance(res, ParserError): self.set_error_equation(eqn) else: self.add_equation(eqn, drawlasteq=True, tree=tree) self.send_message("add_eq", value=str(eqn)) self.parser.set_var('Ans', eqn.result) # Setting LastEqn to the parse tree would certainly be faster, # however, it introduces recursion problems self.parser.set_var('LastEqn', eqn.result) self.showing_error = False self.ans_inserted = False self.text_entry.set_text(u'') self.label_entry.set_text(u'') return res is not None