home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 January / maximum-cd-2011-01.iso / DiscContents / calibre-0.7.26.msi / file_1391 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-10-31  |  43.2 KB  |  1,170 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
  6. import re
  7. import os
  8. import traceback
  9. from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter, QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton, QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSettings, QSize, QSplitter, QPainter, QLineEdit, QComboBox, QPen, QMenu, QStringListModel, QCompleter, QStringList, QTimer, QRect
  10. from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
  11. from calibre.constants import isosx
  12. from calibre.gui2.filename_pattern_ui import Ui_Form
  13. from calibre import fit_image
  14. from calibre.utils.fonts import fontconfig
  15. from calibre.ebooks import BOOK_EXTENSIONS
  16. from calibre.ebooks.metadata.meta import metadata_from_filename
  17. from calibre.utils.config import prefs, XMLConfig
  18. from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
  19. history = XMLConfig('history')
  20.  
  21. class ProgressIndicator(QWidget):
  22.     
  23.     def __init__(self, *args):
  24.         QWidget.__init__(self, *args)
  25.         self.setGeometry(0, 0, 300, 350)
  26.         self.pi = _ProgressIndicator(self)
  27.         self.status = QLabel(self)
  28.         self.status.setWordWrap(True)
  29.         self.status.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
  30.         self.setVisible(False)
  31.         self.pos = None
  32.  
  33.     
  34.     def start(self, msg = ''):
  35.         view = self.parent()
  36.         pwidth = view.size().width()
  37.         pheight = view.size().height()
  38.         self.resize(pwidth, min(pheight, 250))
  39.         if self.pos is None:
  40.             self.move(0, (pheight - self.size().height()) / 2)
  41.         else:
  42.             self.move(self.pos[0], self.pos[1])
  43.         self.pi.resize(self.pi.sizeHint())
  44.         self.pi.move(int((self.size().width() - self.pi.size().width()) / 2), 0)
  45.         self.status.resize(self.size().width(), self.size().height() - self.pi.size().height() - 10)
  46.         self.status.move(0, self.pi.size().height() + 10)
  47.         self.status.setText('<h1>' + msg + '</h1>')
  48.         self.setVisible(True)
  49.         self.pi.startAnimation()
  50.  
  51.     
  52.     def stop(self):
  53.         self.pi.stopAnimation()
  54.         self.setVisible(False)
  55.  
  56.  
  57.  
  58. class FilenamePattern(QWidget, Ui_Form):
  59.     changed_signal = pyqtSignal()
  60.     
  61.     def __init__(self, parent):
  62.         QWidget.__init__(self, parent)
  63.         self.setupUi(self)
  64.         self.connect(self.test_button, SIGNAL('clicked()'), self.do_test)
  65.         self.connect(self.re, SIGNAL('returnPressed()'), self.do_test)
  66.         self.initialize()
  67.         (self.re.textChanged.connect,)((lambda x: self.changed_signal.emit()))
  68.  
  69.     
  70.     def initialize(self, defaults = False):
  71.         if defaults:
  72.             val = prefs.defaults['filename_pattern']
  73.         else:
  74.             val = prefs['filename_pattern']
  75.         self.re.setText(val)
  76.  
  77.     
  78.     def do_test(self):
  79.         
  80.         try:
  81.             pat = self.pattern()
  82.         except Exception:
  83.             err = None
  84.             error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s') % err).exec_()
  85.             return None
  86.  
  87.         mi = metadata_from_filename(unicode(self.filename.text()), pat)
  88.         if mi.title:
  89.             self.title.setText(mi.title)
  90.         else:
  91.             self.title.setText(_('No match'))
  92.         if mi.authors:
  93.             self.authors.setText(', '.join(mi.authors))
  94.         else:
  95.             self.authors.setText(_('No match'))
  96.         if mi.series:
  97.             self.series.setText(mi.series)
  98.         else:
  99.             self.series.setText(_('No match'))
  100.         if mi.series_index is not None:
  101.             self.series_index.setText(str(mi.series_index))
  102.         else:
  103.             self.series_index.setText(_('No match'))
  104.         None(self.isbn.setText if mi.isbn is None else str(mi.isbn))
  105.  
  106.     
  107.     def pattern(self):
  108.         pat = unicode(self.re.text())
  109.         return re.compile(pat)
  110.  
  111.     
  112.     def commit(self):
  113.         pat = self.pattern().pattern
  114.         prefs['filename_pattern'] = pat
  115.         return pat
  116.  
  117.  
  118. IMAGE_EXTENSIONS = [
  119.     'jpg',
  120.     'jpeg',
  121.     'gif',
  122.     'png',
  123.     'bmp']
  124.  
  125. class FormatList(QListWidget):
  126.     DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
  127.     
  128.     def paths_from_event(cls, event):
  129.         if event.mimeData().hasFormat('text/uri-list'):
  130.             urls = [ unicode(u.toLocalFile()) for u in event.mimeData().urls() ]
  131.             urls = _[2]
  132.             return _[3]
  133.  
  134.     paths_from_event = classmethod(paths_from_event)
  135.     
  136.     def dragEnterEvent(self, event):
  137.         if int(event.possibleActions() & Qt.CopyAction) + int(event.possibleActions() & Qt.MoveAction) == 0:
  138.             return None
  139.         paths = self.paths_from_event(event)
  140.         if paths:
  141.             event.acceptProposedAction()
  142.         
  143.  
  144.     
  145.     def dropEvent(self, event):
  146.         paths = self.paths_from_event(event)
  147.         event.setDropAction(Qt.CopyAction)
  148.         self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), event, paths)
  149.  
  150.     
  151.     def dragMoveEvent(self, event):
  152.         event.acceptProposedAction()
  153.  
  154.     
  155.     def keyPressEvent(self, event):
  156.         if event.key() == Qt.Key_Delete:
  157.             self.emit(SIGNAL('delete_format()'))
  158.         else:
  159.             return QListWidget.keyPressEvent(self, event)
  160.         return event.key() == Qt.Key_Delete
  161.  
  162.  
  163.  
  164. class ImageView(QWidget):
  165.     BORDER_WIDTH = 1
  166.     
  167.     def __init__(self, parent = None):
  168.         QWidget.__init__(self, parent)
  169.         self._pixmap = QPixmap(self)
  170.         self.setMinimumSize(QSize(150, 200))
  171.         self.setAcceptDrops(True)
  172.         self.draw_border = True
  173.  
  174.     DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS
  175.     
  176.     def paths_from_event(cls, event):
  177.         if event.mimeData().hasFormat('text/uri-list'):
  178.             urls = [ unicode(u.toLocalFile()) for u in event.mimeData().urls() ]
  179.             urls = _[2]
  180.             return _[3]
  181.  
  182.     paths_from_event = classmethod(paths_from_event)
  183.     
  184.     def dragEnterEvent(self, event):
  185.         if int(event.possibleActions() & Qt.CopyAction) + int(event.possibleActions() & Qt.MoveAction) == 0:
  186.             return None
  187.         paths = self.paths_from_event(event)
  188.         if paths:
  189.             event.acceptProposedAction()
  190.         
  191.  
  192.     
  193.     def dropEvent(self, event):
  194.         paths = self.paths_from_event(event)
  195.         event.setDropAction(Qt.CopyAction)
  196.         for path in paths:
  197.             pmap = QPixmap()
  198.             pmap.load(path)
  199.             if not pmap.isNull():
  200.                 self.setPixmap(pmap)
  201.                 event.accept()
  202.                 self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), open(path, 'rb').read())
  203.                 break
  204.                 continue
  205.         
  206.  
  207.     
  208.     def dragMoveEvent(self, event):
  209.         event.acceptProposedAction()
  210.  
  211.     
  212.     def setPixmap(self, pixmap):
  213.         if not isinstance(pixmap, QPixmap):
  214.             raise TypeError('Must use a QPixmap')
  215.         isinstance(pixmap, QPixmap)
  216.         self._pixmap = pixmap
  217.         self.updateGeometry()
  218.         self.update()
  219.  
  220.     
  221.     def pixmap(self):
  222.         return self._pixmap
  223.  
  224.     
  225.     def sizeHint(self):
  226.         if self._pixmap.isNull():
  227.             return self.minimumSize()
  228.         return self._pixmap.size()
  229.  
  230.     
  231.     def paintEvent(self, event):
  232.         QWidget.paintEvent(self, event)
  233.         pmap = self._pixmap
  234.         if pmap.isNull():
  235.             return None
  236.         w = pmap.width()
  237.         h = pmap.height()
  238.         cw = self.rect().width()
  239.         ch = self.rect().height()
  240.         (scaled, nw, nh) = fit_image(w, h, cw, ch)
  241.         if scaled:
  242.             pmap = pmap.scaled(nw, nh, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
  243.         
  244.         w = pmap.width()
  245.         h = pmap.height()
  246.         x = int(abs(cw - w) / 2)
  247.         y = int(abs(ch - h) / 2)
  248.         target = QRect(x, y, w, h)
  249.         p = QPainter(self)
  250.         p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
  251.         p.drawPixmap(target, pmap)
  252.         pen = QPen()
  253.         pen.setWidth(self.BORDER_WIDTH)
  254.         p.setPen(pen)
  255.         if self.draw_border:
  256.             p.drawRect(target)
  257.         
  258.         p.end()
  259.  
  260.     
  261.     def contextMenuEvent(self, ev):
  262.         cm = QMenu(self)
  263.         copy = cm.addAction(_('Copy Image'))
  264.         paste = cm.addAction(_('Paste Image'))
  265.         if not QApplication.instance().clipboard().mimeData().hasImage():
  266.             paste.setEnabled(False)
  267.         
  268.         copy.triggered.connect(self.copy_to_clipboard)
  269.         paste.triggered.connect(self.paste_from_clipboard)
  270.         cm.exec_(ev.globalPos())
  271.  
  272.     
  273.     def copy_to_clipboard(self):
  274.         QApplication.instance().clipboard().setPixmap(self.pixmap())
  275.  
  276.     
  277.     def paste_from_clipboard(self):
  278.         cb = QApplication.instance().clipboard()
  279.         pmap = cb.pixmap()
  280.         if pmap.isNull() and cb.supportsSelection():
  281.             pmap = cb.pixmap(cb.Selection)
  282.         
  283.         if not pmap.isNull():
  284.             self.setPixmap(pmap)
  285.             self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), pixmap_to_data(pmap))
  286.         
  287.  
  288.  
  289.  
  290. class FontFamilyModel(QAbstractListModel):
  291.     
  292.     def __init__(self, *args):
  293.         QAbstractListModel.__init__(self, *args)
  294.         
  295.         try:
  296.             self.families = fontconfig.find_font_families()
  297.         except:
  298.             self.families = []
  299.             print 'WARNING: Could not load fonts'
  300.             traceback.print_exc()
  301.  
  302.         self.families.sort()
  303.         self.families[:0] = [
  304.             _('None')]
  305.  
  306.     
  307.     def rowCount(self, *args):
  308.         return len(self.families)
  309.  
  310.     
  311.     def data(self, index, role):
  312.         
  313.         try:
  314.             family = self.families[index.row()]
  315.         except:
  316.             traceback.print_exc()
  317.             return NONE
  318.  
  319.         if role == Qt.DisplayRole:
  320.             return QVariant(family)
  321.         if not isosx and role == Qt.FontRole:
  322.             return QVariant(QFont(family))
  323.         return NONE
  324.  
  325.     
  326.     def index_of(self, family):
  327.         return self.families.index(family.strip())
  328.  
  329.  
  330.  
  331. class BasicComboModel(QAbstractListModel):
  332.     
  333.     def __init__(self, items, *args):
  334.         QAbstractListModel.__init__(self, *args)
  335.         self.items = [ i for i in items ]
  336.         self.items.sort()
  337.  
  338.     
  339.     def rowCount(self, *args):
  340.         return len(self.items)
  341.  
  342.     
  343.     def data(self, index, role):
  344.         
  345.         try:
  346.             item = self.items[index.row()]
  347.         except:
  348.             traceback.print_exc()
  349.             return NONE
  350.  
  351.         if role == Qt.DisplayRole:
  352.             return QVariant(item)
  353.         if role == Qt.FontRole:
  354.             return QVariant(QFont(item))
  355.         return NONE
  356.  
  357.     
  358.     def index_of(self, item):
  359.         return self.items.index(item.strip())
  360.  
  361.  
  362.  
  363. class BasicListItem(QListWidgetItem):
  364.     
  365.     def __init__(self, text, user_data = None):
  366.         QListWidgetItem.__init__(self, text)
  367.         self.user_data = user_data
  368.  
  369.     
  370.     def __eq__(self, other):
  371.         if hasattr(other, 'text'):
  372.             return self.text() == other.text()
  373.         return False
  374.  
  375.  
  376.  
  377. class BasicList(QListWidget):
  378.     
  379.     def add_item(self, text, user_data = None, replace = False):
  380.         item = BasicListItem(text, user_data)
  381.         for oitem in self.items():
  382.             if oitem == item:
  383.                 if replace:
  384.                     self.takeItem(self.row(oitem))
  385.                 else:
  386.                     raise ValueError('Item already in list')
  387.             replace
  388.         
  389.         self.addItem(item)
  390.  
  391.     
  392.     def remove_selected_items(self, *args):
  393.         for item in self.selectedItems():
  394.             self.takeItem(self.row(item))
  395.         
  396.  
  397.     
  398.     def items(self):
  399.         for i in range(self.count()):
  400.             yield self.item(i)
  401.         
  402.  
  403.  
  404.  
  405. class LineEditECM(object):
  406.     
  407.     def contextMenuEvent(self, event):
  408.         menu = self.createStandardContextMenu()
  409.         menu.addSeparator()
  410.         case_menu = QMenu(_('Change Case'))
  411.         action_upper_case = case_menu.addAction(_('Upper Case'))
  412.         action_lower_case = case_menu.addAction(_('Lower Case'))
  413.         action_swap_case = case_menu.addAction(_('Swap Case'))
  414.         action_title_case = case_menu.addAction(_('Title Case'))
  415.         self.connect(action_upper_case, SIGNAL('triggered()'), self.upper_case)
  416.         self.connect(action_lower_case, SIGNAL('triggered()'), self.lower_case)
  417.         self.connect(action_swap_case, SIGNAL('triggered()'), self.swap_case)
  418.         self.connect(action_title_case, SIGNAL('triggered()'), self.title_case)
  419.         menu.addMenu(case_menu)
  420.         menu.exec_(event.globalPos())
  421.  
  422.     
  423.     def upper_case(self):
  424.         self.setText(unicode(self.text()).upper())
  425.  
  426.     
  427.     def lower_case(self):
  428.         self.setText(unicode(self.text()).lower())
  429.  
  430.     
  431.     def swap_case(self):
  432.         self.setText(unicode(self.text()).swapcase())
  433.  
  434.     
  435.     def title_case(self):
  436.         titlecase = titlecase
  437.         import calibre.utils.titlecase
  438.         self.setText(titlecase(unicode(self.text())))
  439.  
  440.  
  441.  
  442. class EnLineEdit(LineEditECM, QLineEdit):
  443.     pass
  444.  
  445.  
  446. class TagsCompleter(QCompleter):
  447.     
  448.     def __init__(self, parent, all_tags):
  449.         QCompleter.__init__(self, all_tags, parent)
  450.         self.all_tags = set(all_tags)
  451.  
  452.     
  453.     def update(self, text_tags, completion_prefix):
  454.         tags = list(self.all_tags.difference(text_tags))
  455.         model = QStringListModel(tags, self)
  456.         self.setModel(model)
  457.         self.setCompletionPrefix(completion_prefix)
  458.         if completion_prefix.strip() != '':
  459.             self.complete()
  460.         
  461.  
  462.     
  463.     def update_tags_cache(self, tags):
  464.         self.all_tags = set(tags)
  465.         model = QStringListModel(tags, self)
  466.         self.setModel(model)
  467.  
  468.  
  469.  
  470. class TagsLineEdit(EnLineEdit):
  471.     
  472.     def __init__(self, parent = 0, tags = []):
  473.         EnLineEdit.__init__(self, parent)
  474.         self.separator = ','
  475.         self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
  476.         self.completer = TagsCompleter(self, tags)
  477.         self.completer.setCaseSensitivity(Qt.CaseInsensitive)
  478.         self.connect(self, SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), self.completer.update)
  479.         self.connect(self.completer, SIGNAL('activated(QString)'), self.complete_text)
  480.         self.completer.setWidget(self)
  481.  
  482.     
  483.     def update_tags_cache(self, tags):
  484.         self.completer.update_tags_cache(tags)
  485.  
  486.     
  487.     def text_changed(self, text):
  488.         all_text = unicode(text)
  489.         text = all_text[:self.cursorPosition()]
  490.         prefix = text.split(',')[-1].strip()
  491.         text_tags = []
  492.         for t in all_text.split(self.separator):
  493.             t1 = unicode(t).strip()
  494.             if t1 != '':
  495.                 text_tags.append(t)
  496.                 continue
  497.         
  498.         text_tags = list(set(text_tags))
  499.         self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), text_tags, prefix)
  500.  
  501.     
  502.     def complete_text(self, text):
  503.         cursor_pos = self.cursorPosition()
  504.         before_text = unicode(self.text())[:cursor_pos]
  505.         after_text = unicode(self.text())[cursor_pos:]
  506.         prefix_len = len(before_text.split(',')[-1].strip())
  507.         self.setText('%s%s%s %s' % (before_text[:cursor_pos - prefix_len], text, self.separator, after_text))
  508.         self.setCursorPosition((cursor_pos - prefix_len) + len(text) + 2)
  509.  
  510.  
  511.  
  512. class EnComboBox(QComboBox):
  513.     
  514.     def __init__(self, *args):
  515.         QComboBox.__init__(self, *args)
  516.         self.setLineEdit(EnLineEdit(self))
  517.         self.setAutoCompletionCaseSensitivity(Qt.CaseSensitive)
  518.         self.setMinimumContentsLength(20)
  519.  
  520.     
  521.     def text(self):
  522.         return unicode(self.currentText())
  523.  
  524.     
  525.     def setText(self, text):
  526.         idx = self.findText(text, Qt.MatchFixedString | Qt.MatchCaseSensitive)
  527.         if idx == -1:
  528.             self.insertItem(0, text)
  529.             idx = 0
  530.         
  531.         self.setCurrentIndex(idx)
  532.  
  533.  
  534.  
  535. class HistoryLineEdit(QComboBox):
  536.     
  537.     def __init__(self, *args):
  538.         QComboBox.__init__(self, *args)
  539.         self.setEditable(True)
  540.         self.setInsertPolicy(self.NoInsert)
  541.         self.setMaxCount(10)
  542.  
  543.     
  544.     def store_name(self):
  545.         return 'lineedit_history_' + self._name
  546.  
  547.     store_name = property(store_name)
  548.     
  549.     def initialize(self, name):
  550.         self._name = name
  551.         self.addItems(QStringList(history.get(self.store_name, [])))
  552.         self.setEditText('')
  553.         self.lineEdit().editingFinished.connect(self.save_history)
  554.  
  555.     
  556.     def save_history(self):
  557.         items = []
  558.         ct = unicode(self.currentText())
  559.         if ct:
  560.             items.append(ct)
  561.         
  562.         for i in range(self.count()):
  563.             item = unicode(self.itemText(i))
  564.             if item not in items:
  565.                 items.append(item)
  566.                 continue
  567.         
  568.         history.set(self.store_name, items)
  569.  
  570.     
  571.     def setText(self, t):
  572.         self.setEditText(t)
  573.         self.lineEdit().setCursorPosition(0)
  574.  
  575.     
  576.     def text(self):
  577.         return self.currentText()
  578.  
  579.  
  580.  
  581. class ComboBoxWithHelp(QComboBox):
  582.     
  583.     def __init__(self, parent = None):
  584.         QComboBox.__init__(self, parent)
  585.         self.currentIndexChanged[int].connect(self.index_changed)
  586.         self.help_text = ''
  587.         self.state_set = False
  588.  
  589.     
  590.     def initialize(self, help_text = _('Search')):
  591.         self.help_text = help_text
  592.         self.set_state()
  593.  
  594.     
  595.     def set_state(self):
  596.         if not self.state_set:
  597.             if self.currentIndex() == 0:
  598.                 self.setItemText(0, self.help_text)
  599.                 self.setStyleSheet('QComboBox { color: gray }')
  600.             else:
  601.                 self.setItemText(0, '')
  602.                 self.setStyleSheet('QComboBox { color: black }')
  603.         
  604.  
  605.     
  606.     def index_changed(self, index):
  607.         self.state_set = False
  608.         self.set_state()
  609.  
  610.     
  611.     def currentText(self):
  612.         if self.currentIndex() == 0:
  613.             return ''
  614.         return QComboBox.currentText(self)
  615.  
  616.     
  617.     def itemText(self, idx):
  618.         if idx == 0:
  619.             return ''
  620.         return QComboBox.itemText(self, idx)
  621.  
  622.     
  623.     def showPopup(self):
  624.         self.setItemText(0, '')
  625.         QComboBox.showPopup(self)
  626.  
  627.     
  628.     def hidePopup(self):
  629.         QComboBox.hidePopup(self)
  630.         self.set_state()
  631.  
  632.  
  633.  
  634. class PythonHighlighter(QSyntaxHighlighter):
  635.     Rules = []
  636.     Formats = { }
  637.     Config = { }
  638.     KEYWORDS = [
  639.         'and',
  640.         'as',
  641.         'assert',
  642.         'break',
  643.         'class',
  644.         'continue',
  645.         'def',
  646.         'del',
  647.         'elif',
  648.         'else',
  649.         'except',
  650.         'exec',
  651.         'finally',
  652.         'for',
  653.         'from',
  654.         'global',
  655.         'if',
  656.         'import',
  657.         'in',
  658.         'is',
  659.         'lambda',
  660.         'not',
  661.         'or',
  662.         'pass',
  663.         'print',
  664.         'raise',
  665.         'return',
  666.         'try',
  667.         'while',
  668.         'with',
  669.         'yield']
  670.     BUILTINS = [
  671.         'abs',
  672.         'all',
  673.         'any',
  674.         'basestring',
  675.         'bool',
  676.         'callable',
  677.         'chr',
  678.         'classmethod',
  679.         'cmp',
  680.         'compile',
  681.         'complex',
  682.         'delattr',
  683.         'dict',
  684.         'dir',
  685.         'divmod',
  686.         'enumerate',
  687.         'eval',
  688.         'execfile',
  689.         'exit',
  690.         'file',
  691.         'filter',
  692.         'float',
  693.         'frozenset',
  694.         'getattr',
  695.         'globals',
  696.         'hasattr',
  697.         'hex',
  698.         'id',
  699.         'int',
  700.         'isinstance',
  701.         'issubclass',
  702.         'iter',
  703.         'len',
  704.         'list',
  705.         'locals',
  706.         'long',
  707.         'map',
  708.         'max',
  709.         'min',
  710.         'object',
  711.         'oct',
  712.         'open',
  713.         'ord',
  714.         'pow',
  715.         'property',
  716.         'range',
  717.         'reduce',
  718.         'repr',
  719.         'reversed',
  720.         'round',
  721.         'set',
  722.         'setattr',
  723.         'slice',
  724.         'sorted',
  725.         'staticmethod',
  726.         'str',
  727.         'sum',
  728.         'super',
  729.         'tuple',
  730.         'type',
  731.         'unichr',
  732.         'unicode',
  733.         'vars',
  734.         'xrange',
  735.         'zip']
  736.     CONSTANTS = [
  737.         'False',
  738.         'True',
  739.         'None',
  740.         'NotImplemented',
  741.         'Ellipsis']
  742.     
  743.     def __init__(self, parent = None):
  744.         super(PythonHighlighter, self).__init__(parent)
  745.         if not self.Config:
  746.             self.loadConfig()
  747.         
  748.         self.initializeFormats()
  749.         '|'.join(([]([]([ '\\b%s\\b' % keyword for keyword in self.KEYWORDS ])), 'keyword'))
  750.         '|'.join(([]([]([ '\\b%s\\b' % builtin for builtin in self.BUILTINS ])), 'builtin'))
  751.         '|'.join(([]([]([ '\\b%s\\b' % constant for constant in self.CONSTANTS ])), 'constant'))
  752.         PythonHighlighter.Rules.append((QRegExp('\\b[+-]?[0-9]+[lL]?\\b|\\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\\b|\\b[+-]?[0-9]+(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b'), 'number'))
  753.         PythonHighlighter.Rules.append((QRegExp('\\bPyQt4\\b|\\bQt?[A-Z][a-z]\\w+\\b'), 'pyqt'))
  754.         PythonHighlighter.Rules.append((QRegExp('\\b@\\w+\\b'), 'decorator'))
  755.         stringRe = QRegExp('(?:\'[^\']*\'|"[^"]*")')
  756.         stringRe.setMinimal(True)
  757.         PythonHighlighter.Rules.append((stringRe, 'string'))
  758.         self.stringRe = QRegExp('(:?"["]".*"["]"|\'\'\'.*\'\'\')')
  759.         self.stringRe.setMinimal(True)
  760.         PythonHighlighter.Rules.append((self.stringRe, 'string'))
  761.         self.tripleSingleRe = QRegExp('\'\'\'(?!")')
  762.         self.tripleDoubleRe = QRegExp('"""(?!\')')
  763.  
  764.     
  765.     def loadConfig(cls):
  766.         Config = cls.Config
  767.         settings = QSettings()
  768.         
  769.         def setDefaultString(name, default):
  770.             value = settings.value(name).toString()
  771.             if value.isEmpty():
  772.                 value = default
  773.             
  774.             Config[name] = value
  775.  
  776.         for name in ('window', 'shell'):
  777.             Config['%swidth' % name] = settings.value('%swidth' % name, QVariant(QApplication.desktop().availableGeometry().width() / 2)).toInt()[0]
  778.             Config['%sheight' % name] = settings.value('%sheight' % name, QVariant(QApplication.desktop().availableGeometry().height() / 2)).toInt()[0]
  779.             Config['%sy' % name] = settings.value('%sy' % name, QVariant(0)).toInt()[0]
  780.         
  781.         Config['toolbars'] = settings.value('toolbars').toByteArray()
  782.         Config['splitter'] = settings.value('splitter').toByteArray()
  783.         Config['shellx'] = settings.value('shellx', QVariant(0)).toInt()[0]
  784.         Config['windowx'] = settings.value('windowx', QVariant(QApplication.desktop().availableGeometry().width() / 2)).toInt()[0]
  785.         Config['remembergeometry'] = settings.value('remembergeometry', QVariant(True)).toBool()
  786.         Config['startwithshell'] = settings.value('startwithshell', QVariant(True)).toBool()
  787.         Config['showwindowinfo'] = settings.value('showwindowinfo', QVariant(True)).toBool()
  788.         setDefaultString('shellstartup', '    from __future__ import division\n    import codecs\n    import sys\n    sys.stdin = codecs.getreader("UTF8")(sys.stdin)\n    sys.stdout = codecs.getwriter("UTF8")(sys.stdout)')
  789.         setDefaultString('newfile', '    #!/usr/bin/env python\n\n    from __future__ import division\n\n    import sys\n    ')
  790.         Config['backupsuffix'] = settings.value('backupsuffix', QVariant('.bak')).toString()
  791.         setDefaultString('beforeinput', '#>>>')
  792.         setDefaultString('beforeoutput', '#---')
  793.         Config['cwd'] = settings.value('cwd', QVariant('.')).toString()
  794.         Config['tooltipsize'] = settings.value('tooltipsize', QVariant(150)).toInt()[0]
  795.         Config['maxlinestoscan'] = settings.value('maxlinestoscan', QVariant(5000)).toInt()[0]
  796.         Config['pythondocpath'] = settings.value('pythondocpath', QVariant('http://docs.python.org')).toString()
  797.         Config['autohidefinddialog'] = settings.value('autohidefinddialog', QVariant(True)).toBool()
  798.         Config['findcasesensitive'] = settings.value('findcasesensitive', QVariant(False)).toBool()
  799.         Config['findwholewords'] = settings.value('findwholewords', QVariant(False)).toBool()
  800.         Config['tabwidth'] = settings.value('tabwidth', QVariant(4)).toInt()[0]
  801.         Config['fontfamily'] = settings.value('fontfamily', QVariant('Bitstream Vera Sans Mono')).toString()
  802.         Config['fontsize'] = settings.value('fontsize', QVariant(10)).toInt()[0]
  803.         for name, color, bold, italic in (('normal', '#000000', False, False), ('keyword', '#000080', True, False), ('builtin', '#0000A0', False, False), ('constant', '#0000C0', False, False), ('decorator', '#0000E0', False, False), ('comment', '#007F00', False, True), ('string', '#808000', False, False), ('number', '#924900', False, False), ('error', '#FF0000', False, False), ('pyqt', '#50621A', False, False)):
  804.             Config['%sfontcolor' % name] = settings.value('%sfontcolor' % name, QVariant(color)).toString()
  805.             Config['%sfontbold' % name] = settings.value('%sfontbold' % name, QVariant(bold)).toBool()
  806.             Config['%sfontitalic' % name] = settings.value('%sfontitalic' % name, QVariant(italic)).toBool()
  807.         
  808.  
  809.     loadConfig = classmethod(loadConfig)
  810.     
  811.     def initializeFormats(cls):
  812.         Config = cls.Config
  813.         baseFormat = QTextCharFormat()
  814.         baseFormat.setFontFamily(Config['fontfamily'])
  815.         baseFormat.setFontPointSize(Config['fontsize'])
  816.         for name in ('normal', 'keyword', 'builtin', 'constant', 'decorator', 'comment', 'string', 'number', 'error', 'pyqt'):
  817.             format = QTextCharFormat(baseFormat)
  818.             format.setForeground(QColor(Config['%sfontcolor' % name]))
  819.             if Config['%sfontbold' % name]:
  820.                 format.setFontWeight(QFont.Bold)
  821.             
  822.             format.setFontItalic(Config['%sfontitalic' % name])
  823.             PythonHighlighter.Formats[name] = format
  824.         
  825.  
  826.     initializeFormats = classmethod(initializeFormats)
  827.     
  828.     def highlightBlock(self, text):
  829.         (NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR) = range(4)
  830.         textLength = text.length()
  831.         prevState = self.previousBlockState()
  832.         self.setFormat(0, textLength, PythonHighlighter.Formats['normal'])
  833.         if text.startsWith('Traceback') or text.startsWith('Error: '):
  834.             self.setCurrentBlockState(ERROR)
  835.             self.setFormat(0, textLength, PythonHighlighter.Formats['error'])
  836.             return None
  837.         if prevState == ERROR:
  838.             if not text.startsWith('>>>'):
  839.                 pass
  840.             if not text.startsWith('#'):
  841.                 self.setCurrentBlockState(ERROR)
  842.                 self.setFormat(0, textLength, PythonHighlighter.Formats['error'])
  843.                 return None
  844.             for regex, format in PythonHighlighter.Rules:
  845.                 i = regex.indexIn(text)
  846.                 while i >= 0:
  847.                     length = regex.matchedLength()
  848.                     self.setFormat(i, length, PythonHighlighter.Formats[format])
  849.                     i = regex.indexIn(text, i + length)
  850.                     continue
  851.                     not text.startsWith('#')
  852.             
  853.         if text.isEmpty():
  854.             pass
  855.         elif text[0] == '#':
  856.             self.setFormat(0, text.length(), PythonHighlighter.Formats['comment'])
  857.         else:
  858.             stack = []
  859.             for i, c in enumerate(text):
  860.                 if c in ('"', "'"):
  861.                     if stack and stack[-1] == c:
  862.                         stack.pop()
  863.                     else:
  864.                         stack.append(c)
  865.                 stack[-1] == c
  866.                 if c == '#' and len(stack) == 0:
  867.                     self.setFormat(i, text.length(), PythonHighlighter.Formats['comment'])
  868.                     break
  869.                     continue
  870.             
  871.         self.setCurrentBlockState(NORMAL)
  872.         if self.stringRe.indexIn(text) != -1:
  873.             return None
  874.         for i, state in ((self.tripleSingleRe.indexIn(text), TRIPLESINGLE), (self.tripleDoubleRe.indexIn(text), TRIPLEDOUBLE)):
  875.             if self.previousBlockState() == state:
  876.                 if i == -1:
  877.                     i = text.length()
  878.                     self.setCurrentBlockState(state)
  879.                 
  880.                 self.setFormat(0, i + 3, PythonHighlighter.Formats['string'])
  881.                 continue
  882.             if i > -1:
  883.                 self.setCurrentBlockState(state)
  884.                 self.setFormat(i, text.length(), PythonHighlighter.Formats['string'])
  885.                 continue
  886.         
  887.  
  888.     
  889.     def rehighlight(self):
  890.         QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
  891.         QSyntaxHighlighter.rehighlight(self)
  892.         QApplication.restoreOverrideCursor()
  893.  
  894.  
  895.  
  896. class SplitterHandle(QSplitterHandle):
  897.     double_clicked = pyqtSignal(object)
  898.     
  899.     def __init__(self, orientation, splitter):
  900.         QSplitterHandle.__init__(self, orientation, splitter)
  901.         splitter.splitterMoved.connect(self.splitter_moved, type = Qt.QueuedConnection)
  902.         self.double_clicked.connect(splitter.double_clicked, type = Qt.QueuedConnection)
  903.         self.highlight = False
  904.         self.setToolTip(_('Drag to resize') + ' ' + splitter.label)
  905.  
  906.     
  907.     def splitter_moved(self, *args):
  908.         oh = self.highlight
  909.         self.highlight = 0 in self.splitter().sizes()
  910.         if oh != self.highlight:
  911.             self.update()
  912.         
  913.  
  914.     
  915.     def paintEvent(self, ev):
  916.         QSplitterHandle.paintEvent(self, ev)
  917.         if self.highlight:
  918.             painter = QPainter(self)
  919.             painter.setClipRect(ev.rect())
  920.             painter.fillRect(self.rect(), Qt.yellow)
  921.         
  922.  
  923.     
  924.     def mouseDoubleClickEvent(self, ev):
  925.         self.double_clicked.emit(self)
  926.  
  927.  
  928.  
  929. class LayoutButton(QToolButton):
  930.     
  931.     def __init__(self, icon, text, splitter, parent = None, shortcut = None):
  932.         QToolButton.__init__(self, parent)
  933.         self.label = text
  934.         self.setIcon(QIcon(icon))
  935.         self.setCheckable(True)
  936.         self.splitter = splitter
  937.         splitter.state_changed.connect(self.update_state)
  938.         self.setCursor(Qt.PointingHandCursor)
  939.         self.shortcut = ''
  940.         if shortcut:
  941.             self.shortcut = shortcut
  942.         
  943.  
  944.     
  945.     def set_state_to_show(self, *args):
  946.         self.setChecked(False)
  947.         label = _('Show')
  948.         self.setText(label + ' ' + self.label + u' (%s)' % self.shortcut)
  949.         self.setToolTip(self.text())
  950.         self.setStatusTip(self.text())
  951.  
  952.     
  953.     def set_state_to_hide(self, *args):
  954.         self.setChecked(True)
  955.         label = _('Hide')
  956.         self.setText(label + ' ' + self.label + u' (%s)' % self.shortcut)
  957.         self.setToolTip(self.text())
  958.         self.setStatusTip(self.text())
  959.  
  960.     
  961.     def update_state(self, *args):
  962.         if self.splitter.is_side_index_hidden:
  963.             self.set_state_to_show()
  964.         else:
  965.             self.set_state_to_hide()
  966.  
  967.  
  968.  
  969. class Splitter(QSplitter):
  970.     state_changed = pyqtSignal(object)
  971.     
  972.     def __init__(self, name, label, icon, initial_show = True, initial_side_size = 120, connect_button = True, orientation = Qt.Horizontal, side_index = 0, parent = None, shortcut = None):
  973.         QSplitter.__init__(self, parent)
  974.         self.resize_timer = QTimer(self)
  975.         self.resize_timer.setSingleShot(True)
  976.         self.desired_side_size = initial_side_size
  977.         self.desired_show = initial_show
  978.         self.resize_timer.setInterval(5)
  979.         self.resize_timer.timeout.connect(self.do_resize)
  980.         self.setOrientation(orientation)
  981.         self.side_index = side_index
  982.         self._name = name
  983.         self.label = label
  984.         self.initial_side_size = initial_side_size
  985.         self.initial_show = initial_show
  986.         self.splitterMoved.connect(self.splitter_moved, type = Qt.QueuedConnection)
  987.         self.button = LayoutButton(icon, label, self, shortcut = shortcut)
  988.         if connect_button:
  989.             self.button.clicked.connect(self.double_clicked)
  990.         
  991.         if shortcut is not None:
  992.             self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label, self)
  993.             self.action_toggle.triggered.connect(self.toggle_triggered)
  994.             self.action_toggle.setShortcut(shortcut)
  995.             if parent is not None:
  996.                 parent.addAction(self.action_toggle)
  997.             
  998.         
  999.  
  1000.     
  1001.     def toggle_triggered(self, *args):
  1002.         self.toggle_side_pane()
  1003.  
  1004.     
  1005.     def createHandle(self):
  1006.         return SplitterHandle(self.orientation(), self)
  1007.  
  1008.     
  1009.     def initialize(self):
  1010.         for i in range(self.count()):
  1011.             h = self.handle(i)
  1012.             if h is not None:
  1013.                 h.splitter_moved()
  1014.                 continue
  1015.         
  1016.         self.state_changed.emit(not (self.is_side_index_hidden))
  1017.  
  1018.     
  1019.     def splitter_moved(self, *args):
  1020.         self.desired_side_size = self.side_index_size
  1021.         self.state_changed.emit(not (self.is_side_index_hidden))
  1022.  
  1023.     
  1024.     def is_side_index_hidden(self):
  1025.         sizes = list(self.sizes())
  1026.         
  1027.         try:
  1028.             return sizes[self.side_index] == 0
  1029.         except IndexError:
  1030.             return True
  1031.  
  1032.  
  1033.     is_side_index_hidden = property(is_side_index_hidden)
  1034.     
  1035.     def save_name(self):
  1036.         ori = None if self.orientation() == Qt.Horizontal else 'vertical'
  1037.         return self._name + '_' + ori
  1038.  
  1039.     save_name = property(save_name)
  1040.     
  1041.     def print_sizes(self):
  1042.         if self.count() > 1:
  1043.             print self.save_name, 'side:', self.side_index_size, 'other:', list(self.sizes())[self.other_index]
  1044.         
  1045.  
  1046.     
  1047.     def side_index_size(self):
  1048.         
  1049.         def fget(self):
  1050.             if self.count() < 2:
  1051.                 return 0
  1052.             return self.sizes()[self.side_index]
  1053.  
  1054.         
  1055.         def fset(self, val):
  1056.             if self.count() < 2:
  1057.                 return None
  1058.             if val == 0 and not (self.is_side_index_hidden):
  1059.                 self.save_state()
  1060.             
  1061.             sizes = list(self.sizes())
  1062.             for i in range(len(sizes)):
  1063.                 sizes[i] = None if i == self.side_index else 10
  1064.             
  1065.             self.setSizes(sizes)
  1066.             total = sum(self.sizes())
  1067.             sizes = list(self.sizes())
  1068.             for i in range(len(sizes)):
  1069.                 sizes[i] = None if i == self.side_index else total - val
  1070.             
  1071.             self.setSizes(sizes)
  1072.             self.initialize()
  1073.  
  1074.         return property(fget = fget, fset = fset)
  1075.  
  1076.     side_index_size = dynamic_property(side_index_size)
  1077.     
  1078.     def do_resize(self, *args):
  1079.         orig = self.desired_side_size
  1080.         QSplitter.resizeEvent(self, self._resize_ev)
  1081.         if orig > 20 and self.desired_show:
  1082.             c = 0
  1083.             while abs(self.side_index_size - orig) > 10 and c < 5:
  1084.                 self.apply_state(self.get_state(), save_desired = False)
  1085.                 c += 1
  1086.         
  1087.  
  1088.     
  1089.     def resizeEvent(self, ev):
  1090.         if self.resize_timer.isActive():
  1091.             self.resize_timer.stop()
  1092.         
  1093.         self._resize_ev = ev
  1094.         self.resize_timer.start()
  1095.  
  1096.     
  1097.     def get_state(self):
  1098.         if self.count() < 2:
  1099.             return (False, 200)
  1100.         return (self.desired_show, self.desired_side_size)
  1101.  
  1102.     
  1103.     def apply_state(self, state, save_desired = True):
  1104.         if state[0]:
  1105.             self.side_index_size = state[1]
  1106.             if save_desired:
  1107.                 self.desired_side_size = self.side_index_size
  1108.             
  1109.         else:
  1110.             self.side_index_size = 0
  1111.         self.desired_show = state[0]
  1112.  
  1113.     
  1114.     def default_state(self):
  1115.         return (self.initial_show, self.initial_side_size)
  1116.  
  1117.     
  1118.     def update_desired_state(self):
  1119.         self.desired_show = not (self.is_side_index_hidden)
  1120.  
  1121.     
  1122.     def save_state(self):
  1123.         if self.count() > 1:
  1124.             gprefs[self.save_name + '_state'] = self.get_state()
  1125.         
  1126.  
  1127.     
  1128.     def other_index(self):
  1129.         return (self.side_index + 1) % 2
  1130.  
  1131.     other_index = property(other_index)
  1132.     
  1133.     def restore_state(self):
  1134.         if self.count() > 1:
  1135.             state = gprefs.get(self.save_name + '_state', self.default_state())
  1136.             self.apply_state(state, save_desired = False)
  1137.             self.desired_side_size = state[1]
  1138.         
  1139.  
  1140.     
  1141.     def toggle_side_pane(self, hide = None):
  1142.         if hide is None:
  1143.             action = None if self.is_side_index_hidden else 'hide'
  1144.         elif hide:
  1145.             pass
  1146.         
  1147.         action = 'show'
  1148.         getattr(self, action + '_side_pane')()
  1149.  
  1150.     
  1151.     def show_side_pane(self):
  1152.         if self.count() < 2 or not (self.is_side_index_hidden):
  1153.             return None
  1154.         if self.desired_side_size == 0:
  1155.             self.desired_side_size = self.initial_side_size
  1156.         
  1157.         self.apply_state((True, self.desired_side_size))
  1158.  
  1159.     
  1160.     def hide_side_pane(self):
  1161.         if self.count() < 2 or self.is_side_index_hidden:
  1162.             return None
  1163.         self.apply_state((False, self.desired_side_size))
  1164.  
  1165.     
  1166.     def double_clicked(self, *args):
  1167.         self.toggle_side_pane()
  1168.  
  1169.  
  1170.