diff --git a/imgsrc/trim.svg b/imgsrc/trim.svg new file mode 100644 index 0000000000..8c8810fc66 --- /dev/null +++ b/imgsrc/trim.svg @@ -0,0 +1,688 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Oxygen team + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/trim.png b/resources/images/trim.png new file mode 100644 index 0000000000..3cb93adfa6 Binary files /dev/null and b/resources/images/trim.png differ diff --git a/resources/recipes/tagesan.recipe b/resources/recipes/tagesan.recipe new file mode 100644 index 0000000000..8514162598 --- /dev/null +++ b/resources/recipes/tagesan.recipe @@ -0,0 +1,45 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1284927619(BasicNewsRecipe): + title = u'Tagesanzeiger' + publisher = u'Tamedia AG' + oldest_article = 2 + __author__ = 'noxxx' + max_articles_per_feed = 100 + description = 'tagesanzeiger.ch: Nichts verpassen' + category = 'News, Politik, Nachrichten, Schweiz, Zürich' + language = 'de' + + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : language + ,'publisher' : publisher + } + + remove_tags = [ + dict(name='img') + ,dict(name='div',attrs={'class':['swissquote ad','boxNews','centerAD','contentTabs2','sbsLabel']}) + ,dict(name='div',attrs={'id':['colRightAd','singleRight','singleSmallRight','MailInfo','metaLine','sidebarSky','contentFooter','commentInfo','commentInfo2','commentInfo3','footerBottom','clear','boxExclusiv','singleLogo','navSearch','headerLogin','headerBottomRight','horizontalNavigation','subnavigation','googleAdSense','footerAd','contentbox','articleGalleryNav']}) + ,dict(name='form',attrs={'id':['articleMailForm','commentform']}) + ,dict(name='div',attrs={'style':['position:absolute']}) + ,dict(name='script',attrs={'type':['text/javascript']}) + ,dict(name='p',attrs={'class':['schreiben','smallPrint','charCounter','caption']}) + ] + feeds = [ + (u'Front', u'http://www.tagesanzeiger.ch/rss.html') + ,(u'Zürich', u'http://www.tagesanzeiger.ch/zuerich/rss.html') + ,(u'Schweiz', u'http://www.tagesanzeiger.ch/schweiz/rss.html') + ,(u'Ausland', u'http://www.tagesanzeiger.ch/ausland/rss.html') + ,(u'Digital', u'http://www.tagesanzeiger.ch/digital/rss.html') + ,(u'Wissen', u'http://www.tagesanzeiger.ch/wissen/rss.html') + ,(u'Panorama', u'http://www.tagesanzeiger.ch/panorama/rss.html') + ,(u'Wirtschaft', u'http://www.tagesanzeiger.ch/wirtschaft/rss.html') + ,(u'Sport', u'http://www.tagesanzeiger.ch/sport/rss.html') + ,(u'Kultur', u'http://www.tagesanzeiger.ch/kultur/rss.html') + ,(u'Leben', u'http://www.tagesanzeiger.ch/leben/rss.html') + ,(u'Auto', u'http://www.tagesanzeiger.ch/auto/rss.html')] + + def print_version(self, url): + return url + '/print.html' + diff --git a/resources/recipes/the_marker.recipe b/resources/recipes/the_marker.recipe new file mode 100644 index 0000000000..e5f1ffc761 --- /dev/null +++ b/resources/recipes/the_marker.recipe @@ -0,0 +1,52 @@ +import re +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1283848012(BasicNewsRecipe): + description = 'TheMarker Financial News in Hebrew' + __author__ = 'TonyTheBookworm, Marbs' + cover_url = 'http://static.ispot.co.il/wp-content/upload/2009/09/themarker.jpg' + title = u'TheMarker' + language = 'he' + simultaneous_downloads = 5 + remove_javascript = True + timefmt = '[%a, %d %b, %Y]' + oldest_article = 1 + remove_tags = [dict(name='tr', attrs={'bgcolor':['#738A94']}) ] + max_articles_per_feed = 10 + extra_css='body{direction: rtl;} .article_description{direction: rtl; } a.article{direction: rtl; } .calibre_feed_description{direction: rtl; }' + feeds = [(u'Head Lines', u'http://www.themarker.com/tmc/content/xml/rss/hpfeed.xml'), + (u'TA Market', u'http://www.themarker.com/tmc/content/xml/rss/sections/marketfeed.xml'), + (u'Real Estate', u'http://www.themarker.com/tmc/content/xml/rss/sections/realEstaterfeed.xml'), + (u'Wall Street & Global', u'http://www.themarker.com/tmc/content/xml/rss/sections/wallsfeed.xml'), + (u'Law', u'http://www.themarker.com/tmc/content/xml/rss/sections/lawfeed.xml'), + (u'Media', u'http://www.themarker.com/tmc/content/xml/rss/sections/mediafeed.xml'), + (u'Consumer', u'http://www.themarker.com/tmc/content/xml/rss/sections/consumerfeed.xml'), + (u'Career', u'http://www.themarker.com/tmc/content/xml/rss/sections/careerfeed.xml'), + (u'Car', u'http://www.themarker.com/tmc/content/xml/rss/sections/carfeed.xml'), + (u'High Tech', u'http://www.themarker.com/tmc/content/xml/rss/sections/hightechfeed.xml'), + (u'Investor Guide', u'http://www.themarker.com/tmc/content/xml/rss/sections/investorGuidefeed.xml')] + + def print_version(self, url): + split1 = url.split("=") + weblinks = url + + if weblinks is not None: + for link in weblinks: + #--------------------------------------------------------- + #here we need some help with some regexpressions + #we are trying to find it.themarker.com in a url + #----------------------------------------------------------- + re1='.*?' # Non-greedy match on filler + re2='(it\\.themarker\\.com)' # Fully Qualified Domain Name 1 + rg = re.compile(re1+re2,re.IGNORECASE|re.DOTALL) + m = rg.search(url) + + + if m: + split2 = url.split("article/") + print_url = 'http://it.themarker.com/tmit/PrintArticle/' + split2[1] + + else: + print_url = 'http://www.themarker.com/ibo/misc/printFriendly.jhtml?ElementId=%2Fibo%2Frepositories%2Fstories%2Fm1_2000%2F' + split1[1]+'.xml' + + return print_url diff --git a/resources/recipes/wsj.recipe b/resources/recipes/wsj.recipe index fd5e977d10..88e07bcea3 100644 --- a/resources/recipes/wsj.recipe +++ b/resources/recipes/wsj.recipe @@ -70,13 +70,16 @@ class WallStreetJournal(BasicNewsRecipe): def wsj_add_feed(self,feeds,title,url): self.log('Found section:', title) - if url.endswith('whatsnews'): - articles = self.wsj_find_wn_articles(url) - else: - articles = self.wsj_find_articles(url) + try: + if url.endswith('whatsnews'): + articles = self.wsj_find_wn_articles(url) + else: + articles = self.wsj_find_articles(url) + except: + articles = [] if articles: feeds.append((title, articles)) - return feeds + return feeds def parse_index(self): soup = self.wsj_get_index() @@ -99,7 +102,7 @@ class WallStreetJournal(BasicNewsRecipe): url = 'http://online.wsj.com' + a['href'] feeds = self.wsj_add_feed(feeds,title,url) title = 'What''s News' - url = url.replace('pageone','whatsnews') + url = url.replace('pageone','whatsnews') feeds = self.wsj_add_feed(feeds,title,url) else: title = self.tag_to_string(a) @@ -141,7 +144,7 @@ class WallStreetJournal(BasicNewsRecipe): articles = [] flavorarea = soup.find('div', attrs={'class':lambda x: x and 'ahed' in x}) - if flavorarea is not None: + if flavorarea is not None: flavorstory = flavorarea.find('a', href=lambda x: x and x.startswith('/article')) if flavorstory is not None: flavorstory['class'] = 'mjLinkItem' diff --git a/resources/recipes/wsj_free.recipe b/resources/recipes/wsj_free.recipe index 7f3664f1c4..df8234e8e2 100644 --- a/resources/recipes/wsj_free.recipe +++ b/resources/recipes/wsj_free.recipe @@ -54,10 +54,13 @@ class WallStreetJournal(BasicNewsRecipe): def wsj_add_feed(self,feeds,title,url): self.log('Found section:', title) - if url.endswith('whatsnews'): - articles = self.wsj_find_wn_articles(url) - else: - articles = self.wsj_find_articles(url) + try: + if url.endswith('whatsnews'): + articles = self.wsj_find_wn_articles(url) + else: + articles = self.wsj_find_articles(url) + except: + articles = [] if articles: feeds.append((title, articles)) return feeds diff --git a/src/calibre/debug.py b/src/calibre/debug.py index c84ce3dfcc..8a2097ddd1 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -37,6 +37,8 @@ Run an embedded python interpreter. parser.add_option('--reinitialize-db', default=None, help='Re-initialize the sqlite calibre database at the ' 'specified path. Useful to recover from db corruption.') + parser.add_option('-p', '--py-console', help='Run python console', + default=False, action='store_true') return parser @@ -148,6 +150,9 @@ def main(args=sys.argv): if len(args) > 1: vargs.append(args[-1]) main(vargs) + elif opts.py_console: + from calibre.utils.pyconsole.main import main + main() elif opts.command: sys.argv = args[:1] exec opts.command diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index c72ad4736f..62507ebfc1 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -443,9 +443,9 @@ class KOBO(USBMS): # Reset Im_Reading list in the database if oncard == 'carda': - query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\'' + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\'' elif oncard != 'carda' and oncard != 'cardb': - query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\'' + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\'' try: cursor.execute (query) diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 8f2550733a..25b6d1aaae 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -241,7 +241,7 @@ OptionRecommendation(name='toc_filter', OptionRecommendation(name='chapter', recommended_value="//*[((name()='h1' or name()='h2') and " - r"re:test(., 'chapter|book|section|part\s+', 'i')) or @class " + r"re:test(., 'chapter|book|section|part|prologue|epilogue\s+', 'i')) or @class " "= 'chapter']", level=OptionRecommendation.LOW, help=_('An XPath expression to detect chapter titles. The default ' 'is to consider

or

tags that contain the words ' diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index b2a3e11b4a..ec5a952346 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -6,8 +6,8 @@ 0 0 - 679 - 685 + 752 + 715 @@ -410,15 +410,15 @@ Future conversion of these books will use the default settings. - - Case sensitive - - - true - Check this box if the search string must match exactly upper and lower case. Uncheck it if case is to be ignored + + Case sensitive + + + true + @@ -511,16 +511,16 @@ field is processed. In regular expression mode, only the matched text is process - - use comma - - - true - If the replace mode is prepend or append, then this box indicates whether a comma or nothing should be put between the original text and the inserted text + + use comma + + + true + diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 26dbda6ca4..53788809b6 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -300,6 +300,24 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.cpixmap = pix self.cover_data = cdata + def trim_cover(self, *args): + from calibre.utils.magick import Image + cdata = self.cover_data + if not cdata: + return + im = Image() + im.load(cdata) + im.trim(10) + cdata = im.export('jpg') + pix = QPixmap() + pix.loadFromData(cdata) + self.cover.setPixmap(pix) + self.cover_changed = True + self.cpixmap = pix + self.cover_data = cdata + + + def sync_formats(self): old_extensions, new_extensions, paths = set(), set(), {} for row in range(self.formats.count()): @@ -380,6 +398,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.remove_unused_series) QObject.connect(self.auto_author_sort, SIGNAL('clicked()'), self.deduce_author_sort) + self.trim_cover_button.clicked.connect(self.trim_cover) self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'), self.author_sort_box_changed) self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'), diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 74febf9c29..dbf825e706 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -625,6 +625,17 @@ Using this button to create author sort will change author sort from red to gree + + + + Remove border (if any) from cover + + + + :/images/trim.png:/images/trim.png + + + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 647e31ff51..9bc504a001 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -19,7 +19,7 @@ from PyQt4.Qt import Qt, SIGNAL, QTimer, \ QMessageBox, QHelpEvent from calibre import prints -from calibre.constants import __appname__, isosx +from calibre.constants import __appname__, isosx, DEBUG from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import prefs, dynamic from calibre.utils.ipc.server import Server @@ -533,7 +533,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ # Save the current field_metadata for applications like calibre2opds # Goes here, because if cf is valid, db is valid. db.prefs['field_metadata'] = db.field_metadata.all_metadata() - if db.gm_count > 0: + if DEBUG and db.gm_count > 0: print 'get_metadata cache: {0:d} calls, {1:4.2f}% misses'.format( db.gm_count, (db.gm_missed*100.0)/db.gm_count) for action in self.iactions.values(): diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py new file mode 100644 index 0000000000..a7cb4eed01 --- /dev/null +++ b/src/calibre/utils/pyconsole/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys + +from calibre import prints as prints_ + +def prints(*args, **kwargs): + kwargs['file'] = sys.__stdout__ + prints_(*args, **kwargs) + + diff --git a/src/calibre/utils/pyconsole/editor.py b/src/calibre/utils/pyconsole/editor.py new file mode 100644 index 0000000000..68b83539f2 --- /dev/null +++ b/src/calibre/utils/pyconsole/editor.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, textwrap + +from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat + +from pygments.lexers import PythonLexer, PythonTracebackLexer + +from calibre.constants import __appname__, __version__ +from calibre.utils.pyconsole.formatter import Formatter +from calibre.utils.pyconsole.repl import Interpreter, DummyFile +from calibre.utils.pyconsole import prints + +class EditBlock(object): # {{{ + + def __init__(self, cursor): + self.cursor = cursor + + def __enter__(self): + self.cursor.beginEditBlock() + return self.cursor + + def __exit__(self, *args): + self.cursor.endEditBlock() +# }}} + +class Editor(QTextEdit): + + @property + def doc(self): + return self.document() + + @property + def cursor(self): + return self.textCursor() + + @property + def root_frame(self): + return self.doc.rootFrame() + + @property + def cursor_pos(self): + pass + #pos = self.cursor.position() - self.prompt_frame.firstPosition() + #i = 0 + #for line in self.current_prompt: + # i += self.prompt_len + + def __init__(self, + prompt='>>> ', + continuation='... ', + parent=None): + QTextEdit.__init__(self, parent) + self.buf = '' + self.prompt_frame = None + self.current_prompt = [''] + self.allow_output = False + self.prompt_frame_format = QTextFrameFormat() + self.prompt_frame_format.setBorder(1) + self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid) + self.prompt_len = len(prompt) + + self.doc.setMaximumBlockCount(10000) + self.lexer = PythonLexer(ensurenl=False) + self.tb_lexer = PythonTracebackLexer() + self.formatter = Formatter(prompt, continuation) + + motd = textwrap.dedent('''\ + # Python {0} + # {1} {2} + '''.format(sys.version.splitlines()[0], __appname__, + __version__)) + + with EditBlock(self.cursor): + self.render_block(motd) + + sys.stdout = sys.stderr = DummyFile(parent=self) + sys.stdout.write_output.connect(self.show_output) + self.interpreter = Interpreter(parent=self) + self.interpreter.show_error.connect(self.show_error) + + #it = self.prompt_frame.begin() + #while not it.atEnd(): + # bl = it.currentBlock() + # prints(repr(bl.text())) + # it += 1 + + + # Rendering {{{ + + def render_block(self, text, restore_prompt=True): + self.formatter.render(self.lexer.get_tokens(text), self.cursor) + self.cursor.insertBlock() + self.cursor.movePosition(self.cursor.End) + if restore_prompt: + self.render_current_prompt() + + def clear_current_prompt(self): + if self.prompt_frame is None: + c = self.root_frame.lastCursorPosition() + self.prompt_frame = c.insertFrame(self.prompt_frame_format) + self.setTextCursor(c) + else: + c = self.prompt_frame.firstCursorPosition() + self.setTextCursor(c) + c.setPosition(self.prompt_frame.lastPosition(), c.KeepAnchor) + c.removeSelectedText() + c.setPosition(self.prompt_frame.firstPosition()) + + def render_current_prompt(self): + self.clear_current_prompt() + + for i, line in enumerate(self.current_prompt): + start = i == 0 + end = i == len(self.current_prompt) - 1 + self.formatter.render_prompt(not start, self.cursor) + self.formatter.render(self.lexer.get_tokens(line), self.cursor) + if not end: + self.cursor.insertText('\n') + + def show_error(self, is_syntax_err, tb): + if self.prompt_frame is not None: + # At a prompt, so redirect output + return prints(tb) + try: + self.buf += tb + if is_syntax_err: + self.formatter.render_syntax_error(tb, self.cursor) + else: + self.formatter.render(self.tb_lexer.get_tokens(tb), self.cursor) + except: + prints(tb) + + def show_output(self, raw): + if self.prompt_frame is not None: + # At a prompt, so redirect output + return prints(raw) + try: + self.current_prompt_range = None + self.buf += raw + self.formatter.render_raw(raw, self.cursor) + except: + prints(raw) + + # }}} + + # Keyboard handling {{{ + + def keyPressEvent(self, ev): + text = unicode(ev.text()) + key = ev.key() + if key in (Qt.Key_Enter, Qt.Key_Return): + self.enter_pressed() + elif key == Qt.Key_Home: + self.home_pressed() + elif key == Qt.Key_End: + self.end_pressed() + elif key == Qt.Key_Left: + self.left_pressed() + elif key == Qt.Key_Right: + self.right_pressed() + elif text: + self.text_typed(text) + else: + QTextEdit.keyPressEvent(self, ev) + + def left_pressed(self): + pass + + def right_pressed(self): + if self.prompt_frame is not None: + c = self.cursor + c.movePosition(c.NextCharacter) + self.setTextCursor(c) + + def home_pressed(self): + if self.prompt_frame is not None: + c = self.cursor + c.movePosition(c.StartOfLine) + c.movePosition(c.NextCharacter, n=self.prompt_len) + self.setTextCursor(c) + + def end_pressed(self): + if self.prompt_frame is not None: + c = self.cursor + c.movePosition(c.EndOfLine) + self.setTextCursor(c) + + def enter_pressed(self): + if self.prompt_frame is None: + return + if self.current_prompt[0]: + c = self.root_frame.lastCursorPosition() + self.setTextCursor(c) + old_pf = self.prompt_frame + self.prompt_frame = None + oldbuf = self.buf + self.buf = '' + ret = self.interpreter.runsource('\n'.join(self.current_prompt)) + if ret: # Incomplete command + self.buf = oldbuf + self.prompt_frame = old_pf + self.current_prompt.append('') + else: # Command completed + self.current_prompt = [''] + old_pf.setFrameFormat(QTextFrameFormat()) + self.render_current_prompt() + + def text_typed(self, text): + if not self.current_prompt[0]: + self.cursor.beginEditBlock() + else: + self.cursor.joinPreviousEditBlock() + self.current_prompt[-1] += text + self.render_current_prompt() + self.cursor.endEditBlock() + + + # }}} + + diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py new file mode 100644 index 0000000000..7f99983ef6 --- /dev/null +++ b/src/calibre/utils/pyconsole/formatter.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QTextCharFormat, QFont, QBrush, QColor + +from pygments.formatter import Formatter as PF +from pygments.token import Token + +class Formatter(object): + + def __init__(self, prompt, continuation, **options): + if len(prompt) != len(continuation): + raise ValueError('%r does not have the same length as %r' % + (prompt, continuation)) + + self.prompt, self.continuation = prompt, continuation + + pf = PF(**options) + self.styles = {} + self.normal = self.base_fmt() + for ttype, ndef in pf.style: + fmt = self.base_fmt() + if ndef['color']: + fmt.setForeground(QBrush(QColor('#%s'%ndef['color']))) + fmt.setUnderlineColor(QColor('#%s'%ndef['color'])) + if ndef['bold']: + fmt.setFontWeight(QFont.Bold) + if ndef['italic']: + fmt.setFontItalic(True) + if ndef['underline']: + fmt.setFontUnderline(True) + if ndef['bgcolor']: + fmt.setBackground(QBrush(QColor('#%s'%ndef['bgcolor']))) + if ndef['border']: + pass # No support for borders + + self.styles[ttype] = fmt + + def base_fmt(self): + fmt = QTextCharFormat() + fmt.setFontFamily('monospace') + return fmt + + def render_raw(self, raw, cursor): + cursor.insertText(raw, self.normal) + + def render_syntax_error(self, tb, cursor): + fmt = self.styles[Token.Error] + cursor.insertText(tb, fmt) + + def render(self, tokens, cursor): + lastval = '' + lasttype = None + + for ttype, value in tokens: + while ttype not in self.styles: + ttype = ttype.parent + if ttype == lasttype: + lastval += value + else: + if lastval: + fmt = self.styles[lasttype] + cursor.insertText(lastval, fmt) + lastval = value + lasttype = ttype + + if lastval: + fmt = self.styles[lasttype] + cursor.insertText(lastval, fmt) + + def render_prompt(self, is_continuation, cursor): + pr = self.continuation if is_continuation else self.prompt + fmt = self.styles[Token.Generic.Subheading] + cursor.insertText(pr, fmt) + + diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py new file mode 100644 index 0000000000..c2694aae5f --- /dev/null +++ b/src/calibre/utils/pyconsole/main.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' +__version__ = '0.1.0' + +from PyQt4.Qt import QMainWindow, QToolBar, QStatusBar, QLabel, QFont, Qt, \ + QApplication + +from calibre.constants import __appname__, __version__ +from calibre.utils.pyconsole.editor import Editor + +class MainWindow(QMainWindow): + + def __init__(self, default_status_msg): + + QMainWindow.__init__(self) + + self.resize(600, 700) + + # Setup status bar {{{ + self.status_bar = QStatusBar(self) + self.status_bar.defmsg = QLabel(__appname__ + _(' console ') + + __version__) + self.status_bar._font = QFont() + self.status_bar._font.setBold(True) + self.status_bar.defmsg.setFont(self.status_bar._font) + self.status_bar.addWidget(self.status_bar.defmsg) + self.setStatusBar(self.status_bar) + # }}} + + # Setup tool bar {{{ + self.tool_bar = QToolBar(self) + self.addToolBar(Qt.BottomToolBarArea, self.tool_bar) + self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly) + # }}} + + self.editor = Editor(parent=self) + self.setCentralWidget(self.editor) + + + +def main(): + QApplication.setApplicationName(__appname__+' console') + QApplication.setOrganizationName('Kovid Goyal') + app = QApplication([]) + m = MainWindow(_('Welcome to') + ' ' + __appname__+' console') + m.show() + app.exec_() + + +if __name__ == '__main__': + main() + diff --git a/src/calibre/utils/pyconsole/repl.py b/src/calibre/utils/pyconsole/repl.py new file mode 100644 index 0000000000..de6262de14 --- /dev/null +++ b/src/calibre/utils/pyconsole/repl.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from code import InteractiveInterpreter + +from PyQt4.Qt import QObject, pyqtSignal + +from calibre import isbytestring +from calibre.constants import preferred_encoding + +class Interpreter(QObject, InteractiveInterpreter): + + # show_error(is_syntax_error, traceback) + show_error = pyqtSignal(object, object) + + def __init__(self, local={}, parent=None): + QObject.__init__(self, parent) + if '__name__' not in local: + local['__name__'] = '__console__' + if '__doc__' not in local: + local['__doc__'] = None + InteractiveInterpreter.__init__(self, locals=local) + + def showtraceback(self, *args, **kwargs): + self.is_syntax_error = False + InteractiveInterpreter.showtraceback(self, *args, **kwargs) + + def showsyntaxerror(self, *args, **kwargs): + self.is_syntax_error = True + InteractiveInterpreter.showsyntaxerror(self, *args, **kwargs) + + def write(self, tb): + self.show_error.emit(self.is_syntax_error, tb) + +class DummyFile(QObject): + + # write_output(unicode_object) + write_output = pyqtSignal(object) + + def __init__(self, parent=None): + QObject.__init__(self, parent) + self.closed = False + self.name = 'console' + self.softspace = 0 + + def flush(self): + pass + + def close(self): + pass + + def write(self, raw): + #import sys, traceback + #print >> sys.__stdout__, 'file,write stack:\n', ''.join(traceback.format_stack()) + if isbytestring(raw): + try: + raw = raw.decode(preferred_encoding, 'replace') + except: + raw = repr(raw) + if isbytestring(raw): + raw = raw.decode(preferred_encoding, 'replace') + self.write_output.emit(raw) +