from PySide2.QtCore import Qt, Signal, Slot, QObject, QTimer, QAbstractListModel, QIdentityProxyModel, QSortFilterProxyModel, QModelIndex, QByteArray, QTextCodec, QSize, QRect from PySide2.QtNetwork import QTcpSocket, QAbstractSocket from PySide2.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QListView, QCheckBox, QAbstractItemView, QSizePolicy, QAbstractScrollArea from PySide2.QtGui import QIcon, QPainter, QPen, QColor, QBrush SocketState = QAbstractSocket.SocketState import sys app = QApplication(sys.argv) # Obecný cestovatel class Traveller: def __init__(self, id, speed, input=0): self.speed = float(speed) self.id = int(id) self.input = int(input) # Časovač opuštění oblasti self.timer = QTimer() self.timer.timeout.connect(self.done) # Cestovatel je aktivní self.active = True # Cestovatel vstupuje do sledovaného úseku def start(self, crossing): self.crossing = crossing # Časovač je v milisekundách self.timer.start(1000 * self.roadLength / self.speed) # Obsluha časovače: cestovatel opouští úsek def done(self): # Pokud jsme se mezitím stihli odpojit a připojit, neposílej nic if self.active: self.crossing.sendBack(self) # Převod argumentů zpět na řetězec def strArgs(self): return ("id=" + str(self.id) + " speed=" + str(self.speed) + " input=" + str(self.input)) def position(self): return self.roadLength - self.timer.remainingTime() * self.speed / 1000 class Car(Traveller): icon = QIcon("car.ico") # Sledujeme 500 metrů silnice roadLength = 500 def __str__(self): return "CAR " + super().strArgs() class Pedestrian(Traveller): icon = QIcon("pedestrian.ico") # Sledujeme 100 metrů chodníku roadLength = 100 def __str__(self): return "PEDESTRIAN " + super().strArgs() # Rozhraní pro server class CrossingNetwork(QObject): # Změna stavu; signál pro stavový řádek stateChanged = Signal(str) def __init__(self, model, *args, **kwargs): super().__init__(*args, **kwargs) self.model = model # Inicializace socketu self.socket = QTcpSocket(self) self.socket.readyRead.connect(self.read) self.socket.stateChanged.connect(self.socketStateChanged) # Příprava pro čtení dat self.readBuffer = QByteArray() self.textCodec = QTextCodec.codecForName("UTF-8") @Slot() def socketConnect(self): # Nejdřív se odpoj, pokud už spojení běží self.socket.abort() # A znovu se připoj self.socket.connectToHost( "ksp.mff.cuni.cz", 48888) # self.socket.connectToHost("localhost", 48888) @Slot() def socketDisconnect(self): # Odpoj se self.socket.disconnectFromHost() # Vyprázdni model self.model.flush() @Slot(SocketState) def socketStateChanged(self, state): if state == SocketState.ConnectedState: # Připojeni? Pozdravíme server. self.socket.write(self.textCodec.fromUnicode("HELLO\n")) # A informujeme připojené posluchače o změně stavu self.stateChanged.emit(str(state).split(".")[-1].split("State")[0]) def read(self): # Přečteme všechno, co jsme dostali while self.socket.bytesAvailable() > 0: self.readBuffer += \ self.socket.read(128) # Rozdělíme na řádky lines = self.readBuffer.split("\n") # Zbytek uložíme na příště self.readBuffer = lines.pop() # Zpracujeme řádky, které dorazily for l in lines: stripped = self.textCodec.toUnicode(l.data()).rstrip() args = stripped.split(" ") travellerType = args.pop(0) argmap = dict(map( lambda x: x.split("="), args)) if travellerType == "CAR": self.addTraveller(Car(**argmap)) elif travellerType == "PEDESTRIAN": self.addTraveller( Pedestrian(**argmap)) def addTraveller(self, traveller): # Uložíme si cestovatele self.model.add(traveller) # Nechť cestovatel vstoupí do oblasti traveller.start(self) def sendBack(self, traveller): # Vrátíme cestovatele serveru text = str(traveller) + "\n" self.socket.write(self.textCodec.fromUnicode(text)) self.model.remove(traveller) # Model pro Qt class CrossingModel(QAbstractListModel): # Nějaké vlastní role navíc, co se hodí # Dalo by se to také udělat tak, že bychom # tato data považovali za více sloupců sortRole = Qt.UserRole typeRole = Qt.UserRole + 1 directionRole = Qt.UserRole + 2 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.list = [] def rowCount(self, parent=None): return len(self.list) def data(self, index, role): row = index.row() if row < 0 or row >= len(self.list): return None traveller = self.list[row] if role == Qt.DisplayRole: pos = traveller.position() return "%(pos).0fm (%(perc).1f%%)" % { "pos": pos, "perc": 100 * pos / traveller.roadLength } elif role == Qt.DecorationRole: return traveller.icon elif role == Qt.ToolTipRole: return "%(id)d: %(speed)d m/s" % { "id": traveller.id, "speed": traveller.speed } elif role == self.sortRole: return traveller.position() / traveller.roadLength elif role == self.typeRole: return type(traveller) elif role == self.directionRole: return traveller.input else: return None def flush(self): self.beginRemoveRows(QModelIndex(), 0, self.rowCount()) self.list = [] self.endRemoveRows() def add(self, traveller): rc = self.rowCount() self.beginInsertRows(QModelIndex(), rc, rc+1) self.list.append(traveller) self.endInsertRows() def remove(self, traveller): for i in range(len(self.list)): # Najdeme cestovatele v seznamu if self.list[i] == traveller: # Vyhodíme jej ze seznamu self.beginRemoveRows(QModelIndex(), i, i+1) del self.list[i] self.endRemoveRows() return class UpdateProxy(QIdentityProxyModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Aktualizátor self.updater = QTimer() self.updater.timeout.connect(self.recalculate) self.updater.start(500) def recalculate(self): idf = self.createIndex(0, 0) idt = self.createIndex(self.rowCount()-1, 0) self.dataChanged.emit(idf, idt) class CrossingView(QAbstractItemView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.horizontalScrollBar().setRange(0, 0) self.verticalScrollBar().setRange(0, 0) # Požadavky na velikost widgetu def sizeHint(self): return QSize(1000, 200) def sizePolicy(self): return QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # View potřebuje vědět, která jeho část je zrovna vidět. def horizontalOffset(self): return self.horizontalScrollBar().value() def verticalOffset(self): return self.verticalScrollBar().value() # Občas View nějaký item hledá. # U nás jsou všechny vidět, žádný se neschoval. def isItemHidden(self, index): return False # Kde se má item vykreslit? def itemPosition(self, index): vp = self.viewport() # Rozměry w = vp.width() h = vp.height() p = self.model().data(index, CrossingModel.sortRole) t = self.model().data(index, CrossingModel.typeRole) d = self.model().data(index, CrossingModel.directionRole) center = None size = None if d == 1: p = 1 - p if t == Pedestrian: center = (w/2, h * p) size = (8, 8) elif t == Car: center = (w * p, h/2 + 13 - 26 * d) size = (30, 10) else: raise Exception("Strange type of Traveller") return (QRect(center[0] - size[0]/2, center[1] - size[1]/2, size[0], size[1]), t) # View to potřebuje vědět přímo def visualRect(self, index): return self.itemPosition(index)[0] # Samotné překreslování def paintEvent(self, event): vp = self.viewport() # Rozměry w = vp.width() h = vp.height() painter = QPainter(vp) painter.setRenderHint(QPainter.Antialiasing) # Silnice painter.setBrush(Qt.black) painter.setPen(QPen()) painter.drawRect(0, h/2 - 30, w, 60) # Čáry na silnici painter.setPen(QPen(Qt.white, 3)) painter.drawLine(0, h/2 - 25, w, h/2 - 25) painter.drawLine(0, h/2, w, h/2) painter.drawLine(0, h/2 + 25, w, h/2 + 25) # Chodník painter.setPen(Qt.white) painter.setBrush(Qt.white) painter.drawRect(w/2 - 20, 0, 42, h-1) # Dlaždičky painter.setPen(Qt.black) for i in range(0, h, 10): painter.drawLine(w/2 - 19, i, w/2 + 20, i) for j in range(w//2-20, w//2+21, 10): painter.drawLine(j, 0, j, h) # Cestovatelé for row in range(self.model().rowCount(self.rootIndex())): (rect, type_) = self.itemPosition(self.model().index(row, 0)) if type_ == Pedestrian: painter.drawEllipse(rect) continue if type_ == Car: painter.drawRect(rect) continue print("bad", row, position, type_) # Nová data? Překreslit! def dataChanged(self, topleft, bottomright, roles): self.viewport().update() # Tato funkce by měla vracet index prvku, # který je vykreslen na zadaném místě. # Jsme líní a vracíme prázdný index. # Pro editaci a delegáty to ovšem bude potřeba. def indexAt(self, point): return QModelIndex() # Tato funkce by měla scrollovat na místo, # kde se prvek vykresluje, jenomže náš View # scrollbar nepoužije def scrollTo(self, index, hint): pass # View a Controller v jednom objektu class Crossing(QWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Instance modelu self.model = CrossingModel() # Updater self.updateProxy = UpdateProxy() self.updateProxy.setSourceModel(self.model) # Sorter self.sortProxy = QSortFilterProxyModel() self.sortProxy.setSortRole(self.model.sortRole) self.sortProxy.setSourceModel(self.updateProxy) self.sortProxy.sort(0, Qt.AscendingOrder) # Instance síťového připojení self.network = CrossingNetwork(self.model) # Výroba ovládacích prvků self.connectButton = QPushButton(self, text = "Start") self.connectButton.clicked.connect(self.network.socketConnect) self.disconnectButton = QPushButton(self, text = "Stop") self.disconnectButton.clicked.connect(self.network.socketDisconnect) # Zobrazení dat a propojení s modelem self.listView = QListView(self) self.listView.setModel(self.sortProxy) self.drawView = CrossingView(self) self.drawView.setModel(self.updateProxy) # Zobrazení stavu self.stateLabel = QLabel(self, text = "Inactive") self.network.stateChanged.connect(self.stateChanged) # Rozložení ovládacích prvků self.layout = QVBoxLayout(self) self.layout.addWidget(self.connectButton) self.layout.addWidget(self.disconnectButton) self.layout.addWidget(self.listView) self.layout.addWidget(self.drawView) self.layout.addWidget(self.stateLabel) # Zobrazení self.setLayout(self.layout) self.show() # Aktualizátor stavového labelu @Slot(str) def stateChanged(self, state): self.stateLabel.setText(state) # Spuštění celého programu crossing = Crossing() app.exec_()