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 45from PyQt5.QtCore import (pyqtSignal, QDataStream, QMutex, QMutexLocker, 46 QThread, QWaitCondition) 47from PyQt5.QtGui import QIntValidator 48from PyQt5.QtWidgets import (QApplication, QDialogButtonBox, QGridLayout, 49 QLabel, QLineEdit, QMessageBox, QPushButton, QWidget) 50from PyQt5.QtNetwork import (QAbstractSocket, QHostAddress, QNetworkInterface, 51 QTcpSocket) 52 53 54class FortuneThread(QThread): 55 newFortune = pyqtSignal(str) 56 57 error = pyqtSignal(int, str) 58 59 def __init__(self, parent=None): 60 super(FortuneThread, self).__init__(parent) 61 62 self.quit = False 63 self.hostName = '' 64 self.cond = QWaitCondition() 65 self.mutex = QMutex() 66 self.port = 0 67 68 def __del__(self): 69 self.mutex.lock() 70 self.quit = True 71 self.cond.wakeOne() 72 self.mutex.unlock() 73 self.wait() 74 75 def requestNewFortune(self, hostname, port): 76 locker = QMutexLocker(self.mutex) 77 self.hostName = hostname 78 self.port = port 79 if not self.isRunning(): 80 self.start() 81 else: 82 self.cond.wakeOne() 83 84 def run(self): 85 self.mutex.lock() 86 serverName = self.hostName 87 serverPort = self.port 88 self.mutex.unlock() 89 90 while not self.quit: 91 Timeout = 5 * 1000 92 93 socket = QTcpSocket() 94 socket.connectToHost(serverName, serverPort) 95 96 if not socket.waitForConnected(Timeout): 97 self.error.emit(socket.error(), socket.errorString()) 98 return 99 100 while socket.bytesAvailable() < 2: 101 if not socket.waitForReadyRead(Timeout): 102 self.error.emit(socket.error(), socket.errorString()) 103 return 104 105 instr = QDataStream(socket) 106 instr.setVersion(QDataStream.Qt_4_0) 107 blockSize = instr.readUInt16() 108 109 while socket.bytesAvailable() < blockSize: 110 if not socket.waitForReadyRead(Timeout): 111 self.error.emit(socket.error(), socket.errorString()) 112 return 113 114 self.mutex.lock() 115 fortune = instr.readQString() 116 self.newFortune.emit(fortune) 117 118 self.cond.wait(self.mutex) 119 serverName = self.hostName 120 serverPort = self.port 121 self.mutex.unlock() 122 123 124class BlockingClient(QWidget): 125 def __init__(self, parent=None): 126 super(BlockingClient, self).__init__(parent) 127 128 self.thread = FortuneThread() 129 self.currentFortune = '' 130 131 hostLabel = QLabel("&Server name:") 132 portLabel = QLabel("S&erver port:") 133 134 for ipAddress in QNetworkInterface.allAddresses(): 135 if ipAddress != QHostAddress.LocalHost and ipAddress.toIPv4Address() != 0: 136 break 137 else: 138 ipAddress = QHostAddress(QHostAddress.LocalHost) 139 140 ipAddress = ipAddress.toString() 141 142 self.hostLineEdit = QLineEdit(ipAddress) 143 self.portLineEdit = QLineEdit() 144 self.portLineEdit.setValidator(QIntValidator(1, 65535, self)) 145 146 hostLabel.setBuddy(self.hostLineEdit) 147 portLabel.setBuddy(self.portLineEdit) 148 149 self.statusLabel = QLabel( 150 "This example requires that you run the Fortune Server example as well.") 151 self.statusLabel.setWordWrap(True) 152 153 self.getFortuneButton = QPushButton("Get Fortune") 154 self.getFortuneButton.setDefault(True) 155 self.getFortuneButton.setEnabled(False) 156 157 quitButton = QPushButton("Quit") 158 159 buttonBox = QDialogButtonBox() 160 buttonBox.addButton(self.getFortuneButton, QDialogButtonBox.ActionRole) 161 buttonBox.addButton(quitButton, QDialogButtonBox.RejectRole) 162 163 self.getFortuneButton.clicked.connect(self.requestNewFortune) 164 quitButton.clicked.connect(self.close) 165 self.hostLineEdit.textChanged.connect(self.enableGetFortuneButton) 166 self.portLineEdit.textChanged.connect(self.enableGetFortuneButton) 167 self.thread.newFortune.connect(self.showFortune) 168 self.thread.error.connect(self.displayError) 169 170 mainLayout = QGridLayout() 171 mainLayout.addWidget(hostLabel, 0, 0) 172 mainLayout.addWidget(self.hostLineEdit, 0, 1) 173 mainLayout.addWidget(portLabel, 1, 0) 174 mainLayout.addWidget(self.portLineEdit, 1, 1) 175 mainLayout.addWidget(self.statusLabel, 2, 0, 1, 2) 176 mainLayout.addWidget(buttonBox, 3, 0, 1, 2) 177 self.setLayout(mainLayout) 178 179 self.setWindowTitle("Blocking Fortune Client") 180 self.portLineEdit.setFocus() 181 182 def requestNewFortune(self): 183 self.getFortuneButton.setEnabled(False) 184 self.thread.requestNewFortune(self.hostLineEdit.text(), 185 int(self.portLineEdit.text())) 186 187 def showFortune(self, nextFortune): 188 if nextFortune == self.currentFortune: 189 self.requestNewFortune() 190 return 191 192 self.currentFortune = nextFortune 193 self.statusLabel.setText(self.currentFortune) 194 self.getFortuneButton.setEnabled(True) 195 196 def displayError(self, socketError, message): 197 if socketError == QAbstractSocket.HostNotFoundError: 198 QMessageBox.information(self, "Blocking Fortune Client", 199 "The host was not found. Please check the host and port " 200 "settings.") 201 elif socketError == QAbstractSocket.ConnectionRefusedError: 202 QMessageBox.information(self, "Blocking Fortune Client", 203 "The connection was refused by the peer. Make sure the " 204 "fortune server is running, and check that the host name " 205 "and port settings are correct.") 206 else: 207 QMessageBox.information(self, "Blocking Fortune Client", 208 "The following error occurred: %s." % message) 209 210 self.getFortuneButton.setEnabled(True) 211 212 def enableGetFortuneButton(self): 213 self.getFortuneButton.setEnabled(self.hostLineEdit.text() != '' and 214 self.portLineEdit.text() != '') 215 216 217if __name__ == '__main__': 218 219 import sys 220 221 app = QApplication(sys.argv) 222 client = BlockingClient() 223 client.show() 224 sys.exit(app.exec_()) 225