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