1#!/usr/bin/env python 2 3 4############################################################################# 5## 6## Copyright (C) 2013 Riverbank Computing Limited. 7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 8## All rights reserved. 9## 10## This file is part of the examples of PyQt. 11## 12## $QT_BEGIN_LICENSE:BSD$ 13## You may use this file under the terms of the BSD license as follows: 14## 15## "Redistribution and use in source and binary forms, with or without 16## modification, are permitted provided that the following conditions are 17## met: 18## * Redistributions of source code must retain the above copyright 19## notice, this list of conditions and the following disclaimer. 20## * Redistributions in binary form must reproduce the above copyright 21## notice, this list of conditions and the following disclaimer in 22## the documentation and/or other materials provided with the 23## distribution. 24## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor 25## the names of its contributors may be used to endorse or promote 26## products derived from this software without specific prior written 27## permission. 28## 29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 40## $QT_END_LICENSE$ 41## 42############################################################################# 43 44 45import math 46 47from PyQt5.QtCore import (pyqtSignal, QLineF, QPointF, QRect, QRectF, QSize, 48 QSizeF, Qt) 49from PyQt5.QtGui import (QBrush, QColor, QFont, QIcon, QIntValidator, QPainter, 50 QPainterPath, QPen, QPixmap, QPolygonF) 51from PyQt5.QtWidgets import (QAction, QApplication, QButtonGroup, QComboBox, 52 QFontComboBox, QGraphicsItem, QGraphicsLineItem, QGraphicsPolygonItem, 53 QGraphicsScene, QGraphicsTextItem, QGraphicsView, QGridLayout, 54 QHBoxLayout, QLabel, QMainWindow, QMenu, QMessageBox, QSizePolicy, 55 QToolBox, QToolButton, QWidget) 56 57import diagramscene_rc 58 59 60class Arrow(QGraphicsLineItem): 61 def __init__(self, startItem, endItem, parent=None, scene=None): 62 super(Arrow, self).__init__(parent, scene) 63 64 self.arrowHead = QPolygonF() 65 66 self.myStartItem = startItem 67 self.myEndItem = endItem 68 self.setFlag(QGraphicsItem.ItemIsSelectable, True) 69 self.myColor = Qt.black 70 self.setPen(QPen(self.myColor, 2, Qt.SolidLine, Qt.RoundCap, 71 Qt.RoundJoin)) 72 73 def setColor(self, color): 74 self.myColor = color 75 76 def startItem(self): 77 return self.myStartItem 78 79 def endItem(self): 80 return self.myEndItem 81 82 def boundingRect(self): 83 extra = (self.pen().width() + 20) / 2.0 84 p1 = self.line().p1() 85 p2 = self.line().p2() 86 return QRectF(p1, QSizeF(p2.x() - p1.x(), p2.y() - p1.y())).normalized().adjusted(-extra, -extra, extra, extra) 87 88 def shape(self): 89 path = super(Arrow, self).shape() 90 path.addPolygon(self.arrowHead) 91 return path 92 93 def updatePosition(self): 94 line = QLineF(self.mapFromItem(self.myStartItem, 0, 0), self.mapFromItem(self.myEndItem, 0, 0)) 95 self.setLine(line) 96 97 def paint(self, painter, option, widget=None): 98 if (self.myStartItem.collidesWithItem(self.myEndItem)): 99 return 100 101 myStartItem = self.myStartItem 102 myEndItem = self.myEndItem 103 myColor = self.myColor 104 myPen = self.pen() 105 myPen.setColor(self.myColor) 106 arrowSize = 20.0 107 painter.setPen(myPen) 108 painter.setBrush(self.myColor) 109 110 centerLine = QLineF(myStartItem.pos(), myEndItem.pos()) 111 endPolygon = myEndItem.polygon() 112 p1 = endPolygon.first() + myEndItem.pos() 113 114 intersectPoint = QPointF() 115 for i in endPolygon: 116 p2 = i + myEndItem.pos() 117 polyLine = QLineF(p1, p2) 118 intersectType = polyLine.intersect(centerLine, intersectPoint) 119 if intersectType == QLineF.BoundedIntersection: 120 break 121 p1 = p2 122 123 self.setLine(QLineF(intersectPoint, myStartItem.pos())) 124 line = self.line() 125 126 angle = math.acos(line.dx() / line.length()) 127 if line.dy() >= 0: 128 angle = (math.pi * 2.0) - angle 129 130 arrowP1 = line.p1() + QPointF(math.sin(angle + math.pi / 3.0) * arrowSize, 131 math.cos(angle + math.pi / 3) * arrowSize) 132 arrowP2 = line.p1() + QPointF(math.sin(angle + math.pi - math.pi / 3.0) * arrowSize, 133 math.cos(angle + math.pi - math.pi / 3.0) * arrowSize) 134 135 self.arrowHead.clear() 136 for point in [line.p1(), arrowP1, arrowP2]: 137 self.arrowHead.append(point) 138 139 painter.drawLine(line) 140 painter.drawPolygon(self.arrowHead) 141 if self.isSelected(): 142 painter.setPen(QPen(myColor, 1, Qt.DashLine)) 143 myLine = QLineF(line) 144 myLine.translate(0, 4.0) 145 painter.drawLine(myLine) 146 myLine.translate(0,-8.0) 147 painter.drawLine(myLine) 148 149 150class DiagramTextItem(QGraphicsTextItem): 151 lostFocus = pyqtSignal(QGraphicsTextItem) 152 153 selectedChange = pyqtSignal(QGraphicsItem) 154 155 def __init__(self, parent=None, scene=None): 156 super(DiagramTextItem, self).__init__(parent, scene) 157 158 self.setFlag(QGraphicsItem.ItemIsMovable) 159 self.setFlag(QGraphicsItem.ItemIsSelectable) 160 161 def itemChange(self, change, value): 162 if change == QGraphicsItem.ItemSelectedChange: 163 self.selectedChange.emit(self) 164 return value 165 166 def focusOutEvent(self, event): 167 self.setTextInteractionFlags(Qt.NoTextInteraction) 168 self.lostFocus.emit(self) 169 super(DiagramTextItem, self).focusOutEvent(event) 170 171 def mouseDoubleClickEvent(self, event): 172 if self.textInteractionFlags() == Qt.NoTextInteraction: 173 self.setTextInteractionFlags(Qt.TextEditorInteraction) 174 super(DiagramTextItem, self).mouseDoubleClickEvent(event) 175 176 177class DiagramItem(QGraphicsPolygonItem): 178 Step, Conditional, StartEnd, Io = range(4) 179 180 def __init__(self, diagramType, contextMenu, parent=None): 181 super(DiagramItem, self).__init__(parent) 182 183 self.arrows = [] 184 185 self.diagramType = diagramType 186 self.contextMenu = contextMenu 187 188 path = QPainterPath() 189 if self.diagramType == self.StartEnd: 190 path.moveTo(200, 50) 191 path.arcTo(150, 0, 50, 50, 0, 90) 192 path.arcTo(50, 0, 50, 50, 90, 90) 193 path.arcTo(50, 50, 50, 50, 180, 90) 194 path.arcTo(150, 50, 50, 50, 270, 90) 195 path.lineTo(200, 25) 196 self.myPolygon = path.toFillPolygon() 197 elif self.diagramType == self.Conditional: 198 self.myPolygon = QPolygonF([ 199 QPointF(-100, 0), QPointF(0, 100), 200 QPointF(100, 0), QPointF(0, -100), 201 QPointF(-100, 0)]) 202 elif self.diagramType == self.Step: 203 self.myPolygon = QPolygonF([ 204 QPointF(-100, -100), QPointF(100, -100), 205 QPointF(100, 100), QPointF(-100, 100), 206 QPointF(-100, -100)]) 207 else: 208 self.myPolygon = QPolygonF([ 209 QPointF(-120, -80), QPointF(-70, 80), 210 QPointF(120, 80), QPointF(70, -80), 211 QPointF(-120, -80)]) 212 213 self.setPolygon(self.myPolygon) 214 self.setFlag(QGraphicsItem.ItemIsMovable, True) 215 self.setFlag(QGraphicsItem.ItemIsSelectable, True) 216 217 def removeArrow(self, arrow): 218 try: 219 self.arrows.remove(arrow) 220 except ValueError: 221 pass 222 223 def removeArrows(self): 224 for arrow in self.arrows[:]: 225 arrow.startItem().removeArrow(arrow) 226 arrow.endItem().removeArrow(arrow) 227 self.scene().removeItem(arrow) 228 229 def addArrow(self, arrow): 230 self.arrows.append(arrow) 231 232 def image(self): 233 pixmap = QPixmap(250, 250) 234 pixmap.fill(Qt.transparent) 235 painter = QPainter(pixmap) 236 painter.setPen(QPen(Qt.black, 8)) 237 painter.translate(125, 125) 238 painter.drawPolyline(self.myPolygon) 239 return pixmap 240 241 def contextMenuEvent(self, event): 242 self.scene().clearSelection() 243 self.setSelected(True) 244 self.myContextMenu.exec_(event.screenPos()) 245 246 def itemChange(self, change, value): 247 if change == QGraphicsItem.ItemPositionChange: 248 for arrow in self.arrows: 249 arrow.updatePosition() 250 251 return value 252 253 254class DiagramScene(QGraphicsScene): 255 InsertItem, InsertLine, InsertText, MoveItem = range(4) 256 257 itemInserted = pyqtSignal(DiagramItem) 258 259 textInserted = pyqtSignal(QGraphicsTextItem) 260 261 itemSelected = pyqtSignal(QGraphicsItem) 262 263 def __init__(self, itemMenu, parent=None): 264 super(DiagramScene, self).__init__(parent) 265 266 self.myItemMenu = itemMenu 267 self.myMode = self.MoveItem 268 self.myItemType = DiagramItem.Step 269 self.line = None 270 self.textItem = None 271 self.myItemColor = Qt.white 272 self.myTextColor = Qt.black 273 self.myLineColor = Qt.black 274 self.myFont = QFont() 275 276 def setLineColor(self, color): 277 self.myLineColor = color 278 if self.isItemChange(Arrow): 279 item = self.selectedItems()[0] 280 item.setColor(self.myLineColor) 281 self.update() 282 283 def setTextColor(self, color): 284 self.myTextColor = color 285 if self.isItemChange(DiagramTextItem): 286 item = self.selectedItems()[0] 287 item.setDefaultTextColor(self.myTextColor) 288 289 def setItemColor(self, color): 290 self.myItemColor = color 291 if self.isItemChange(DiagramItem): 292 item = self.selectedItems()[0] 293 item.setBrush(self.myItemColor) 294 295 def setFont(self, font): 296 self.myFont = font 297 if self.isItemChange(DiagramTextItem): 298 item = self.selectedItems()[0] 299 item.setFont(self.myFont) 300 301 def setMode(self, mode): 302 self.myMode = mode 303 304 def setItemType(self, type): 305 self.myItemType = type 306 307 def editorLostFocus(self, item): 308 cursor = item.textCursor() 309 cursor.clearSelection() 310 item.setTextCursor(cursor) 311 312 if item.toPlainText(): 313 self.removeItem(item) 314 item.deleteLater() 315 316 def mousePressEvent(self, mouseEvent): 317 if (mouseEvent.button() != Qt.LeftButton): 318 return 319 320 if self.myMode == self.InsertItem: 321 item = DiagramItem(self.myItemType, self.myItemMenu) 322 item.setBrush(self.myItemColor) 323 self.addItem(item) 324 item.setPos(mouseEvent.scenePos()) 325 self.itemInserted.emit(item) 326 elif self.myMode == self.InsertLine: 327 self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), 328 mouseEvent.scenePos())) 329 self.line.setPen(QPen(self.myLineColor, 2)) 330 self.addItem(self.line) 331 elif self.myMode == self.InsertText: 332 textItem = DiagramTextItem() 333 textItem.setFont(self.myFont) 334 textItem.setTextInteractionFlags(Qt.TextEditorInteraction) 335 textItem.setZValue(1000.0) 336 textItem.lostFocus.connect(self.editorLostFocus) 337 textItem.selectedChange.connect(self.itemSelected) 338 self.addItem(textItem) 339 textItem.setDefaultTextColor(self.myTextColor) 340 textItem.setPos(mouseEvent.scenePos()) 341 self.textInserted.emit(textItem) 342 343 super(DiagramScene, self).mousePressEvent(mouseEvent) 344 345 def mouseMoveEvent(self, mouseEvent): 346 if self.myMode == self.InsertLine and self.line: 347 newLine = QLineF(self.line.line().p1(), mouseEvent.scenePos()) 348 self.line.setLine(newLine) 349 elif self.myMode == self.MoveItem: 350 super(DiagramScene, self).mouseMoveEvent(mouseEvent) 351 352 def mouseReleaseEvent(self, mouseEvent): 353 if self.line and self.myMode == self.InsertLine: 354 startItems = self.items(self.line.line().p1()) 355 if len(startItems) and startItems[0] == self.line: 356 startItems.pop(0) 357 endItems = self.items(self.line.line().p2()) 358 if len(endItems) and endItems[0] == self.line: 359 endItems.pop(0) 360 361 self.removeItem(self.line) 362 self.line = None 363 364 if len(startItems) and len(endItems) and \ 365 isinstance(startItems[0], DiagramItem) and \ 366 isinstance(endItems[0], DiagramItem) and \ 367 startItems[0] != endItems[0]: 368 startItem = startItems[0] 369 endItem = endItems[0] 370 arrow = Arrow(startItem, endItem) 371 arrow.setColor(self.myLineColor) 372 startItem.addArrow(arrow) 373 endItem.addArrow(arrow) 374 arrow.setZValue(-1000.0) 375 self.addItem(arrow) 376 arrow.updatePosition() 377 378 self.line = None 379 super(DiagramScene, self).mouseReleaseEvent(mouseEvent) 380 381 def isItemChange(self, type): 382 for item in self.selectedItems(): 383 if isinstance(item, type): 384 return True 385 return False 386 387 388class MainWindow(QMainWindow): 389 InsertTextButton = 10 390 391 def __init__(self): 392 super(MainWindow, self).__init__() 393 394 self.createActions() 395 self.createMenus() 396 self.createToolBox() 397 398 self.scene = DiagramScene(self.itemMenu) 399 self.scene.setSceneRect(QRectF(0, 0, 5000, 5000)) 400 self.scene.itemInserted.connect(self.itemInserted) 401 self.scene.textInserted.connect(self.textInserted) 402 self.scene.itemSelected.connect(self.itemSelected) 403 404 self.createToolbars() 405 406 layout = QHBoxLayout() 407 layout.addWidget(self.toolBox) 408 self.view = QGraphicsView(self.scene) 409 layout.addWidget(self.view) 410 411 self.widget = QWidget() 412 self.widget.setLayout(layout) 413 414 self.setCentralWidget(self.widget) 415 self.setWindowTitle("Diagramscene") 416 417 def backgroundButtonGroupClicked(self, button): 418 buttons = self.backgroundButtonGroup.buttons() 419 for myButton in buttons: 420 if myButton != button: 421 button.setChecked(False) 422 423 text = button.text() 424 if text == "Blue Grid": 425 self.scene.setBackgroundBrush(QBrush(QPixmap(':/images/background1.png'))) 426 elif text == "White Grid": 427 self.scene.setBackgroundBrush(QBrush(QPixmap(':/images/background2.png'))) 428 elif text == "Gray Grid": 429 self.scene.setBackgroundBrush(QBrush(QPixmap(':/images/background3.png'))) 430 else: 431 self.scene.setBackgroundBrush(QBrush(QPixmap(':/images/background4.png'))) 432 433 self.scene.update() 434 self.view.update() 435 436 def buttonGroupClicked(self, id): 437 buttons = self.buttonGroup.buttons() 438 for button in buttons: 439 if self.buttonGroup.button(id) != button: 440 button.setChecked(False) 441 442 if id == self.InsertTextButton: 443 self.scene.setMode(DiagramScene.InsertText) 444 else: 445 self.scene.setItemType(id) 446 self.scene.setMode(DiagramScene.InsertItem) 447 448 def deleteItem(self): 449 for item in self.scene.selectedItems(): 450 if isinstance(item, DiagramItem): 451 item.removeArrows() 452 self.scene.removeItem(item) 453 454 def pointerGroupClicked(self, i): 455 self.scene.setMode(self.pointerTypeGroup.checkedId()) 456 457 def bringToFront(self): 458 if not self.scene.selectedItems(): 459 return 460 461 selectedItem = self.scene.selectedItems()[0] 462 overlapItems = selectedItem.collidingItems() 463 464 zValue = 0 465 for item in overlapItems: 466 if (item.zValue() >= zValue and isinstance(item, DiagramItem)): 467 zValue = item.zValue() + 0.1 468 selectedItem.setZValue(zValue) 469 470 def sendToBack(self): 471 if not self.scene.selectedItems(): 472 return 473 474 selectedItem = self.scene.selectedItems()[0] 475 overlapItems = selectedItem.collidingItems() 476 477 zValue = 0 478 for item in overlapItems: 479 if (item.zValue() <= zValue and isinstance(item, DiagramItem)): 480 zValue = item.zValue() - 0.1 481 selectedItem.setZValue(zValue) 482 483 def itemInserted(self, item): 484 self.pointerTypeGroup.button(DiagramScene.MoveItem).setChecked(True) 485 self.scene.setMode(self.pointerTypeGroup.checkedId()) 486 self.buttonGroup.button(item.diagramType).setChecked(False) 487 488 def textInserted(self, item): 489 self.buttonGroup.button(self.InsertTextButton).setChecked(False) 490 self.scene.setMode(self.pointerTypeGroup.checkedId()) 491 492 def currentFontChanged(self, font): 493 self.handleFontChange() 494 495 def fontSizeChanged(self, font): 496 self.handleFontChange() 497 498 def sceneScaleChanged(self, scale): 499 newScale = scale.left(scale.indexOf("%")).toDouble()[0] / 100.0 500 oldMatrix = self.view.matrix() 501 self.view.resetMatrix() 502 self.view.translate(oldMatrix.dx(), oldMatrix.dy()) 503 self.view.scale(newScale, newScale) 504 505 def textColorChanged(self): 506 self.textAction = self.sender() 507 self.fontColorToolButton.setIcon( 508 self.createColorToolButtonIcon(':/images/textpointer.png', 509 QColor(self.textAction.data()))) 510 self.textButtonTriggered() 511 512 def itemColorChanged(self): 513 self.fillAction = self.sender() 514 self.fillColorToolButton.setIcon( 515 self.createColorToolButtonIcon( ':/images/floodfill.png', 516 QColor(self.fillAction.data()))) 517 self.fillButtonTriggered() 518 519 def lineColorChanged(self): 520 self.lineAction = self.sender() 521 self.lineColorToolButton.setIcon( 522 self.createColorToolButtonIcon(':/images/linecolor.png', 523 QColor(self.lineAction.data()))) 524 self.lineButtonTriggered() 525 526 def textButtonTriggered(self): 527 self.scene.setTextColor(QColor(self.textAction.data())) 528 529 def fillButtonTriggered(self): 530 self.scene.setItemColor(QColor(self.fillAction.data())) 531 532 def lineButtonTriggered(self): 533 self.scene.setLineColor(QColor(self.lineAction.data())) 534 535 def handleFontChange(self): 536 font = self.fontCombo.currentFont() 537 font.setPointSize(self.fontSizeCombo.currentText().toInt()[0]) 538 if self.boldAction.isChecked(): 539 font.setWeight(QFont.Bold) 540 else: 541 font.setWeight(QFont.Normal) 542 font.setItalic(self.italicAction.isChecked()) 543 font.setUnderline(self.underlineAction.isChecked()) 544 545 self.scene.setFont(font) 546 547 def itemSelected(self, item): 548 font = item.font() 549 color = item.defaultTextColor() 550 self.fontCombo.setCurrentFont(font) 551 self.fontSizeCombo.setEditText(str(font.pointSize())) 552 self.boldAction.setChecked(font.weight() == QFont.Bold) 553 self.italicAction.setChecked(font.italic()) 554 self.underlineAction.setChecked(font.underline()) 555 556 def about(self): 557 QMessageBox.about(self, "About Diagram Scene", 558 "The <b>Diagram Scene</b> example shows use of the graphics framework.") 559 560 def createToolBox(self): 561 self.buttonGroup = QButtonGroup() 562 self.buttonGroup.setExclusive(False) 563 self.buttonGroup.buttonClicked[int].connect(self.buttonGroupClicked) 564 565 layout = QGridLayout() 566 layout.addWidget(self.createCellWidget("Conditional", DiagramItem.Conditional), 567 0, 0) 568 layout.addWidget(self.createCellWidget("Process", DiagramItem.Step), 0, 569 1) 570 layout.addWidget(self.createCellWidget("Input/Output", DiagramItem.Io), 571 1, 0) 572 573 textButton = QToolButton() 574 textButton.setCheckable(True) 575 self.buttonGroup.addButton(textButton, self.InsertTextButton) 576 textButton.setIcon(QIcon(QPixmap(':/images/textpointer.png').scaled(30, 30))) 577 textButton.setIconSize(QSize(50, 50)) 578 579 textLayout = QGridLayout() 580 textLayout.addWidget(textButton, 0, 0, Qt.AlignHCenter) 581 textLayout.addWidget(QLabel("Text"), 1, 0, Qt.AlignCenter) 582 textWidget = QWidget() 583 textWidget.setLayout(textLayout) 584 layout.addWidget(textWidget, 1, 1) 585 586 layout.setRowStretch(3, 10) 587 layout.setColumnStretch(2, 10) 588 589 itemWidget = QWidget() 590 itemWidget.setLayout(layout) 591 592 self.backgroundButtonGroup = QButtonGroup() 593 self.backgroundButtonGroup.buttonClicked.connect(self.backgroundButtonGroupClicked) 594 595 backgroundLayout = QGridLayout() 596 backgroundLayout.addWidget(self.createBackgroundCellWidget("Blue Grid", 597 ':/images/background1.png'), 0, 0) 598 backgroundLayout.addWidget(self.createBackgroundCellWidget("White Grid", 599 ':/images/background2.png'), 0, 1) 600 backgroundLayout.addWidget(self.createBackgroundCellWidget("Gray Grid", 601 ':/images/background3.png'), 1, 0) 602 backgroundLayout.addWidget(self.createBackgroundCellWidget("No Grid", 603 ':/images/background4.png'), 1, 1) 604 605 backgroundLayout.setRowStretch(2, 10) 606 backgroundLayout.setColumnStretch(2, 10) 607 608 backgroundWidget = QWidget() 609 backgroundWidget.setLayout(backgroundLayout) 610 611 self.toolBox = QToolBox() 612 self.toolBox.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)) 613 self.toolBox.setMinimumWidth(itemWidget.sizeHint().width()) 614 self.toolBox.addItem(itemWidget, "Basic Flowchart Shapes") 615 self.toolBox.addItem(backgroundWidget, "Backgrounds") 616 617 def createActions(self): 618 self.toFrontAction = QAction( 619 QIcon(':/images/bringtofront.png'), "Bring to &Front", 620 self, shortcut="Ctrl+F", statusTip="Bring item to front", 621 triggered=self.bringToFront) 622 623 self.sendBackAction = QAction( 624 QIcon(':/images/sendtoback.png'), "Send to &Back", self, 625 shortcut="Ctrl+B", statusTip="Send item to back", 626 triggered=self.sendToBack) 627 628 self.deleteAction = QAction(QIcon(':/images/delete.png'), 629 "&Delete", self, shortcut="Delete", 630 statusTip="Delete item from diagram", 631 triggered=self.deleteItem) 632 633 self.exitAction = QAction("E&xit", self, shortcut="Ctrl+X", 634 statusTip="Quit Scenediagram example", triggered=self.close) 635 636 self.boldAction = QAction(QIcon(':/images/bold.png'), 637 "Bold", self, checkable=True, shortcut="Ctrl+B", 638 triggered=self.handleFontChange) 639 640 self.italicAction = QAction(QIcon(':/images/italic.png'), 641 "Italic", self, checkable=True, shortcut="Ctrl+I", 642 triggered=self.handleFontChange) 643 644 self.underlineAction = QAction( 645 QIcon(':/images/underline.png'), "Underline", self, 646 checkable=True, shortcut="Ctrl+U", 647 triggered=self.handleFontChange) 648 649 self.aboutAction = QAction("A&bout", self, shortcut="Ctrl+B", 650 triggered=self.about) 651 652 def createMenus(self): 653 self.fileMenu = self.menuBar().addMenu("&File") 654 self.fileMenu.addAction(self.exitAction) 655 656 self.itemMenu = self.menuBar().addMenu("&Item") 657 self.itemMenu.addAction(self.deleteAction) 658 self.itemMenu.addSeparator() 659 self.itemMenu.addAction(self.toFrontAction) 660 self.itemMenu.addAction(self.sendBackAction) 661 662 self.aboutMenu = self.menuBar().addMenu("&Help") 663 self.aboutMenu.addAction(self.aboutAction) 664 665 def createToolbars(self): 666 self.editToolBar = self.addToolBar("Edit") 667 self.editToolBar.addAction(self.deleteAction) 668 self.editToolBar.addAction(self.toFrontAction) 669 self.editToolBar.addAction(self.sendBackAction) 670 671 self.fontCombo = QFontComboBox() 672 self.fontCombo.currentFontChanged.connect(self.currentFontChanged) 673 674 self.fontSizeCombo = QComboBox() 675 self.fontSizeCombo.setEditable(True) 676 for i in range(8, 30, 2): 677 self.fontSizeCombo.addItem(str(i)) 678 validator = QIntValidator(2, 64, self) 679 self.fontSizeCombo.setValidator(validator) 680 self.fontSizeCombo.currentIndexChanged.connect(self.fontSizeChanged) 681 682 self.fontColorToolButton = QToolButton() 683 self.fontColorToolButton.setPopupMode(QToolButton.MenuButtonPopup) 684 self.fontColorToolButton.setMenu( 685 self.createColorMenu(self.textColorChanged, Qt.black)) 686 self.textAction = self.fontColorToolButton.menu().defaultAction() 687 self.fontColorToolButton.setIcon( 688 self.createColorToolButtonIcon(':/images/textpointer.png', 689 Qt.black)) 690 self.fontColorToolButton.setAutoFillBackground(True) 691 self.fontColorToolButton.clicked.connect(self.textButtonTriggered) 692 693 self.fillColorToolButton = QToolButton() 694 self.fillColorToolButton.setPopupMode(QToolButton.MenuButtonPopup) 695 self.fillColorToolButton.setMenu( 696 self.createColorMenu(self.itemColorChanged, Qt.white)) 697 self.fillAction = self.fillColorToolButton.menu().defaultAction() 698 self.fillColorToolButton.setIcon( 699 self.createColorToolButtonIcon(':/images/floodfill.png', 700 Qt.white)) 701 self.fillColorToolButton.clicked.connect(self.fillButtonTriggered) 702 703 self.lineColorToolButton = QToolButton() 704 self.lineColorToolButton.setPopupMode(QToolButton.MenuButtonPopup) 705 self.lineColorToolButton.setMenu( 706 self.createColorMenu(self.lineColorChanged, Qt.black)) 707 self.lineAction = self.lineColorToolButton.menu().defaultAction() 708 self.lineColorToolButton.setIcon( 709 self.createColorToolButtonIcon(':/images/linecolor.png', 710 Qt.black)) 711 self.lineColorToolButton.clicked.connect(self.lineButtonTriggered) 712 713 self.textToolBar = self.addToolBar("Font") 714 self.textToolBar.addWidget(self.fontCombo) 715 self.textToolBar.addWidget(self.fontSizeCombo) 716 self.textToolBar.addAction(self.boldAction) 717 self.textToolBar.addAction(self.italicAction) 718 self.textToolBar.addAction(self.underlineAction) 719 720 self.colorToolBar = self.addToolBar("Color") 721 self.colorToolBar.addWidget(self.fontColorToolButton) 722 self.colorToolBar.addWidget(self.fillColorToolButton) 723 self.colorToolBar.addWidget(self.lineColorToolButton) 724 725 pointerButton = QToolButton() 726 pointerButton.setCheckable(True) 727 pointerButton.setChecked(True) 728 pointerButton.setIcon(QIcon(':/images/pointer.png')) 729 linePointerButton = QToolButton() 730 linePointerButton.setCheckable(True) 731 linePointerButton.setIcon(QIcon(':/images/linepointer.png')) 732 733 self.pointerTypeGroup = QButtonGroup() 734 self.pointerTypeGroup.addButton(pointerButton, DiagramScene.MoveItem) 735 self.pointerTypeGroup.addButton(linePointerButton, 736 DiagramScene.InsertLine) 737 self.pointerTypeGroup.buttonClicked[int].connect(self.pointerGroupClicked) 738 739 self.sceneScaleCombo = QComboBox() 740 self.sceneScaleCombo.addItems(["50%", "75%", "100%", "125%", "150%"]) 741 self.sceneScaleCombo.setCurrentIndex(2) 742 self.sceneScaleCombo.currentIndexChanged[str].connect(self.sceneScaleChanged) 743 744 self.pointerToolbar = self.addToolBar("Pointer type") 745 self.pointerToolbar.addWidget(pointerButton) 746 self.pointerToolbar.addWidget(linePointerButton) 747 self.pointerToolbar.addWidget(self.sceneScaleCombo) 748 749 def createBackgroundCellWidget(self, text, image): 750 button = QToolButton() 751 button.setText(text) 752 button.setIcon(QIcon(image)) 753 button.setIconSize(QSize(50, 50)) 754 button.setCheckable(True) 755 self.backgroundButtonGroup.addButton(button) 756 757 layout = QGridLayout() 758 layout.addWidget(button, 0, 0, Qt.AlignHCenter) 759 layout.addWidget(QLabel(text), 1, 0, Qt.AlignCenter) 760 761 widget = QWidget() 762 widget.setLayout(layout) 763 764 return widget 765 766 def createCellWidget(self, text, diagramType): 767 item = DiagramItem(diagramType, self.itemMenu) 768 icon = QIcon(item.image()) 769 770 button = QToolButton() 771 button.setIcon(icon) 772 button.setIconSize(QSize(50, 50)) 773 button.setCheckable(True) 774 self.buttonGroup.addButton(button, diagramType) 775 776 layout = QGridLayout() 777 layout.addWidget(button, 0, 0, Qt.AlignHCenter) 778 layout.addWidget(QLabel(text), 1, 0, Qt.AlignCenter) 779 780 widget = QWidget() 781 widget.setLayout(layout) 782 783 return widget 784 785 def createColorMenu(self, slot, defaultColor): 786 colors = [Qt.black, Qt.white, Qt.red, Qt.blue, Qt.yellow] 787 names = ["black", "white", "red", "blue", "yellow"] 788 789 colorMenu = QMenu(self) 790 for color, name in zip(colors, names): 791 action = QAction(self.createColorIcon(color), name, self, 792 triggered=slot) 793 action.setData(QColor(color)) 794 colorMenu.addAction(action) 795 if color == defaultColor: 796 colorMenu.setDefaultAction(action) 797 return colorMenu 798 799 def createColorToolButtonIcon(self, imageFile, color): 800 pixmap = QPixmap(50, 80) 801 pixmap.fill(Qt.transparent) 802 painter = QPainter(pixmap) 803 image = QPixmap(imageFile) 804 target = QRect(0, 0, 50, 60) 805 source = QRect(0, 0, 42, 42) 806 painter.fillRect(QRect(0, 60, 50, 80), color) 807 painter.drawPixmap(target, image, source) 808 painter.end() 809 810 return QIcon(pixmap) 811 812 def createColorIcon(self, color): 813 pixmap = QPixmap(20, 20) 814 painter = QPainter(pixmap) 815 painter.setPen(Qt.NoPen) 816 painter.fillRect(QRect(0, 0, 20, 20), color) 817 painter.end() 818 819 return QIcon(pixmap) 820 821 822if __name__ == '__main__': 823 824 import sys 825 826 app = QApplication(sys.argv) 827 828 mainWindow = MainWindow() 829 mainWindow.setGeometry(100, 100, 800, 500) 830 mainWindow.show() 831 832 sys.exit(app.exec_()) 833