1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2017, Data61 5# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 6# ABN 41 687 119 230. 7# 8# This software may be distributed and modified according to the terms of 9# the BSD 2-Clause license. Note that NO WARRANTY is provided. 10# See "LICENSE_BSD2.txt" for details. 11# 12# @TAG(DATA61_BSD) 13 14import six 15import math 16 17from PyQt5 import QtGui, QtWidgets, QtCore 18 19import Connection_Widget 20from Model import Common 21from Interface.Property import PropertyInterface 22from Instance_Property_Widget import InstancePropertyWidget 23 24# TODO: Delete itself from all connections when __del__ ed 25 26class InstanceWidget(QtWidgets.QGraphicsWidget, PropertyInterface): 27 """ 28 InstanceWidget - a View representation of camkes.ast.Instance. 29 If model changes, update the fields in InstanceWidget. 30 """ 31 32 # Constants and private class variables 33 _bounding_rect = None 34 _border_thickness = 7 35 36 @property 37 def velocity(self): 38 if self._velocity is None: 39 self._velocity = QtCore.QPointF(0, 0) 40 return self._velocity 41 42 @velocity.setter 43 def velocity(self, value): 44 assert isinstance(value, QtCore.QPointF) 45 self._velocity = value 46 47 # --- Information about Instance --- 48 49 @property 50 def name(self): 51 if self._name is None: 52 self._name = "Uninitialised widget" 53 return self._name 54 55 @name.setter 56 def name(self, value): 57 assert isinstance(value, six.string_types) 58 self._name = value 59 self.update_ui() 60 61 @property 62 def component_type(self): 63 if self._component_type is None: 64 self._component_type = "Uninitialised widget" 65 return self._component_type 66 67 @component_type.setter 68 def component_type(self, value): 69 assert isinstance(value, six.string_types) 70 self._component_type = value 71 self.update_ui() 72 73 @property 74 def control(self): 75 return self._control 76 77 @control.setter 78 def control(self, value): 79 assert isinstance(value, bool) 80 self._control = value 81 self.update_ui() 82 83 @property 84 def hardware(self): 85 return self._hardware 86 87 @hardware.setter 88 def hardware(self, value): 89 assert isinstance(value, bool) 90 self._hardware = value 91 self.update_ui() 92 93 @property 94 def hidden(self): 95 return self._hidden 96 97 @hidden.setter 98 def hidden(self, value): 99 assert isinstance(value, bool) 100 self._hidden = value 101 102 if value: 103 self.setZValue(3) 104 else: 105 self.setZValue(5) 106 107 for connection in self.connection_list: 108 # This will only set if both source and destination is not hidden 109 connection.hidden = value 110 connection.update() 111 112 self.update() 113 114 # Provides 115 @property 116 def provides(self): 117 if self._provides is None: 118 self._provides = [] 119 return self._provides 120 121 # TODO: Handle multiple connections 122 def add_provide(self, name, interface_type, connection=None): 123 assert isinstance(name, six.string_types) 124 assert isinstance(interface_type, six.string_types) 125 126 self.provides.append({'Name': name, 127 'Interface_type': interface_type, 128 'Connection_Widget': connection}) 129 130 self.update_ui() 131 132 def add_provide_connection(self, interface_name, connection): 133 assert self._provides is not None 134 for dictionary in self.provides: 135 if dictionary['Name'] == interface_name: 136 dictionary['Connection_Widget'] = connection 137 break 138 139 def remove_provide_connection(self, interface_name, connection): 140 assert self._provides is not None 141 for dictionary in self.provides: 142 if dictionary['Name'] == interface_name and dictionary['Connection_Widget'] is connection: 143 dictionary['Connection_Widget'] = None 144 break 145 146 def delete_provide(self, name): 147 raise NotImplementedError 148 149 # Uses 150 @property 151 def uses(self): 152 if self._uses is None: 153 self._uses = [] 154 return self._uses 155 156 def add_use(self, name, interface_type, connection=None): 157 assert isinstance(name, six.string_types) 158 assert isinstance(interface_type, six.string_types) 159 160 self.uses.append({'Name': name, 'Interface_type': interface_type, 'Connection_Widget': connection}) 161 162 self.update_ui() 163 # TODO NotImplementedError 164 165 def add_use_connection(self, interface_name, connection): 166 assert self._uses is not None 167 for dictionary in self.uses: 168 if dictionary['Name'] == interface_name: 169 dictionary['Connection_Widget'] = connection 170 break 171 172 def remove_use_connection(self, interface_name, connection): 173 assert self._uses is not None 174 for dictionary in self.uses: 175 if dictionary['Name'] == interface_name and dictionary['Connection_Widget'] is connection: 176 dictionary['Connection_Widget'] = None 177 break 178 179 def delete_use(self, name): 180 raise NotImplementedError 181 182 # Emits 183 @property 184 def emits(self): 185 if self._emits is None: 186 self._emits = [] 187 return self._emits 188 189 def add_emit(self, name, interface_type, connection=None): 190 assert isinstance(name, six.string_types) 191 assert isinstance(interface_type, six.string_types) 192 193 self.emits.append({'Name': name, 'Interface_type': interface_type, 'Connection_Widget': connection}) 194 195 self.update_ui() 196 # TODO NotImplementedError 197 198 def add_emit_connection(self, interface_name, connection): 199 assert self._emits is not None 200 for dictionary in self.emits: 201 if dictionary['Name'] == interface_name: 202 dictionary['Connection_Widget'] = connection 203 break 204 205 def remove_emit_connection(self, interface_name, connection): 206 assert self._emits is not None 207 for dictionary in self.emits: 208 if dictionary['Name'] == interface_name and dictionary['Connection_Widget'] is connection: 209 dictionary['Connection_Widget'] = None 210 break 211 212 def delete_emit(self, name): 213 raise NotImplementedError 214 215 # Consumes 216 @property 217 def consumes(self): 218 if self._consumes is None: 219 self._consumes = [] 220 return self._consumes 221 222 def add_consume(self, name, interface_type, optional, connection=None): 223 assert isinstance(name, six.string_types) 224 assert isinstance(interface_type, six.string_types) 225 assert isinstance(optional, bool) 226 227 self.consumes.append({'Name': name, 'Interface_type': interface_type, 'Optional': optional, 228 'Connection_Widget': connection}) 229 230 self.update_ui() 231 # TODO NotImplementedError 232 233 def add_consume_connection(self, interface_name, connection): 234 assert self._consumes is not None 235 for dictionary in self.consumes: 236 if dictionary['Name'] == interface_name: 237 dictionary['Connection_Widget'] = connection 238 break 239 240 def remove_consume_connection(self, interface_name, connection): 241 assert self._consumes is not None 242 for dictionary in self.consumes: 243 if dictionary['Name'] == interface_name and \ 244 dictionary['Connection_Widget'] is connection: 245 dictionary['Connection_Widget'] = None 246 break 247 248 def delete_consume(self, name): 249 raise NotImplementedError 250 251 # Dataport 252 @property 253 def dataport(self): 254 if self._dataport is None: 255 self._dataport = [] 256 return self._dataport 257 258 def add_dataport(self, name, interface_type, optional, connection=None): 259 assert isinstance(name, six.string_types) 260 assert isinstance(interface_type, six.string_types) 261 assert isinstance(optional, bool) 262 263 if self._dataport is None: 264 self._dataport = [] 265 266 self._dataport.append({'Name': name, 'Interface_type': interface_type, 'Optional': optional, 267 'Connection_Widget': connection}) 268 269 self.update_ui() 270 # TODO NotImplementedError 271 272 def add_dataport_connection(self, interface_name, connection): 273 assert self._dataport is not None 274 for dictionary in self.dataport: 275 if dictionary['Name'] == interface_name: 276 dictionary['Connection_Widget'] = connection 277 break 278 279 def remove_dataport_connection(self, interface_name, connection): 280 assert self._dataport is not None 281 for dictionary in self.dataport: 282 if dictionary['Name'] == interface_name and \ 283 dictionary['Connection_Widget'] is connection: 284 dictionary['Connection_Widget'] = None 285 break 286 287 def delete_dataport(self, name): 288 raise NotImplementedError 289 290 @property 291 def connection_list(self): 292 return self._connections_list 293 294 # TODO: connection overrides, for multiway connection. Eg. eigenConnection 295 def add_connection(self, connection): 296 297 assert isinstance(connection, Connection_Widget.ConnectionWidget) 298 299 if connection.source_instance_widget is self: 300 if connection.source_connection_type == Common.Event: 301 self.add_emit_connection(connection.source_interface_name, connection) 302 elif connection.source_connection_type == Common.Procedure: 303 self.add_use_connection(connection.source_interface_name, connection) 304 elif connection.source_connection_type == Common.Dataport: 305 self.add_dataport_connection(connection.source_interface_name, connection) 306 307 elif connection.dest_instance_widget is self: 308 if connection.dest_connection_type == Common.Event: 309 self.add_consume_connection(connection.dest_interface_name, connection) 310 elif connection.dest_connection_type == Common.Procedure: 311 self.add_provide_connection(connection.dest_interface_name, connection) 312 elif connection.dest_connection_type == Common.Dataport: 313 self.add_dataport_connection(connection.dest_interface_name, connection) 314 315 else: 316 raise NotImplementedError # Something is wrong 317 318 self._connections_list.append(connection) 319 self.update_connection_position(connection) 320 321 def remove_connection(self, connection): 322 assert isinstance(connection, Connection_Widget.ConnectionWidget) 323 324 if connection.source_instance_widget is self: 325 if connection.source_connection_type == Common.Event: 326 self.remove_emit_connection(connection.source_interface_name, connection) 327 elif connection.source_connection_type == Common.Procedure: 328 self.remove_use_connection(connection.source_interface_name, connection) 329 elif connection.source_connection_type == Common.Dataport: 330 self.remove_dataport_connection(connection.source_interface_name, connection) 331 elif connection.dest_instance_widget is self: 332 if connection.dest_connection_type == Common.Event: 333 self.remove_consume_connection(connection.dest_interface_name, connection) 334 elif connection.dest_connection_type == Common.Procedure: 335 self.remove_provide_connection(connection.dest_interface_name, connection) 336 elif connection.dest_connection_type == Common.Dataport: 337 self.remove_dataport_connection(connection.dest_interface_name, connection) 338 else: 339 raise NotImplementedError # Something is wrong 340 341 self._connections_list.remove(connection) 342 343 @property 344 def context_menu(self): 345 return self._context_menu 346 347 @context_menu.setter 348 def context_menu(self, value): 349 assert isinstance(value, QtWidgets.QGraphicsProxyWidget) 350 assert isinstance(value.widget(), QtWidgets.QMenu) 351 self._context_menu = value 352 353 @property 354 def property_widget(self): 355 self._property_widget = InstancePropertyWidget(self) 356 return self._property_widget 357 # ------- 358 359 # Signals & Slots 360 widget_moved = QtCore.pyqtSignal() 361 362 # --- INITIALISATION 363 def __init__(self, context_menu, preferred_point=None): 364 super(InstanceWidget, self).__init__() 365 # Model 366 367 self._preferred_point = preferred_point 368 self._pinned = False 369 self._velocity = None 370 371 self._name = None 372 self._component_type = None 373 self._control = False 374 self._hardware = False 375 self._provides = None 376 self._uses = None 377 self._emits = None 378 self._consumes = None 379 self._dataport = None 380 381 self._context_menu = None 382 self.context_menu = context_menu 383 self._hidden = False 384 self._property_widget = None 385 386 self._connections_list = [] 387 388 # GUI 389 self.color = QtGui.QColor(245,245,245) 390 391 self.setFlag(QtWidgets.QGraphicsWidget.ItemIsMovable) 392 393 self.update_ui() 394 395 # --- UI FUNCTIONS --- 396 def paint(self, painter, style_options, widget=None): 397 """ 398 Overridden function, paints the box with name, type and the C H symbols. 399 :param painter: 400 :param style_options: 401 :param widget: 402 :return: 403 """ 404 405 assert isinstance(painter, QtGui.QPainter) 406 assert isinstance(style_options, QtWidgets.QStyleOptionGraphicsItem) 407 # assert isinstance(widget, QtWidgets.QWidget) 408 409 super(InstanceWidget, self).paint(painter, style_options, widget) 410 411 painter.setRenderHint(QtGui.QPainter.Antialiasing) 412 413 # -- If hidden, changing alpha values to transparent -- 414 color = self.color 415 416 if self.hidden: 417 color.setAlphaF(0.2) 418 else: 419 color.setAlphaF(1) 420 421 # Setting brush color 422 brush = painter.brush() 423 brush.setColor(color) 424 painter.setBrush(brush) 425 426 pen = painter.pen() 427 pen_color = pen.color() 428 if self.hidden: 429 pen_color.setAlphaF(0.2) 430 else: 431 pen_color.setAlphaF(1) 432 pen.setColor(pen_color) 433 painter.setPen(pen) 434 435 rounded_rect = QtGui.QPainterPath() 436 assert isinstance(rounded_rect, QtGui.QPainterPath) 437 # If instance is control or hardware, boundedRect will compensate for that. 438 inner_rect = self.boundingRect().adjusted(0,0,0,0) # Hacking way to get a copy of rect 439 if self.hardware or self.control: 440 inner_rect.adjust(2,2,-2,-2) 441 rounded_rect.addRoundedRect(inner_rect,5,5) 442 443 painter.fillPath(rounded_rect, color) 444 painter.drawPath(rounded_rect) 445 446 # Draw an outline if the instance is control or hardware 447 # Assumption is, an instance cannot be both control and hardware 448 outline_rect = inner_rect.adjusted(-1,-1,1,1) 449 outline_rect_path = QtGui.QPainterPath() 450 outline_rect_path.addRoundedRect(outline_rect, 5, 5) 451 stroker = QtGui.QPainterPathStroker() 452 stroker.setWidth(5) 453 outline_rounded_rect = stroker.createStroke(outline_rect_path) 454 455 # Draw outline to highlight control components 456 if self.control: 457 # Make a BLUE color pen 458 pen_color.setRed(30) 459 pen_color.setGreen(136) 460 pen_color.setBlue(229) 461 painter.fillPath(outline_rounded_rect, pen_color) 462 463 # Draw outline to highlight hardware components 464 if self.hardware: 465 pen_color.setRed(67) 466 pen_color.setGreen(160) 467 pen_color.setBlue(71) 468 painter.fillPath(outline_rounded_rect, pen_color) 469 470 # TODO IDEA: Update rect with new size 471 472 # Printing instance name 473 font = QtGui.QFont("Helvetica", 15, QtGui.QFont.Normal) 474 painter.setFont(font) 475 font_metrics = painter.fontMetrics() 476 assert isinstance(font_metrics, QtGui.QFontMetrics) 477 bounding_rect_font = painter.boundingRect(QtCore.QRectF(1, 1, 1, 1), QtCore.Qt.AlignCenter, self.name) 478 479 bounding_rect_font.moveTo(inner_rect.center().x() - bounding_rect_font.width() / 2, 480 inner_rect.center().y() - font_metrics.ascent()) 481 482 painter.drawText(bounding_rect_font, QtCore.Qt.AlignCenter, self.name) 483 484 control_hardware_x_pos = bounding_rect_font.x() 485 486 # Printing component name 487 font.setPointSize(11) 488 painter.setFont(font) 489 bounding_rect_font = painter.boundingRect(QtCore.QRectF(1, 1, 1, 1), QtCore.Qt.AlignCenter, self.component_type) 490 491 bounding_rect_font.moveTo(inner_rect.center().x() - bounding_rect_font.width() / 2, 492 inner_rect.center().y() + font_metrics.descent()) 493 494 painter.drawText(bounding_rect_font, QtCore.Qt.AlignCenter, self.component_type) 495 496 if bounding_rect_font.x() < control_hardware_x_pos: 497 control_hardware_x_pos = bounding_rect_font.x() 498 499 control_hardware_x_pos -= 5 500 501 # The C 502 503 font.setPointSize(12) 504 painter.setFont(font) 505 font_metrics = painter.fontMetrics() 506 bounding_rect_font = painter.boundingRect(QtCore.QRectF(1, 1, 1, 1), QtCore.Qt.AlignCenter, "C") 507 508 bounding_rect_font.moveTo(control_hardware_x_pos - bounding_rect_font.width(), 509 inner_rect.center().y() - font_metrics.ascent()) 510 if self.control: 511 painter.drawText(bounding_rect_font, QtCore.Qt.AlignCenter, "C") 512 513 # The H 514 bounding_rect_font = painter.boundingRect(QtCore.QRectF(1, 1, 1, 1), QtCore.Qt.AlignCenter, "H") 515 bounding_rect_font.moveTo(control_hardware_x_pos - bounding_rect_font.width(), 516 inner_rect.center().y() + font_metrics.descent()) 517 if self.hardware: 518 painter.drawText(bounding_rect_font, QtCore.Qt.AlignCenter, "H") 519 520 def update_ui(self): 521 """ 522 Recalculates the expected size of the view, and calls a repaint. 523 :return: 524 """ 525 526 # Calculate rect for instance name 527 practise_font = QtGui.QFont("Helvetica", 15, QtGui.QFont.Normal) 528 practise_font_metrics = QtGui.QFontMetrics(practise_font) 529 instance_name_rect = practise_font_metrics.boundingRect(self.name) 530 531 # Calculate rect for component type 532 practise_font.setPointSize(11) 533 practise_font_metrics = QtGui.QFontMetrics(practise_font) 534 component_name_rect = practise_font_metrics.boundingRect(self.component_type) 535 536 # Calculate rects for control and hardware symbols 537 practise_font.setPointSize(12) 538 practise_font_metrics = QtGui.QFontMetrics(practise_font) 539 control_rect = practise_font_metrics.boundingRect("C") 540 hardware_rect = practise_font_metrics.boundingRect("H") 541 542 # Find the max height 543 max_height = 2 * self._border_thickness + instance_name_rect.height() + hardware_rect.height() + 7 544 545 # Find the max width 546 max_width = 2 * self._border_thickness + 2 * control_rect.width() + 10 547 if instance_name_rect.width() > component_name_rect.width(): 548 max_width = max_width + instance_name_rect.width() 549 else: 550 max_width = max_width + component_name_rect.width() 551 552 # Set bounding rect to new max width and height 553 self._bounding_rect = QtCore.QRectF(self.scenePos().x(), self.scenePos().y(), max_width, max_height) 554 555 # Adjust for new hardware or control border 556 if self.hardware or self.control: 557 self._bounding_rect.adjust(-2,-2,2,2) 558 559 self.setPreferredSize(self._bounding_rect.width(), self._bounding_rect.height()) 560 561 self.update() # Call a repaint 562 563 def boundingRect(self): 564 """ 565 :return: QRect - bounding rectangle of this widget 566 """ 567 568 return self._bounding_rect 569 570 def update_connections(self): 571 """ 572 Forces all connections and connecting instances to update. 573 :return: 574 """ 575 576 for connection in self.connection_list: 577 self.update_connection_position(connection) 578 if connection.source_instance_widget is self: 579 connection.dest_instance_widget.update_connection_position(connection) 580 else: 581 connection.source_instance_widget.update_connection_position(connection) 582 583 def update_connection_position(self, connection): 584 """ 585 Updates the touching point between the connection and this widget. 586 :param connection: The connection to be updated 587 :return: 588 """ 589 590 assert isinstance(connection, Connection_Widget.ConnectionWidget) 591 592 decrease_angle = None 593 594 # Find the direction of the angle on the other end - if it is set. 595 if connection.source_instance_widget is self: 596 other_widget = connection.dest_instance_widget 597 if connection.dest_angle: 598 decrease_angle = connection.dest_angle >= 0 599 else: 600 other_widget = connection.source_instance_widget 601 if connection.source_angle: 602 decrease_angle = connection.source_angle >= 0 603 604 # --- Find position based on straight line distance between this and other widget --- 605 606 # -- Vector between other and this -- 607 assert isinstance(other_widget, InstanceWidget) 608 609 our_pos = self.scenePos() 610 # Get middle of widget 611 our_pos.setX(our_pos.x() + self.boundingRect().width() / 2) 612 our_pos.setY(our_pos.y() + self.boundingRect().height() / 2) 613 614 other_widget_pos = other_widget.scenePos() 615 # Get middle of widget 616 other_widget_pos.setX(other_widget_pos.x() + other_widget.boundingRect().width() / 2) 617 other_widget_pos.setY(other_widget_pos.y() + other_widget.boundingRect().height() / 2) 618 619 vector = other_widget_pos - our_pos 620 621 # -- Finding intersection between vector and edge of this widget -- 622 final_pos = self.edge_intersection(our_pos, vector) 623 624 # Check if final_pos is inside other_widget 625 # If inside, use the centre of this widget instead. 626 other_widget_top_left = other_widget.scenePos() 627 other_widget_bottom_right = QtCore.QPointF(other_widget.scenePos().x() + \ 628 other_widget.boundingRect().width(), 629 other_widget.scenePos().y() + \ 630 other_widget.boundingRect().height()) 631 if final_pos.x() >= other_widget_top_left.x() and \ 632 final_pos.x() <= other_widget_bottom_right.x() and \ 633 final_pos.y() >= other_widget_top_left.y() and \ 634 final_pos.y() <= other_widget_bottom_right.y(): 635 final_pos = our_pos 636 637 # Find unclashing angle 638 angle = self.find_free_angle(final_pos, connection, decrease_angle) 639 640 # Set our newly found position and angle (at the appropriate side of the connection) 641 if connection.source_instance_widget is self: 642 connection.set_source_pos_angle(final_pos, angle) 643 else: 644 connection.set_dest_pos_angle(final_pos, angle) 645 646 # TODO: Potentially inefficient algorithm 647 def edge_intersection(self, our_pos, vector): 648 """ 649 Finding the intersection between the vector + pos , to the edge of the widget 650 :param our_pos: The starting position of the vector (usually centre of widget) 651 :param vector: The vector from the starting position 652 :return: PyQt5.QPointF - The position of the intersection with the edge. 653 """ 654 655 # Consider the case where x is bigger than y 656 # . 657 # . . 658 # . . 659 # ............ 660 # We reduce y, proportional to x, such that x is equal to width of widget. 661 662 # If the x is 0, then it is a horizontal 663 if vector.x() == 0: 664 y_pos = self.boundingRect().height() 665 # If original y is negative, new y must also be negative 666 if vector.y() < 0: 667 y_pos = -y_pos 668 else: 669 # Using ratios to get y value 670 y_pos = vector.y() * math.fabs((self.boundingRect().width() / 2) / vector.x()) 671 672 half_height = self.boundingRect().height() / 2 + 1 # Bit of room for rounding 673 674 # If y is within the box then above assumption is correct 675 if -half_height <= y_pos <= half_height: 676 vector.setY(y_pos) 677 if vector.x() < 0: 678 vector.setX(-self.boundingRect().width() / 2) 679 else: 680 vector.setX(self.boundingRect().width() / 2) 681 else: 682 # If y wasn't within the box, then we assumption is wrong, y is bigger than x 683 # . 684 # . 685 # . . 686 # . 687 # . . 688 # . 689 # ...... 690 # We reduce x, proportional to y, such that y is equal to height. 691 692 # If y is 0, then it is vertical 693 if vector.y() == 0: 694 x_pos = self.boundingRect().width() 695 if vector.x() < 0: 696 x_pos = -x_pos 697 else: 698 # Using ratios to get x value 699 x_pos = vector.x() * math.fabs((self.boundingRect().height() / 2) / vector.y()) 700 701 vector.setX(x_pos) 702 if vector.y() < 0: 703 vector.setY(-self.boundingRect().height() / 2) 704 else: 705 vector.setY(self.boundingRect().height() / 2) 706 707 # We got a vector from the center, now we get the final position 708 final_pos = our_pos + vector 709 710 return final_pos 711 712 # TODO: Potentially inefficient algorithm 713 def find_free_angle(self, pos, connection, decrease_angle=None): 714 """ 715 Find a angle which doesn't collide with any other connection 716 at the same position. 717 :param pos: Position to find angle 718 :param connection: The current connection we are checking for 719 :param decrease_angle: If a specific direction is required, then use this variable 720 to specific whether the final angle is positive or negative. 721 Default is None. 722 """ 723 angle = 0 724 725 if decrease_angle is None: 726 decrease_angle = False 727 angle_set = False 728 else: 729 angle_set = True 730 731 # Choose an angle, start with 0 degrees, and search through all connection points, 732 # looking for clashes 733 for compare in self.connection_list: 734 assert isinstance(compare, Connection_Widget.ConnectionWidget) 735 736 if compare is connection: 737 continue # Not interested in the same connection, find others 738 739 # Get the current position and angle of the potential clashing connection 740 if compare.source_instance_widget is self: 741 compare_pos = compare.source_pos 742 compare_angle = compare.source_angle 743 elif compare.dest_instance_widget is self: 744 compare_pos = compare.dest_pos 745 compare_angle = compare.dest_angle 746 else: 747 raise NotImplementedError # Something went wrong 748 749 if compare_pos != pos: 750 continue # Does not clash, continue searching 751 752 # If clashing, find a angle which doesn't clash 753 while compare_angle == angle: 754 if angle_set: 755 if decrease_angle: 756 angle -= 35 757 else: 758 angle += 35 759 else: 760 # If angle is not set, try 0, -35, 35, -70, 70 etc 761 # In order to alternate between positive and negative, 762 # use decrease_angle as a toggle 763 angle = -angle 764 if decrease_angle: 765 angle -= 35 766 767 decrease_angle = not decrease_angle 768 769 return angle 770 771 # --- EVENTS --- 772 def itemChange(self, change, value): 773 """ 774 Deals with position changes. Updates connections when ever position changes 775 :param change: 776 :param value: 777 :return: 778 """ 779 780 if change == QtWidgets.QGraphicsWidget.ItemPositionHasChanged: 781 self.update_connections() 782 783 return super(InstanceWidget, self).itemChange(change, value) 784 785 def mousePressEvent(self, mouse_event): 786 """ 787 Deals with instances being pressed. Right now doesn't do anything special other than 788 printing the name, type and number of connections 789 :param mouse_event: 790 :return: 791 """ 792 793 string = " " 794 795 for connection in self.connection_list: 796 string += "%s " % connection.name 797 798 print "%s contains: %s" % (self.name, string) 799 800 no_of_connections = len(self.dataport) + len(self.provides) + len(self.consumes) + len(self.uses) + \ 801 len(self.emits) 802 print "\tNumber of connections is: %s" % str(no_of_connections) 803 print "\tdataport: %s" % str(len(self.dataport)) 804 print "\tprovides: %s" % str(len(self.provides)) 805 print "\tconsumes: %s" % str(len(self.consumes)) 806 print "\tuses: %s" % str(len(self.uses)) 807 print "\temits: %s" % str(len(self.emits)) 808 809 def mouseMoveEvent(self, mouse_event): 810 """ 811 Deals with this instance being clicked and dragged. Emits a signal that component was moved. 812 :param mouse_event: 813 :return: 814 """ 815 816 self.widget_moved.emit() 817 super(InstanceWidget, self).mouseMoveEvent(mouse_event) 818 819 def contextMenuEvent(self, event): 820 """ 821 Shows a context menu for this instance, asking to either show or hide the component. 822 Uses context menu given by graph widget. 823 :param event: 824 :return: 825 """ 826 827 assert isinstance(event, QtWidgets.QGraphicsSceneContextMenuEvent) 828 829 # Get menu widget from proxy widget 830 menu = self.context_menu.widget() 831 assert isinstance(menu, QtWidgets.QMenu) 832 833 # If current hidden, action is "Show" otherwise "Hide" 834 menu.clear() 835 if self.hidden: 836 showComponentAction = menu.addAction("Show component") 837 showComponentAction.triggered.connect(self.show_component) 838 else: 839 hideComponentAction = menu.addAction("Hide component") 840 hideComponentAction.triggered.connect(self.hide_component) 841 842 # Set the current position [of proxy widget] to mouse click position 843 self.context_menu.setPos(event.scenePos()) 844 menu.exec_() 845 846 def show_component(self): 847 self.hidden = False 848 849 def hide_component(self): 850 self.hidden = True 851