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