camelot.camelot.view.controls.editors

Package camelot :: Package camelot :: Package view :: Package controls :: Module editors |
[hide private]
[frames] | no frames]

# Source Code for Module camelot.camelot.view.controls.editors

      1  #  ============================================================================
      2  #
      3  #  Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.
      4  #  www.conceptive.be / project-camelot@conceptive.be
      5  #
      6  #  This file is part of the Camelot Library.
      7  #
      8  #  This file may be used under the terms of the GNU General Public
      9  #  License version 2.0 as published by the Free Software Foundation
     10  #  and appearing in the file LICENSE.GPL included in the packaging of
     11  #  this file.  Please review the following information to ensure GNU
     12  #  General Public Licensing requirements will be met:
     13  #  http://www.trolltech.com/products/qt/opensource.html
     14  #
     15  #  If you are unsure which license is appropriate for your use, please
     16  #  review the following information:
     17  #  http://www.trolltech.com/products/qt/licensing.html or contact
     18  #  project-camelot@conceptive.be.
     19  #
     20  #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     21  #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     22  #
     23  #  For use of this library in commercial applications, please contact
     24  #  project-camelot@conceptive.be
     25  #
     26  #  ============================================================================
     27
     28  """Editors for various type of values"""
     29  import os
     30  import os.path
     31  import tempfile
     32  import datetime
     33  import logging
     34  logger = logging.getLogger('camelot.view.controls.editors')
     35
     36  import settings
     37
     38  from PyQt4.QtCore import Qt
     39  from PyQt4 import QtGui, QtCore
     40
     41  import camelot.types
     42  from camelot.view import art
     43  from camelot.view.model_thread import gui_function, model_function
     44  from camelot.view.workspace import get_workspace
     45  from camelot.view.search import create_entity_search_query_decorator
     46
     47 -def create_constant_function(constant):
     48    return lambda:constant
     49
     50
     51 -class DateEditor(QtGui.QWidget):
     52    """Widget for editing date values"""
     53 -  def __init__(self, nullable=True, format='dd/MM/yyyy', parent=None):
     54      super(DateEditor, self).__init__(parent)
     55      self.format = format
     56      self.qdateedit = QtGui.QDateEdit(self)
     57      self.connect(self.qdateedit,
     58                   QtCore.SIGNAL('editingFinished ()'),
     59                   self.editingFinished)
     60      self.qdateedit.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
     61      self.qdateedit.setDisplayFormat(QtCore.QString(format))
     62      self.hlayout = QtGui.QHBoxLayout()
     63      self.hlayout.addWidget(self.qdateedit)
     64
     65      if nullable:
     66        nullbutton = QtGui.QToolButton()
     67        nullbutton.setIcon(QtGui.QIcon(art.icon16('places/user-trash')))
     68        nullbutton.setAutoRaise(True)
     69        #nullbutton.setCheckable(True)
     70        self.connect(nullbutton, QtCore.SIGNAL('clicked()'), self.setMinimumDate)
     71        self.qdateedit.setSpecialValueText('0/0/0')
     72        self.hlayout.addWidget(nullbutton)
     73
     74      self.hlayout.setContentsMargins(0, 0, 0, 0)
     75      self.hlayout.setMargin(0)
     76      self.hlayout.setSpacing(0)
     77
     78      self.setContentsMargins(0, 0, 0, 0)
     79      self.setLayout(self.hlayout)
     80
     81      self.minimum = datetime.date.min
     82      self.maximum = datetime.date.max
     83      self.set_date_range()
     84
     85      self.setFocusProxy(self.qdateedit)
     86      self.setAutoFillBackground(True);
     87
     88 -  def _python_to_qt(self, value):
     89      return QtCore.QDate(value.year, value.month, value.day)
     90
     91 -  def _qt_to_python(self, value):
     92      return datetime.date(value.year(), value.month(), value.day())
     93
     94 -  def editingFinished(self):
     95      self.emit(QtCore.SIGNAL('editingFinished()'))
     96
     97 -  def set_date_range(self):
     98      qdate_min = self._python_to_qt(self.minimum)
     99      qdate_max = self._python_to_qt(self.maximum)
    100      self.qdateedit.setDateRange(qdate_min, qdate_max)
    101
    102 -  def date(self):
    103      return self.qdateedit.date()
    104
    105 -  def minimumDate(self):
    106      return self.qdateedit.minimumDate()
    107
    108 -  def setMinimumDate(self):
    109      self.qdateedit.setDate(self.minimumDate())
    110      self.emit(QtCore.SIGNAL('editingFinished()'))
    111
    112 -  def setDate(self, date):
    113      self.qdateedit.setDate(date)
    114
    115
    116 -class VirtualAddressEditor(QtGui.QWidget):
    117 -  def __init__(self, parent=None):
    118      super(VirtualAddressEditor, self).__init__(parent)
    119      layout = QtGui.QHBoxLayout()
    120      layout.setMargin(0)
    121      self.combo = QtGui.QComboBox()
    122      self.combo.addItems(camelot.types.VirtualAddress.virtual_address_types)
    123      layout.addWidget(self.combo)
    124      self.editor = QtGui.QLineEdit()
    125      layout.addWidget(self.editor)
    126      self.connect(self.editor, QtCore.SIGNAL('editingFinished()'), self.editingFinished)
    127      self.setLayout(layout)
    128      self.setAutoFillBackground(True);
    129
    130 -  def editingFinished(self):
    131      self.emit(QtCore.SIGNAL('editingFinished()'))
    132
    133
    134 -class CodeEditor(QtGui.QWidget):
    135 -  def __init__(self, parts=['99', 'AA'], parent=None):
    136      super(CodeEditor, self).__init__(parent)
    137      self.setFocusPolicy(Qt.StrongFocus)
    138      self.parts = parts
    139      self.part_editors = []
    140      layout = QtGui.QHBoxLayout()
    141      #layout.setSpacing(0)
    142      layout.setMargin(0)
    143      for part in parts:
    144        editor = QtGui.QLineEdit()
    145        editor.setInputMask(part)
    146        editor.installEventFilter(self)
    147        self.part_editors.append(editor)
    148        layout.addWidget(editor)
    149        self.connect(editor, QtCore.SIGNAL('editingFinished()'), self.editingFinished)
    150      self.setLayout(layout)
    151      self.setAutoFillBackground(True);
    152
    153 -  def editingFinished(self):
    154      self.emit(QtCore.SIGNAL('editingFinished()'))
    155
    156
    157 -class EmbeddedMany2OneEditor(QtGui.QWidget):
    158    """Widget for editing a many 2 one relation a a form embedded in another
    159    form.
    160    """
    161
    162 -  def __init__(self, admin=None, parent=None, **kwargs):
    163      assert admin != None
    164      super(EmbeddedMany2OneEditor, self).__init__(parent)
    165      self.admin = admin
    166      self.layout = QtGui.QHBoxLayout()
    167      self.entity_instance_getter = None
    168      self.form = None
    169      self.setLayout(self.layout)
    170      self.setEntity(lambda:None, propagate = False)
    171
    172 -  def setEntity(self, entity_instance_getter, propagate=True):
    173
    174      def create_instance_getter(entity_instance):
    175        return lambda:entity_instance
    176
    177      def set_entity_instance():
    178        entity = entity_instance_getter()
    179        if entity:
    180          self.entity_instance_getter = create_instance_getter(entity)
    181        else:
    182          self.entity_instance_getter = create_instance_getter(self.admin.entity())
    183
    184      def update_form(existing_entity):
    185        if self.form:
    186          self.form.deleteLater()
    187          self.layout.removeWidget(self.form)
    188
    189        from camelot.view.proxy.collection_proxy import CollectionProxy
    190
    191        def create_collection_getter(instance_getter):
    192          return lambda:[instance_getter()]
    193
    194        model = CollectionProxy(self.admin,
    195                                create_collection_getter(self.entity_instance_getter),
    196                                self.admin.getFields)
    197        self.form = self.admin.createFormView('', model, 0, self)
    198        self.layout.addWidget(self.form)
    199        if propagate:
    200          print 'emit editing finished'
    201          self.emit(QtCore.SIGNAL('editingFinished()'))
    202
    203      self.admin.mt.post(set_entity_instance, update_form)
    204
    205
    206 -class Many2OneEditor(QtGui.QWidget):
    207    """Widget for editing many 2 one relations
    208
    209    @param entity_admin : The Admin interface for the object on the one side of
    210    the relation
    211    """
    212
    213 -  class CompletionsModel(QtCore.QAbstractListModel):
    214 -    def __init__(self, parent=None):
    215        super(Many2OneEditor.CompletionsModel, self).__init__(parent)
    216        self._completions = []
    217
    218 -    def setCompletions(self, completions):
    219        self._completions = completions
    220        self.emit(QtCore.SIGNAL('layoutChanged()'))
    221
    222 -    def data(self, index, role):
    223        if role==Qt.DisplayRole:
    224          return QtCore.QVariant(self._completions[index.row()][0])
    225        elif role==Qt.EditRole:
    226          return QtCore.QVariant(self._completions[index.row()][1])
    227        return QtCore.QVariant()
    228
    229 -    def rowCount(self, index=None):
    230        return len(self._completions)
    231
    232 -    def columnCount(self, index=None):
    233        return 1
    234
    235 -  def __init__(self, entity_admin=None, parent=None):
    236      super(Many2OneEditor, self).__init__(parent)
    237      self.admin = entity_admin
    238      self.entity_instance_getter = None
    239      self._entity_representation = ''
    240      self.entity_set = False
    241      self.layout = QtGui.QHBoxLayout()
    242      self.layout.setSpacing(0)
    243      self.layout.setMargin(0)
    244      # Search button
    245      self.search_button = QtGui.QToolButton()
    246      self.search_button.setIcon(QtGui.QIcon(art.icon16('places/user-trash')))
    247      self.search_button.setAutoRaise(True)
    248      self.connect(self.search_button, QtCore.SIGNAL('clicked()'), self.searchButtonClicked)
    249      # Open button
    250      self.open_button = QtGui.QToolButton()
    251      self.open_button.setIcon(QtGui.QIcon(art.icon16('actions/document-new')))
    252      self.connect(self.open_button, QtCore.SIGNAL('clicked()'), self.openButtonClicked)
    253      self.open_button.setAutoRaise(True)
    254      # Search input
    255      self.search_input = QtGui.QLineEdit()
    256      #self.search_input.setReadOnly(True)
    257      #self.connect(self.search_input, QtCore.SIGNAL('returnPressed()'), self.returnPressed)
    258      self.connect(self.search_input, QtCore.SIGNAL('textEdited(const QString&)'), self.textEdited)
    259      # suppose garbage was entered, we need to refresh the content
    260      self.connect(self.search_input, QtCore.SIGNAL('editingFinished()'), self.editingFinished)
    261
    262      self.completer = QtGui.QCompleter()
    263      self.completions_model = self.CompletionsModel(self.completer)
    264      self.completer.setModel(self.completions_model)
    265      self.completer.setCaseSensitivity(Qt.CaseInsensitive)
    266      self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
    267      self.connect(self.completer, QtCore.SIGNAL('activated(const QModelIndex&)'), self.completionActivated)
    268      self.search_input.setCompleter(self.completer)
    269      # Setup layout
    270      self.layout.addWidget(self.search_input)
    271      self.layout.addWidget(self.open_button)
    272      self.layout.addWidget(self.search_button)
    273      self.setLayout(self.layout)
    274      self.setAutoFillBackground(True);
    275
    276 -  def textEdited(self, text):
    277      def create_search_completion(text):
    278        return lambda: self.search_completions(text)
    279
    280      self.admin.mt.post(create_search_completion(unicode(text)), self.display_search_completions)
    281      self.completer.complete()
    282
    283    @model_function
    284 -  def search_completions(self, text):
    285      """Search for object that match text, to fill the list of completions
    286
    287      @return: a list of tuples of (object_representation, object_getter)
    288      """
    289      search_decorator = create_entity_search_query_decorator(self.admin, text)
    290      return [(unicode(e),create_constant_function(e))
    291              for e in search_decorator(self.admin.entity.query).limit(20)]
    292
    293    @gui_function
    294 -  def display_search_completions(self, completions):
    295      self.completions_model.setCompletions(completions)
    296      self.completer.complete()
    297
    298 -  def completionActivated(self, index):
    299      object_getter = index.data(Qt.EditRole)
    300      self.setEntity(object_getter.toPyObject())
    301
    302 -  def openButtonClicked(self):
    303      if self.entity_set:
    304        return self.createFormView()
    305      else:
    306        return self.createNew()
    307
    308 -  def returnPressed(self):
    309      if not self.entity_set:
    310        self.createSelectView()
    311
    312 -  def searchButtonClicked(self):
    313      if self.entity_set:
    314        self.setEntity(lambda:None)
    315      else:
    316        self.createSelectView()
    317
    318 -  def trashButtonClicked(self):
    319      self.setEntity(lambda:None)
    320
    321 -  def createNew(self):
    322      workspace = get_workspace()
    323      form = self.admin.createNewView(workspace)
    324      workspace.addSubWindow(form)
    325      self.connect(form, form.entity_created_signal, self.selectEntity)
    326      form.show()
    327
    328 -  def createFormView(self):
    329      if self.entity_instance_getter:
    330
    331        def create_collection_getter(instance_getter):
    332          return lambda:[instance_getter()]
    333
    334        from camelot.view.proxy.collection_proxy import CollectionProxy
    335
    336        workspace = get_workspace()
    337        model = CollectionProxy(self.admin,
    338                          create_collection_getter(self.entity_instance_getter),
    339                          self.admin.getFields)
    340        form = self.admin.createFormView('', model, 0, workspace)
    341        workspace.addSubWindow(form)
    342        form.show()
    343
    344 -  def editingFinished(self):
    345      self.search_input.setText(self._entity_representation)
    346
    347 -  def setEntity(self, entity_instance_getter, propagate=True):
    348
    349      def create_instance_getter(entity_instance):
    350        return lambda:entity_instance
    351
    352      def get_instance_represenation():
    353        """Get a representation of the instance
    354
    355        @return: (unicode, pk) its unicode representation and its primary key
    356        or ('', False) if the instance was None
    357        """
    358        entity = entity_instance_getter()
    359        self.entity_instance_getter = create_instance_getter(entity)
    360        if entity and hasattr(entity, 'id'):
    361          return (unicode(entity), entity.id)
    362        return ('', False)
    363
    364      def set_instance_represenation(representation):
    365        """Update the gui"""
    366        desc, pk = representation
    367        self._entity_representation = desc
    368        self.search_input.setText(desc)
    369        if pk != False:
    370          open_icon = QtGui.QIcon(art.icon16('places/folder'))
    371          search_icon = QtGui.QIcon(art.icon16('places/user-trash'))
    372          self.open_button.setIcon(open_icon)
    373          self.search_button.setIcon(search_icon)
    374          self.entity_set = True
    375          #self.search_input.setReadOnly(True)
    376        else:
    377          open_icon = QtGui.QIcon(art.icon16('actions/document-new'))
    378          search_icon = QtGui.QIcon(art.icon16('actions/system-search'))
    379          self.open_button.setIcon(open_icon)
    380          self.search_button.setIcon(search_icon)
    381          self.entity_set = False
    382          #self.search_input.setReadOnly(False)
    383        if propagate:
    384          self.emit(QtCore.SIGNAL('editingFinished()'))
    385
    386      self.admin.mt.post(get_instance_represenation, set_instance_represenation)
    387
    388 -  def createSelectView(self):
    389      workspace = get_workspace()
    390      search_text = unicode(self.search_input.text())
    391      select = self.admin.createSelectView(self.admin.entity.query,
    392                                           parent=workspace,
    393                                           search_text=search_text)
    394      self.connect(select, select.entity_selected_signal, self.selectEntity)
    395      workspace.addSubWindow(select)
    396      select.show()
    397
    398 -  def selectEntity(self, entity_instance_getter):
    399      self.setEntity(entity_instance_getter)
    400
    401
    402 -class One2ManyEditor(QtGui.QWidget):
    403 -  def __init__(self, admin=None, parent=None, create_inline=False, **kw):
    404      """@param admin: the Admin interface for the objects on the one side of
    405      the relation
    406
    407      @param create_inline: if False, then a new entity will be created within a
    408      new window, if True, it will be created inline
    409
    410      after creating the editor, setEntityInstance needs to be called to set the
    411      actual data to the editor
    412      """
    413      QtGui.QWidget.__init__(self, parent)
    414      layout = QtGui.QHBoxLayout()
    415      layout.setContentsMargins(0, 0, 0, 0)
    416      #
    417      # Setup table
    418      #
    419      from tableview import QueryTable
    420      # parent set by layout manager
    421      self.table = QueryTable()
    422      layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint)
    423      layout.addWidget(self.table)
    424      self.setSizePolicy(QtGui.QSizePolicy.Expanding,
    425                         QtGui.QSizePolicy.Expanding)
    426      self.connect(self.table.verticalHeader(),
    427                   QtCore.SIGNAL('sectionClicked(int)'),
    428                   self.createFormForIndex)
    429      self.admin = admin
    430      self.create_inline = create_inline
    431      #
    432      # Setup buttons
    433      #
    434      button_layout = QtGui.QVBoxLayout()
    435      button_layout.setSpacing(0)
    436      delete_button = QtGui.QToolButton()
    437      delete_button.setIcon(QtGui.QIcon(art.icon16('places/user-trash')))
    438      delete_button.setAutoRaise(True)
    439      self.connect(delete_button,
    440                   QtCore.SIGNAL('clicked()'),
    441                   self.deleteSelectedRows)
    442      add_button = QtGui.QToolButton()
    443      add_button.setIcon(QtGui.QIcon(art.icon16('actions/document-new')))
    444      add_button.setAutoRaise(True)
    445      self.connect(add_button, QtCore.SIGNAL('clicked()'), self.newRow)
    446      button_layout.addStretch()
    447      button_layout.addWidget(add_button)
    448      button_layout.addWidget(delete_button)
    449      layout.addLayout(button_layout)
    450      self.setLayout(layout)
    451      self.model = None
    452
    453 -  def setModel(self, model):
    454      self.model = model
    455      self.table.setModel(model)
    456
    457      def create_fill_model_cache(model):
    458        def fill_model_cache():
    459          model._extend_cache(0, 10)
    460
    461        return fill_model_cache
    462
    463      def create_delegate_updater(model):
    464        def update_delegates(*args):
    465          self.table.setItemDelegate(model.getItemDelegate())
    466          #hheader = self.table.horizontalHeader()
    467          #hheader.setResizeMode(QtGui.QHeaderView.Interactive)
    468          #hheader.setStretchLastSection(True)
    469          # resize the columns if there is data in the rows
    470          if self.model.rowCount() > 1:
    471            self.table.resizeColumnsToContents()
    472
    473        return update_delegates
    474
    475      self.admin.mt.post(create_fill_model_cache(model),
    476                         create_delegate_updater(model))
    477
    478 -  def newRow(self):
    479      workspace = get_workspace()
    480
    481      if self.create_inline:
    482
    483        @model_function
    484        def create():
    485          o = self.admin.entity()
    486          self.model.insertEntityInstance(0,o)
    487          self.admin.setDefaults(o)
    488
    489        self.admin.mt.post(create)
    490
    491      else:
    492        prependentity = lambda o: self.model.insertEntityInstance(0, o)
    493        removeentity = lambda o: self.model.removeEntityInstance(o)
    494        form = self.admin.createNewView(workspace,
    495                                        oncreate=prependentity,
    496                                        onexpunge=removeentity)
    497        workspace.addSubWindow(form)
    498        form.show()
    499
    500 -  def deleteSelectedRows(self):
    501      """Delete the selected rows in this tableview"""
    502      logger.debug('delete selected rows called')
    503      for row in set(map(lambda x: x.row(), self.table.selectedIndexes())):
    504        self.model.removeRow(row)
    505
    506 -  def createFormForIndex(self, index):
    507      from camelot.view.proxy.collection_proxy import CollectionProxy
    508      model = CollectionProxy(self.admin,
    509                              self.model.collection_getter,
    510                              self.admin.getFields,
    511                              max_number_of_rows=1,
    512                              edits=None)
    513      title = self.admin.getName()
    514      form = self.admin.createFormView(title, model, index, get_workspace())
    515      get_workspace().addSubWindow(form)
    516      form.show()
    517
    518
    519  try:
    520    from PIL import Image as PILImage
    521  except:
    522    import Image as PILImage
    523
    524  filter = """Image files (*.bmp *.jpg *.jpeg *.mng *.png *.pbm *.pgm """\
    525           """*.ppm *.tiff *.xbm *.xpm)
    526  All files (*)"""
    527
    528
    529 -class ImageEditor(QtGui.QWidget):
    530 -  def __init__(self, parent=None):
    531      QtGui.QWidget.__init__(self, parent)
    532      self.image = None
    533      self.layout = QtGui.QHBoxLayout()
    534      #
    535      # Setup label
    536      #
    537      self.label = QtGui.QLabel(parent)
    538      self.layout.addWidget(self.label)
    539      self.label.setAcceptDrops(True)
    540      # self.draw_border()
    541      self.label.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter)
    542      self.label.__class__.dragEnterEvent = self.dragEnterEvent
    543      self.label.__class__.dragMoveEvent = self.dragEnterEvent
    544      self.label.__class__.dropEvent = self.dropEvent
    545      #
    546      # Setup buttons
    547      #
    548      button_layout = QtGui.QVBoxLayout()
    549      button_layout.setSpacing(0)
    550      button_layout.setMargin(0)
    551
    552      file_button = QtGui.QToolButton()
    553      file_button.setIcon( QtGui.QIcon(art.icon16('actions/document-new')))
    554      file_button.setAutoRaise(True)
    555      file_button.setToolTip('Select image')
    556      self.connect(file_button, QtCore.SIGNAL('clicked()'), self.openFileDialog)
    557
    558      app_button = QtGui.QToolButton()
    559      app_button.setIcon( QtGui.QIcon(art.icon16('status/folder-open')))
    560      app_button.setAutoRaise(True)
    561      app_button.setToolTip('Open image')
    562      self.connect(app_button, QtCore.SIGNAL('clicked()'), self.openInApp)
    563
    564      clear_button = QtGui.QToolButton()
    565      clear_button.setIcon( QtGui.QIcon(art.icon16('places/user-trash')))
    566      clear_button.setToolTip('Clear image')
    567      clear_button.setAutoRaise(True)
    568      self.connect(clear_button, QtCore.SIGNAL('clicked()'), self.clearImage)
    569
    570      vspacerItem = QtGui.QSpacerItem(20,20,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
    571
    572      button_layout.addItem(vspacerItem)
    573      button_layout.addWidget(file_button)
    574      button_layout.addWidget(app_button)
    575      button_layout.addWidget(clear_button)
    576
    577      self.layout.addLayout(button_layout)
    578
    579      hspacerItem = QtGui.QSpacerItem(20,20,QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
    580      self.layout.addItem(hspacerItem)
    581      self.setLayout(self.layout)
    582      #
    583      # Image
    584      #
    585      self.dummy_image = os.path.normpath(art.icon32('apps/help-browser'))
    586      if self.image is None:
    587        testImage = QtGui.QImage(self.dummy_image)
    588        if not testImage.isNull():
    589          fp = open(self.dummy_image, 'rb')
    590          self.image = PILImage.open(fp)
    591          self.setPixmap(QtGui.QPixmap(self.dummy_image))
    592
    593    #
    594    # Drag & Drop
    595    #
    596 -  def dragEnterEvent(self, event):
    597      event.acceptProposedAction()
    598
    599 -  def dragMoveEvent(self, event):
    600      event.acceptProposedAction()
    601
    602 -  def dropEvent(self, event):
    603      if event.mimeData().hasUrls():
    604        url = event.mimeData().urls()[0]
    605        filename = url.toLocalFile()
    606        if filename != '':
    607          self.pilimage_from_file(filename)
    608
    609    #
    610    # Buttons methods
    611    #
    612 -  def clearImage(self):
    613      self.pilimage_from_file(self.dummy_image)
    614      self.draw_border()
    615
    616 -  def openFileDialog(self):
    617      filename = QtGui.QFileDialog.getOpenFileName(self, 'Open file',
    618                                                   QtCore.QDir.currentPath(),
    619                                                   filter)
    620      if filename != '':
    621        self.pilimage_from_file(filename)
    622
    623 -  def openInApp(self):
    624      if self.image != None:
    625        tmpfp, tmpfile = tempfile.mkstemp(suffix='.png')
    626        self.image.save(os.fdopen(tmpfp, 'wb'), 'png')
    627        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(tmpfile))
    628
    629    #
    630    # Utils methods
    631    #
    632 -  def pilimage_from_file(self, filepath):
    633      testImage = QtGui.QImage(filepath)
    634      if not testImage.isNull():
    635        fp = open(filepath, 'rb')
    636        self.image = PILImage.open(fp)
    637        self.emit(QtCore.SIGNAL('editingFinished()'))
    638
    639 -  def draw_border(self):
    640      self.label.setFrameShape(QtGui.QFrame.Box)
    641      self.label.setFrameShadow(QtGui.QFrame.Plain)
    642      self.label.setLineWidth(1)
    643      self.label.setFixedSize(100, 100)
    644
    645 -  def setPixmap(self, pixmap):
    646      self.label.setPixmap(pixmap)
    647      self.draw_border()
    648
    649 -  def clearFirstImage(self):
    650      testImage = QtGui.QImage(self.dummy_image)
    651      if not testImage.isNull():
    652        fp = open(self.dummy_image, 'rb')
    653        self.image = PILImage.open(fp)
    654      self.draw_border()
    655
    656
    657 -class RichTextEditor(QtGui.QWidget):
    658 -  def __init__(self, parent=None, editable=True, **kwargs):
    659      QtGui.QWidget.__init__(self, parent)
    660
    661      self.layout = QtGui.QVBoxLayout(self)
    662      self.layout.setSpacing(0)
    663      self.layout.setMargin(0)
    664      self.editable = editable
    665
    666      #
    667      # Textedit
    668      #
    669
    670      class CustomTextEdit(QtGui.QTextEdit):
    671
    672        def __init__(self, parent):
    673          super(CustomTextEdit, self).__init__(parent)
    674        def focusOutEvent(self, event):
    675          # this seems to cause weird behaviour, where editingFinished is fired, even
    676          # if nothing has been edited yet
    677          #self.emit(QtCore.SIGNAL('editingFinished()'))
    678          pass
    679
    680      self.textedit = CustomTextEdit(self)
    681
    682      self.connect(self.textedit, QtCore.SIGNAL('editingFinished()'), self.editingFinished)
    683      self.textedit.setAcceptRichText(True)
    684
    685      if not self.editable:
    686        self.textedit.setReadOnly(True)
    687      else:
    688        #
    689        # Buttons setup
    690        #
    691        self.toolbar = QtGui.QToolBar(self)
    692        self.toolbar.setContentsMargins(0,0,0,0)
    693        self.bold_button = QtGui.QToolButton(self)
    694        self.bold_button.setIcon( QtGui.QIcon(art.icon16('actions/format-text-bold')))
    695        self.bold_button.setAutoRaise(True)
    696        self.bold_button.setCheckable(True)
    697        self.bold_button.setMaximumSize(QtCore.QSize(20,20))
    698        self.bold_button.setShortcut(QtGui.QKeySequence('Ctrl+B'))
    699        self.connect(self.bold_button, QtCore.SIGNAL('clicked()'), self.set_bold)
    700        self.italic_button = QtGui.QToolButton(self)
    701        self.italic_button.setIcon(QtGui.QIcon(art.icon16('actions/format-text-italic')))
    702        self.italic_button.setAutoRaise(True)
    703        self.italic_button.setCheckable(True)
    704        self.italic_button.setMaximumSize(QtCore.QSize(20,20))
    705        self.italic_button.setShortcut(QtGui.QKeySequence('Ctrl+I'))
    706        self.connect(self.italic_button, QtCore.SIGNAL('clicked(bool)'), self.set_italic)
    707
    708        self.underline_button = QtGui.QToolButton(self)
    709        self.underline_button.setIcon(QtGui.QIcon(art.icon16('actions/format-text-underline')))
    710        self.underline_button.setAutoRaise(True)
    711        self.underline_button.setCheckable(True)
    712        self.underline_button.setMaximumSize(QtCore.QSize(20,20))
    713        self.underline_button.setShortcut(QtGui.QKeySequence('Ctrl+U'))
    714        self.connect(self.underline_button, QtCore.SIGNAL('clicked(bool)'), self.set_underline)
    715
    716        self.copy_button = QtGui.QToolButton(self)
    717        self.copy_button.setIcon(QtGui.QIcon(art.icon16('actions/edit-copy')))
    718        self.copy_button.setAutoRaise(True)
    719        self.copy_button.setMaximumSize(QtCore.QSize(20,20))
    720        self.connect(self.copy_button, QtCore.SIGNAL('clicked(bool)'), self.textedit.copy)
    721
    722        self.cut_button = QtGui.QToolButton(self)
    723        self.cut_button.setIcon(QtGui.QIcon(art.icon16('actions/edit-cut')))
    724        self.cut_button.setAutoRaise(True)
    725        self.cut_button.setMaximumSize(QtCore.QSize(20,20))
    726        self.connect(self.cut_button, QtCore.SIGNAL('clicked(bool)'), self.textedit.cut)
    727
    728        self.paste_button = QtGui.QToolButton(self)
    729        self.paste_button.setIcon(QtGui.QIcon(art.icon16('actions/edit-paste')))
    730        self.paste_button.setAutoRaise(True)
    731        self.paste_button.setMaximumSize(QtCore.QSize(20,20))
    732        self.connect(self.paste_button, QtCore.SIGNAL('clicked(bool)'), self.textedit.paste)
    733
    734        self.alignleft_button = QtGui.QToolButton(self)
    735        self.alignleft_button.setIcon(QtGui.QIcon(art.icon16('actions/format-justify-left')))
    736        self.alignleft_button.setAutoRaise(True)
    737        self.alignleft_button.setCheckable(True)
    738        self.alignleft_button.setMaximumSize(QtCore.QSize(20,20))
    739        self.connect(self.alignleft_button, QtCore.SIGNAL('clicked(bool)'), self.set_alignleft)
    740
    741        self.aligncenter_button = QtGui.QToolButton(self)
    742        self.aligncenter_button.setIcon(QtGui.QIcon(art.icon16('actions/format-justify-center')))
    743        self.aligncenter_button.setAutoRaise(True)
    744        self.aligncenter_button.setCheckable(True)
    745        self.aligncenter_button.setMaximumSize(QtCore.QSize(20,20))
    746        self.connect(self.aligncenter_button, QtCore.SIGNAL('clicked(bool)'), self.set_aligncenter)
    747
    748        self.alignright_button = QtGui.QToolButton(self)
    749        self.alignright_button.setIcon(QtGui.QIcon(art.icon16('actions/format-justify-right')))
    750        self.alignright_button.setAutoRaise(True)
    751        self.alignright_button.setCheckable(True)
    752        self.alignright_button.setMaximumSize(QtCore.QSize(20,20))
    753        self.connect(self.alignright_button, QtCore.SIGNAL('clicked(bool)'), self.set_alignright)
    754
    755        self.color_button = QtGui.QToolButton(self)
    756        self.color_button.setAutoRaise(True)
    757        self.color_button.setMaximumSize(QtCore.QSize(20,20))
    758        self.connect(self.color_button, QtCore.SIGNAL('clicked(bool)'), self.set_color)
    759
    760        self.toolbar.addWidget(self.copy_button)
    761        self.toolbar.addWidget(self.cut_button)
    762        self.toolbar.addWidget(self.paste_button)
    763        self.toolbar.addSeparator()
    764        self.toolbar.addWidget(self.bold_button)
    765        self.toolbar.addWidget(self.italic_button)
    766        self.toolbar.addWidget(self.underline_button)
    767        self.toolbar.addSeparator()
    768        self.toolbar.addWidget(self.alignleft_button)
    769        self.toolbar.addWidget(self.aligncenter_button)
    770        self.toolbar.addWidget(self.alignright_button)
    771        self.toolbar.addSeparator()
    772        self.toolbar.addWidget(self.color_button)
    773
    774        #
    775        # Layout
    776        #
    777        self.layout.addWidget(self.toolbar)
    778      self.layout.addWidget(self.textedit)
    779
    780      self.setLayout(self.layout)
    781
    782      #
    783      # Format
    784      #
    785      self.textedit.setFontWeight(QtGui.QFont.Normal)
    786      self.textedit.setFontItalic(False)
    787      self.textedit.setFontUnderline(False)
    788      self.textedit.setFocus(Qt.OtherFocusReason)
    789      self.update_alignment()
    790
    791      if self.editable:
    792        self.connect(self.textedit, QtCore.SIGNAL('currentCharFormatChanged (const QTextCharFormat&)'), self.update_format)
    793        self.connect(self.textedit, QtCore.SIGNAL('cursorPositionChanged ()'), self.update_text)
    794
    795 -  def editingFinished(self):
    796      print 'rich text editing finished'
    797      self.emit(QtCore.SIGNAL('editingFinished()'))
    798
    799    #
    800    # Button methods
    801    #
    802 -  def set_bold(self):
    803      if self.bold_button.isChecked():
    804        self.textedit.setFocus(Qt.OtherFocusReason)
    805        self.textedit.setFontWeight(QtGui.QFont.Bold)
    806      else:
    807        self.textedit.setFocus(Qt.OtherFocusReason)
    808        self.textedit.setFontWeight(QtGui.QFont.Normal)
    809
    810 -  def set_italic(self, bool):
    811      if bool:
    812        self.textedit.setFocus(Qt.OtherFocusReason)
    813        self.textedit.setFontItalic(True)
    814      else:
    815        self.textedit.setFocus(Qt.OtherFocusReason)
    816        self.textedit.setFontItalic(False)
    817
    818 -  def set_underline(self, bool):
    819      if bool:
    820        self.textedit.setFocus(Qt.OtherFocusReason)
    821        self.textedit.setFontUnderline(True)
    822      else:
    823        self.textedit.setFocus(Qt.OtherFocusReason)
    824        self.textedit.setFontUnderline(False)
    825
    826
    827 -  def set_alignleft(self, bool):
    828      if bool:
    829        self.textedit.setFocus(Qt.OtherFocusReason)
    830        self.textedit.setAlignment(Qt.AlignLeft)
    831      self.update_alignment(Qt.AlignLeft)
    832
    833 -  def set_aligncenter(self, bool):
    834      if bool:
    835        self.textedit.setFocus(Qt.OtherFocusReason)
    836        self.textedit.setAlignment(Qt.AlignCenter)
    837      self.update_alignment(Qt.AlignCenter)
    838
    839 -  def set_alignright(self, bool):
    840      if bool:
    841        self.textedit.setFocus(Qt.OtherFocusReason)
    842        self.textedit.setAlignment(Qt.AlignRight)
    843      self.update_alignment(Qt.AlignRight)
    844
    845 -  def update_alignment(self, al=None):
    846      if self.editable:
    847        if al is None:
    848            al = self.textedit.alignment()
    849        if al == Qt.AlignLeft:
    850            self.alignleft_button.setChecked(True)
    851            self.aligncenter_button.setChecked(False)
    852            self.alignright_button.setChecked(False)
    853        elif al == Qt.AlignCenter:
    854            self.aligncenter_button.setChecked(True)
    855            self.alignleft_button.setChecked(False)
    856            self.alignright_button.setChecked(False)
    857        elif al == Qt.AlignRight:
    858            self.alignright_button.setChecked(True)
    859            self.alignleft_button.setChecked(False)
    860            self.aligncenter_button.setChecked(False)
    861
    862 -  def set_color(self):
    863      color = QtGui.QColorDialog.getColor(self.textedit.textColor())
    864      if color.isValid():
    865        self.textedit.setFocus(Qt.OtherFocusReason)
    866        self.textedit.setTextColor(color)
    867        pixmap = QtGui.QPixmap(16,16)
    868        pixmap.fill(color)
    869        self.color_button.setIcon(QtGui.QIcon(pixmap))
    870
    871 -  def update_color(self):
    872      if self.editable:
    873        color = self.textedit.textColor()
    874        pixmap = QtGui.QPixmap(16,16)
    875        pixmap.fill(color)
    876        self.color_button.setIcon(QtGui.QIcon(pixmap))
    877
    878 -  def update_format(self, format):
    879      if self.editable:
    880        font = format.font()
    881        self.bold_button.setChecked(font.bold())
    882        self.italic_button.setChecked(font.italic())
    883        self.underline_button.setChecked(font.underline())
    884        self.update_alignment(self.textedit.alignment())
    885
    886 -  def update_text(self):
    887      if self.editable:
    888        self.update_alignment()
    889        self.update_color()
    890
    891    #
    892    # Textedit functions
    893    #
    894 -  def clear(self):
    895      self.textedit.clear()
    896
    897 -  def setHtml(self, html):
    898      self.update_alignment()
    899      self.textedit.setHtml(html)
    900      self.update_color()
    901
    902 -  def toHtml(self):
    903      return self.textedit.toHtml()
    904