def init(): """Start listening to incoming connections.""" global _server if _server is not None: return server = QLocalServer(None) # find a free socket name to use for name in ids(): if server.listen(name): break else: # all names failed, try to contact and remove stale file if that fails socket = QLocalSocket() for name in ids(): socket.connectToServer(name) if not socket.waitForConnected(10000): QLocalServer.removeServer(name) if server.listen(name): break else: socket.disconnectFromServer() else: # no ids left, don't listen return app.aboutToQuit.connect(server.close) server.newConnection.connect(slot_new_connection) os.environ["FRESCOBALDI_SOCKET"] = name _server = server
class SingleApplication(QObject): newInstance = pyqtSignal() urlPost = pyqtSignal(str) def __init__(self): super().__init__() self.mServer = QLocalServer() self.mServer.newConnection.connect(self.newConnection) def listen(self, client): self.mServer.removeServer(client) self.mServer.listen(client) print(self.mServer.errorString()) def hasPrevious(self, name, args): socket = QLocalSocket() socket.connectToServer(name, QLocalSocket.ReadWrite) if socket.waitForConnected(): if len(args) > 1: socket.write(args[1]) socket.flush() return True return False def newConnection(self): self.newInstance.emit() self.mSocket = self.mServer.nextPendingConnection() self.mSocket.readyRead.connect(self.readyRead) def readyRead(self): self.urlPost.emit(str(self.mSocket.readAll())) self.mSocket.close()
def __init__(self, _id, *argv): super(QtSingleApplication, self).__init__(*argv) self._id = _id self._activationWindow = None self._activateOnMessage = False # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected(-1) if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't. # First we remove existing servers of that name that might not have been properly closed as the server died QLocalServer.removeServer(self._id) self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection)
def __init__(self, appid, *argv): super(SingleApplication, self).__init__(*argv) self._appid = appid self._activationWindow = None self._activateOnMessage = False self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._appid) self._isRunning = self._outSocket.waitForConnected() self._outStream = None self._inSocket = None self._inStream = None self._server = None self.settings = QSettings(SingleApplication.getSettingsPath(), QSettings.IniFormat) self.singleInstance = self.settings.value('singleInstance', 'on', type=str) in {'on', 'true'} if self._isRunning and self.singleInstance: self._outStream = QTextStream(self._outSocket) for a in argv[0][1:]: a = os.path.join(os.getcwd(), a) if os.path.isfile(a): self.sendMessage(a) break sys.exit(0) else: error = self._outSocket.error() if error == QLocalSocket.ConnectionRefusedError: self.close() QLocalServer.removeServer(self._appid) self._outSocket = None self._server = QLocalServer() self._server.listen(self._appid) self._server.newConnection.connect(self._onNewConnection)
def newLocalServer(self): self.localServer = QLocalServer(self) self.localServer.newConnection.connect(self.newLocalConnection) if not self.localServer.listen(self.serverName): if self.localServer.serverError( ) == QAbstractSocket.AddressInUseError: QLocalServer.removeServer(self.serverName) self.localServer.listen(self.serverName)
def create(self, name=piony.G_SOCKET_NAME): QLocalServer.removeServer(name) self.server = QLocalServer() if not self.server.listen(name): print("Error: server -- unable to start: {}.".format( self.server.errorString())) self.quit.emit() self.server.newConnection.connect(self.notify)
def create(self, name=piony.G_SOCKET_NAME): QLocalServer.removeServer(name) self.server = QLocalServer() if not self.server.listen(name): print("Error: server -- unable to start: {}." .format(self.server.errorString())) self.quit.emit() self.server.newConnection.connect(self.notify)
def __init__(self, instance=None): QObject.__init__(self) sock_name = self.get_sock_name(instance) QLocalServer.removeServer(sock_name) self._server = QLocalServer() self._server.newConnection.connect(self._on_new_connection) if not self._server.listen(sock_name): logging.error("Can not start ipc: %s" % self._server.errorString()) self._readers = {}
class InstanceHandler(QObject): """ Makes sure that only one instance of this application can be run. """ received = pyqtSignal(str) def __init__(self, parent, name): super(__class__, self).__init__(parent) self._parent = parent self._name = name self._timeout = 1000 self._socket = QLocalSocket(self) self._socket.connectToServer(self._name) self._is_already_running = self._socket.waitForConnected(self._timeout) if not self.isAlreadyRunning(): self._server = QLocalServer(self) self._server.newConnection.connect(self._receive_data) self._server.removeServer(self._name) self._server.listen(self._name) def _send_data(self, data=None): """ Sends model to an server-application. """ if not data: data = "" self._socket.write(data.encode()) self._socket.waitForBytesWritten(self._timeout) self._socket.disconnectFromServer() def _receive_data(self): """ Receives model from an client-application. """ socket = self._server.nextPendingConnection() if socket.waitForReadyRead(self._timeout): # Emit transmitted model. self.received.emit(socket.readAll().data().decode( "utf-8", "surrogateescape")) else: # When no model was transmitted just emit empty string. self.received.emit("") def newTab(self, input): """ Opens a new tab in an already running instance. """ self._send_data(input) def isAlreadyRunning(self) -> bool: """ Returns True when an instance of the application is already running, otherwise False. """ return self._is_already_running
def __init__(self, win_id, *argv): self.logger = logging.getLogger(self.__class__.__name__) self.logger.info(f'Start Tribler application. Win id: "{win_id}". ' f'Sys argv: "{sys.argv}"') QApplication.__init__(self, *argv) self._id = win_id self._activation_window = None self._activate_on_message = False # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected() self._outStream = None self._inSocket = None self._inStream = None self._server = None if self._isRunning: # Yes, there is. self.logger.info('Another instance is running') self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't, at least not properly. # Cleanup any past, crashed server. error = self._outSocket.error() self.logger.info(f'No running instances (socket error: {error})') if error == QLocalSocket.ConnectionRefusedError: self.logger.info( 'Received QLocalSocket.ConnectionRefusedError; removing server.' ) self.close() QLocalServer.removeServer(self._id) self._outSocket = None self._server = QLocalServer() self._server.listen(self._id) connect(self._server.newConnection, self._on_new_connection)
def __init__(self, win_id, *argv): logfunc = logging.info logfunc(sys._getframe().f_code.co_name + '()') QApplication.__init__(self, *argv) self._id = win_id self._activation_window = None self._activate_on_message = False # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected() self._outStream = None self._inSocket = None self._inStream = None self._server = None if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't, at least not properly. # Cleanup any past, crashed server. error = self._outSocket.error() logfunc(LOGVARSTR % ('self._outSocket.error()', error)) if error == QLocalSocket.ConnectionRefusedError: logfunc('received QLocalSocket.ConnectionRefusedError; ' + \ 'removing server.') self.close() QLocalServer.removeServer(self._id) self._outSocket = None self._server = QLocalServer() self._server.listen(self._id) self._server.newConnection.connect(self._on_new_connection) logfunc(sys._getframe().f_code.co_name + '(): returning')
class QtSingleApplication(QApplication): """ Adapted from https://stackoverflow.com/a/12712362/11038610 Published by Johan Rade under 2-clause BSD license, opensource.org/licenses/BSD-2-Clause """ message_received_event = pyqtSignal(str) def __init__(self, id, *argv): super().__init__(*argv) self._id = id # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected() if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't. self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.removeServer(self._id) self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection) def isRunning(self): return self._isRunning def id(self): return self._id def sendMessage(self, msg): if not self._outStream: return False self._outStream << msg << '\n' self._outStream.flush() return self._outSocket.waitForBytesWritten() def _onNewConnection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._onReadyRead) self._inSocket = self._server.nextPendingConnection() if not self._inSocket: return self._inStream = QTextStream(self._inSocket) self._inStream.setCodec('UTF-8') self._inSocket.readyRead.connect(self._onReadyRead) def _onReadyRead(self): while True: msg = self._inStream.readLine() if not msg: break self.message_received_event.emit(msg)
class TreeMainControl(QObject): """Class to handle all global controls. Provides methods for all controls and stores local control objects. """ def __init__(self, pathObjects, parent=None): """Initialize the main tree controls Arguments: pathObjects -- a list of file objects to open parent -- the parent QObject if given """ super().__init__(parent) self.localControls = [] self.activeControl = None self.trayIcon = None self.isTrayMinimized = False self.configDialog = None self.sortDialog = None self.numberingDialog = None self.findTextDialog = None self.findConditionDialog = None self.findReplaceDialog = None self.filterTextDialog = None self.filterConditionDialog = None self.basicHelpView = None self.passwords = {} self.creatingLocalControlFlag = False globalref.mainControl = self self.allActions = {} try: # check for existing TreeLine session socket = QLocalSocket() socket.connectToServer('treeline3-session', QIODevice.WriteOnly) # if found, send files to open and exit TreeLine if socket.waitForConnected(1000): socket.write( bytes(repr([str(path) for path in pathObjects]), 'utf-8')) if socket.waitForBytesWritten(1000): socket.close() sys.exit(0) # start local server to listen for attempt to start new session self.serverSocket = QLocalServer() # remove any old servers still around after a crash in linux self.serverSocket.removeServer('treeline3-session') self.serverSocket.listen('treeline3-session') self.serverSocket.newConnection.connect(self.getSocket) except AttributeError: print(_('Warning: Could not create local socket')) mainVersion = '.'.join(__version__.split('.')[:2]) globalref.genOptions = options.Options('general', 'TreeLine', mainVersion, 'bellz') optiondefaults.setGenOptionDefaults(globalref.genOptions) globalref.miscOptions = options.Options('misc') optiondefaults.setMiscOptionDefaults(globalref.miscOptions) globalref.histOptions = options.Options('history') optiondefaults.setHistOptionDefaults(globalref.histOptions) globalref.toolbarOptions = options.Options('toolbar') optiondefaults.setToolbarOptionDefaults(globalref.toolbarOptions) globalref.keyboardOptions = options.Options('keyboard') optiondefaults.setKeyboardOptionDefaults(globalref.keyboardOptions) try: globalref.genOptions.readFile() globalref.miscOptions.readFile() globalref.histOptions.readFile() globalref.toolbarOptions.readFile() globalref.keyboardOptions.readFile() except IOError: errorDir = options.Options.basePath if not errorDir: errorDir = _('missing directory') QMessageBox.warning( None, 'TreeLine', _('Error - could not write config file to {}').format( errorDir)) options.Options.basePath = None iconPathList = self.findResourcePaths('icons', iconPath) globalref.toolIcons = icondict.IconDict( [path / 'toolbar' for path in iconPathList], ['', '32x32', '16x16']) globalref.toolIcons.loadAllIcons() windowIcon = globalref.toolIcons.getIcon('treelogo') if windowIcon: QApplication.setWindowIcon(windowIcon) globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree']) icon = globalref.treeIcons.getIcon('default') qApp.setStyle(QStyleFactory.create('Fusion')) self.colorSet = colorset.ColorSet() if globalref.miscOptions['ColorTheme'] != 'system': self.colorSet.setAppColors() self.recentFiles = recentfiles.RecentFileList() if globalref.genOptions['AutoFileOpen'] and not pathObjects: recentPath = self.recentFiles.firstPath() if recentPath: pathObjects = [recentPath] self.setupActions() self.systemFont = QApplication.font() self.updateAppFont() if globalref.genOptions['MinToSysTray']: self.createTrayIcon() qApp.focusChanged.connect(self.updateActionsAvail) if pathObjects: for pathObj in pathObjects: self.openFile(pathObj, True) else: self.createLocalControl() def getSocket(self): """Open a socket from an attempt to open a second Treeline instance. Opens the file (or raise and focus if open) in this instance. """ socket = self.serverSocket.nextPendingConnection() if socket and socket.waitForReadyRead(1000): data = str(socket.readAll(), 'utf-8') try: paths = ast.literal_eval(data) if paths: for path in paths: pathObj = pathlib.Path(path) if pathObj != self.activeControl.filePathObj: self.openFile(pathObj, True) else: self.activeControl.activeWindow.activateAndRaise() else: self.activeControl.activeWindow.activateAndRaise() except (SyntaxError, ValueError, TypeError, RuntimeError): pass def findResourcePaths(self, resourceName, preferredPath=''): """Return list of potential non-empty pathlib objects for the resource. List includes preferred, module and user option paths. Arguments: resourceName -- the typical name of the resource directory preferredPath -- add this as the second path if given """ # use abspath() - pathlib's resolve() can be buggy with network drives modPath = pathlib.Path(os.path.abspath(sys.path[0])) if modPath.is_file(): modPath = modPath.parent # for frozen binary pathList = [modPath / '..' / resourceName, modPath / resourceName] if options.Options.basePath: basePath = pathlib.Path(options.Options.basePath) pathList.insert(0, basePath / resourceName) if preferredPath: pathList.insert(1, pathlib.Path(preferredPath)) return [ pathlib.Path(os.path.abspath(str(path))) for path in pathList if path.is_dir() and list(path.iterdir()) ] def findResourceFile(self, fileName, resourceName, preferredPath=''): """Return a path object for a resource file. Add a language code before the extension if it exists. Arguments: fileName -- the name of the file to find resourceName -- the typical name of the resource directory preferredPath -- search this path first if given """ fileList = [fileName] if globalref.lang and globalref.lang != 'C': fileList[0:0] = [ fileName.replace('.', '_{0}.'.format(globalref.lang)), fileName.replace('.', '_{0}.'.format(globalref.lang[:2])) ] for fileName in fileList: for path in self.findResourcePaths(resourceName, preferredPath): if (path / fileName).is_file(): return path / fileName return None def defaultPathObj(self, dirOnly=False): """Return a reasonable default file path object. Used for open, save-as, import and export. Arguments: dirOnly -- if True, do not include basename of file """ pathObj = None if self.activeControl: pathObj = self.activeControl.filePathObj if not pathObj: pathObj = self.recentFiles.firstDir() if not pathObj: pathObj = pathlib.Path.home() if dirOnly: pathObj = pathObj.parent return pathObj def openFile(self, pathObj, forceNewWindow=False, checkModified=False, importOnFail=True): """Open the file given by path if not already open. If already open in a different window, focus and raise the window. Arguments: pathObj -- the path object to read forceNewWindow -- if True, use a new window regardless of option checkModified -- if True & not new win, prompt if file modified importOnFail -- if True, prompts for import on non-TreeLine files """ match = [ control for control in self.localControls if pathObj == control.filePathObj ] if match and self.activeControl not in match: control = match[0] control.activeWindow.activateAndRaise() self.updateLocalControlRef(control) return if checkModified and not (forceNewWindow or globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): return if not self.checkAutoSave(pathObj): if not self.localControls: self.createLocalControl() return QApplication.setOverrideCursor(Qt.WaitCursor) try: self.createLocalControl(pathObj, None, forceNewWindow) self.recentFiles.addItem(pathObj) if not (globalref.genOptions['SaveTreeStates'] and self.recentFiles.retrieveTreeState(self.activeControl)): self.activeControl.expandRootNodes() self.activeControl.selectRootSpot() QApplication.restoreOverrideCursor() except IOError: QApplication.restoreOverrideCursor() QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not read file {0}').format(pathObj)) self.recentFiles.removeItem(pathObj) except (ValueError, KeyError, TypeError): fileObj = pathObj.open('rb') fileObj, encrypted = self.decryptFile(fileObj) if not fileObj: if not self.localControls: self.createLocalControl() QApplication.restoreOverrideCursor() return fileObj, compressed = self.decompressFile(fileObj) if compressed or encrypted: try: textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8') self.createLocalControl(textFileObj, None, forceNewWindow) fileObj.close() textFileObj.close() self.recentFiles.addItem(pathObj) if not (globalref.genOptions['SaveTreeStates'] and self.recentFiles.retrieveTreeState( self.activeControl)): self.activeControl.expandRootNodes() self.activeControl.selectRootSpot() self.activeControl.compressed = compressed self.activeControl.encrypted = encrypted QApplication.restoreOverrideCursor() return except (ValueError, KeyError, TypeError): pass fileObj.close() importControl = imports.ImportControl(pathObj) structure = importControl.importOldTreeLine() if structure: self.createLocalControl(pathObj, structure, forceNewWindow) self.activeControl.printData.readData( importControl.treeLineRootAttrib) self.recentFiles.addItem(pathObj) self.activeControl.expandRootNodes() self.activeControl.imported = True QApplication.restoreOverrideCursor() return QApplication.restoreOverrideCursor() if importOnFail: importControl = imports.ImportControl(pathObj) structure = importControl.interactiveImport(True) if structure: self.createLocalControl(pathObj, structure, forceNewWindow) self.activeControl.imported = True return else: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - invalid TreeLine file {0}').format(pathObj)) self.recentFiles.removeItem(pathObj) if not self.localControls: self.createLocalControl() def decryptFile(self, fileObj): """Check for encryption and decrypt the fileObj if needed. Return a tuple of the file object and True if it was encrypted. Return None for the file object if the user cancels. Arguments: fileObj -- the file object to check and decrypt """ if fileObj.read(len(encryptPrefix)) != encryptPrefix: fileObj.seek(0) return (fileObj, False) while True: pathObj = pathlib.Path(fileObj.name) password = self.passwords.get(pathObj, '') if not password: QApplication.restoreOverrideCursor() dialog = miscdialogs.PasswordDialog( False, pathObj.name, QApplication.activeWindow()) if dialog.exec_() != QDialog.Accepted: fileObj.close() return (None, True) QApplication.setOverrideCursor(Qt.WaitCursor) password = dialog.password if miscdialogs.PasswordDialog.remember: self.passwords[pathObj] = password try: text = p3.p3_decrypt(fileObj.read(), password.encode()) fileIO = io.BytesIO(text) fileIO.name = fileObj.name fileObj.close() return (fileIO, True) except p3.CryptError: try: del self.passwords[pathObj] except KeyError: pass def decompressFile(self, fileObj): """Check for compression and decompress the fileObj if needed. Return a tuple of the file object and True if it was compressed. Arguments: fileObj -- the file object to check and decompress """ prefix = fileObj.read(2) fileObj.seek(0) if prefix != b'\037\213': return (fileObj, False) try: newFileObj = gzip.GzipFile(fileobj=fileObj) except zlib.error: return (fileObj, False) newFileObj.name = fileObj.name return (newFileObj, True) def checkAutoSave(self, pathObj): """Check for presence of auto save file & prompt user. Return True if OK to contimue, False if aborting or already loaded. Arguments: pathObj -- the base path object to search for a backup """ if not globalref.genOptions['AutoSaveMinutes']: return True basePath = pathObj pathObj = pathlib.Path(str(pathObj) + '~') if not pathObj.is_file(): return True msgBox = QMessageBox( QMessageBox.Information, 'TreeLine', _('Backup file "{}" exists.\nA previous ' 'session may have crashed').format(pathObj), QMessageBox.NoButton, QApplication.activeWindow()) restoreButton = msgBox.addButton(_('&Restore Backup'), QMessageBox.ApplyRole) deleteButton = msgBox.addButton(_('&Delete Backup'), QMessageBox.DestructiveRole) cancelButton = msgBox.addButton(_('&Cancel File Open'), QMessageBox.RejectRole) msgBox.exec_() if msgBox.clickedButton() == restoreButton: self.openFile(pathObj) if self.activeControl.filePathObj != pathObj: return False try: basePath.unlink() pathObj.rename(basePath) except OSError: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not rename "{0}" to "{1}"').format( pathObj, basePath)) return False self.activeControl.filePathObj = basePath self.activeControl.updateWindowCaptions() self.recentFiles.removeItem(pathObj) self.recentFiles.addItem(basePath) return False elif msgBox.clickedButton() == deleteButton: try: pathObj.unlink() except OSError: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not remove backup file {}').format( pathObj)) else: # cancel button return False return True def createLocalControl(self, pathObj=None, treeStruct=None, forceNewWindow=False): """Create a new local control object and add it to the list. Use an imported structure if given or open the file if path is given. Arguments: pathObj -- the path object or file object for the control to open treeStruct -- the imported structure to use forceNewWindow -- if True, use a new window regardless of option """ self.creatingLocalControlFlag = True localControl = treelocalcontrol.TreeLocalControl( self.allActions, pathObj, treeStruct, forceNewWindow) localControl.controlActivated.connect(self.updateLocalControlRef) localControl.controlClosed.connect(self.removeLocalControlRef) self.localControls.append(localControl) self.updateLocalControlRef(localControl) self.creatingLocalControlFlag = False localControl.updateRightViews() localControl.updateCommandsAvail() def updateLocalControlRef(self, localControl): """Set the given local control as active. Called by signal from a window becoming active. Also updates non-modal dialogs. Arguments: localControl -- the new active local control """ if localControl != self.activeControl: self.activeControl = localControl if self.configDialog and self.configDialog.isVisible(): self.configDialog.setRefs(self.activeControl) def removeLocalControlRef(self, localControl): """Remove ref to local control based on a closing signal. Also do application exit clean ups if last control closing. Arguments: localControl -- the local control that is closing """ try: self.localControls.remove(localControl) except ValueError: return # skip for unreporducible bug - odd race condition? if globalref.genOptions['SaveTreeStates']: self.recentFiles.saveTreeState(localControl) if not self.localControls and not self.creatingLocalControlFlag: if globalref.genOptions['SaveWindowGeom']: localControl.windowList[0].saveWindowGeom() else: localControl.windowList[0].resetWindowGeom() self.recentFiles.writeItems() localControl.windowList[0].saveToolbarPosition() globalref.histOptions.writeFile() if self.trayIcon: self.trayIcon.hide() # stop listening for session connections try: self.serverSocket.close() del self.serverSocket except AttributeError: pass if self.localControls: # make sure a window is active (may not be focused), to avoid # bugs due to a deleted current window newControl = self.localControls[0] newControl.setActiveWin(newControl.windowList[0]) localControl.deleteLater() def createTrayIcon(self): """Create a new system tray icon if not already created. """ if QSystemTrayIcon.isSystemTrayAvailable: if not self.trayIcon: self.trayIcon = QSystemTrayIcon(qApp.windowIcon(), qApp) self.trayIcon.activated.connect(self.toggleTrayShow) self.trayIcon.show() def trayMinimize(self): """Minimize to tray based on window minimize signal. """ if self.trayIcon and QSystemTrayIcon.isSystemTrayAvailable: # skip minimize to tray if not all windows minimized for control in self.localControls: for window in control.windowList: if not window.isMinimized(): return for control in self.localControls: for window in control.windowList: window.hide() self.isTrayMinimized = True def toggleTrayShow(self): """Toggle show and hide application based on system tray icon click. """ if self.isTrayMinimized: for control in self.localControls: for window in control.windowList: window.show() window.showNormal() self.activeControl.activeWindow.treeView.setFocus() else: for control in self.localControls: for window in control.windowList: window.hide() self.isTrayMinimized = not self.isTrayMinimized def updateConfigDialog(self): """Update the config dialog for changes if it exists. """ if self.configDialog: self.configDialog.reset() def currentStatusBar(self): """Return the status bar from the current main window. """ return self.activeControl.activeWindow.statusBar() def windowActions(self): """Return a list of window menu actions from each local control. """ actions = [] for control in self.localControls: actions.extend( control.windowActions( len(actions) + 1, control == self.activeControl)) return actions def updateActionsAvail(self, oldWidget, newWidget): """Update command availability based on focus changes. Arguments: oldWidget -- the previously focused widget newWidget -- the newly focused widget """ self.allActions['FormatSelectAll'].setEnabled( hasattr(newWidget, 'selectAll') and not hasattr(newWidget, 'editTriggers')) def setupActions(self): """Add the actions for contols at the global level. """ fileNewAct = QAction(_('&New...'), self, toolTip=_('New File'), statusTip=_('Start a new file')) fileNewAct.triggered.connect(self.fileNew) self.allActions['FileNew'] = fileNewAct fileOpenAct = QAction(_('&Open...'), self, toolTip=_('Open File'), statusTip=_('Open a file from disk')) fileOpenAct.triggered.connect(self.fileOpen) self.allActions['FileOpen'] = fileOpenAct fileSampleAct = QAction(_('Open Sa&mple...'), self, toolTip=_('Open Sample'), statusTip=_('Open a sample file')) fileSampleAct.triggered.connect(self.fileOpenSample) self.allActions['FileOpenSample'] = fileSampleAct fileImportAct = QAction(_('&Import...'), self, statusTip=_('Open a non-TreeLine file')) fileImportAct.triggered.connect(self.fileImport) self.allActions['FileImport'] = fileImportAct fileQuitAct = QAction(_('&Quit'), self, statusTip=_('Exit the application')) fileQuitAct.triggered.connect(self.fileQuit) self.allActions['FileQuit'] = fileQuitAct dataConfigAct = QAction( _('&Configure Data Types...'), self, statusTip=_('Modify data types, fields & output lines'), checkable=True) dataConfigAct.triggered.connect(self.dataConfigDialog) self.allActions['DataConfigType'] = dataConfigAct dataVisualConfigAct = QAction( _('Show C&onfiguration Structure...'), self, statusTip=_('Show read-only visualization of type structure')) dataVisualConfigAct.triggered.connect(self.dataVisualConfig) self.allActions['DataVisualConfig'] = dataVisualConfigAct dataSortAct = QAction(_('Sor&t Nodes...'), self, statusTip=_('Define node sort operations'), checkable=True) dataSortAct.triggered.connect(self.dataSortDialog) self.allActions['DataSortNodes'] = dataSortAct dataNumberingAct = QAction(_('Update &Numbering...'), self, statusTip=_('Update node numbering fields'), checkable=True) dataNumberingAct.triggered.connect(self.dataNumberingDialog) self.allActions['DataNumbering'] = dataNumberingAct toolsFindTextAct = QAction( _('&Find Text...'), self, statusTip=_('Find text in node titles & data'), checkable=True) toolsFindTextAct.triggered.connect(self.toolsFindTextDialog) self.allActions['ToolsFindText'] = toolsFindTextAct toolsFindConditionAct = QAction( _('&Conditional Find...'), self, statusTip=_('Use field conditions to find nodes'), checkable=True) toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog) self.allActions['ToolsFindCondition'] = toolsFindConditionAct toolsFindReplaceAct = QAction( _('Find and &Replace...'), self, statusTip=_('Replace text strings in node data'), checkable=True) toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog) self.allActions['ToolsFindReplace'] = toolsFindReplaceAct toolsFilterTextAct = QAction( _('&Text Filter...'), self, statusTip=_('Filter nodes to only show text matches'), checkable=True) toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog) self.allActions['ToolsFilterText'] = toolsFilterTextAct toolsFilterConditionAct = QAction( _('C&onditional Filter...'), self, statusTip=_('Use field conditions to filter nodes'), checkable=True) toolsFilterConditionAct.triggered.connect( self.toolsFilterConditionDialog) self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct toolsGenOptionsAct = QAction( _('&General Options...'), self, statusTip=_('Set user preferences for all files')) toolsGenOptionsAct.triggered.connect(self.toolsGenOptions) self.allActions['ToolsGenOptions'] = toolsGenOptionsAct toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'), self, statusTip=_('Customize keyboard commands')) toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts) self.allActions['ToolsShortcuts'] = toolsShortcutAct toolsToolbarAct = QAction(_('C&ustomize Toolbars...'), self, statusTip=_('Customize toolbar buttons')) toolsToolbarAct.triggered.connect(self.toolsCustomToolbars) self.allActions['ToolsToolbars'] = toolsToolbarAct toolsFontsAct = QAction( _('Customize Fo&nts...'), self, statusTip=_('Customize fonts in various views')) toolsFontsAct.triggered.connect(self.toolsCustomFonts) self.allActions['ToolsFonts'] = toolsFontsAct toolsColorsAct = QAction( _('Custo&mize Colors...'), self, statusTip=_('Customize GUI colors and themes')) toolsColorsAct.triggered.connect(self.toolsCustomColors) self.allActions['ToolsColors'] = toolsColorsAct formatSelectAllAct = QAction( _('&Select All'), self, statusTip=_('Select all text in an editor')) formatSelectAllAct.setEnabled(False) formatSelectAllAct.triggered.connect(self.formatSelectAll) self.allActions['FormatSelectAll'] = formatSelectAllAct helpBasicAct = QAction(_('&Basic Usage...'), self, statusTip=_('Display basic usage instructions')) helpBasicAct.triggered.connect(self.helpViewBasic) self.allActions['HelpBasic'] = helpBasicAct helpFullAct = QAction( _('&Full Documentation...'), self, statusTip=_('Open a TreeLine file with full documentation')) helpFullAct.triggered.connect(self.helpViewFull) self.allActions['HelpFull'] = helpFullAct helpAboutAct = QAction( _('&About TreeLine...'), self, statusTip=_('Display version info about this program')) helpAboutAct.triggered.connect(self.helpAbout) self.allActions['HelpAbout'] = helpAboutAct for name, action in self.allActions.items(): icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) def fileNew(self): """Start a new blank file. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('templates', templatePath) if searchPaths: dialog = miscdialogs.TemplateFileDialog( _('New File'), _('&Select Template'), searchPaths) if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) self.activeControl.filePathObj = None self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() else: self.createLocalControl() self.activeControl.selectRootSpot() def fileOpen(self): """Prompt for a filename and open it. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): filters = ';;'.join((globalref.fileFilters['trlnopen'], globalref.fileFilters['all'])) fileName, selFilter = QFileDialog.getOpenFileName( QApplication.activeWindow(), _('TreeLine - Open File'), str(self.defaultPathObj(True)), filters) if fileName: self.openFile(pathlib.Path(fileName)) def fileOpenSample(self): """Open a sample file from the doc directories. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('samples', samplePath) dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'), _('&Select Sample'), searchPaths, False) if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) name = dialog.selectedName() + '.trln' self.activeControl.filePathObj = pathlib.Path(name) self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True def fileImport(self): """Prompt for an import type, then a file to import. """ importControl = imports.ImportControl() structure = importControl.interactiveImport() if structure: self.createLocalControl(importControl.pathObj, structure) if importControl.treeLineRootAttrib: self.activeControl.printData.readData( importControl.treeLineRootAttrib) self.activeControl.imported = True def fileQuit(self): """Close all windows to exit the applications. """ for control in self.localControls[:]: control.closeWindows() def dataConfigDialog(self, show): """Show or hide the non-modal data config dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.configDialog: self.configDialog = configdialog.ConfigDialog() dataConfigAct = self.allActions['DataConfigType'] self.configDialog.dialogShown.connect(dataConfigAct.setChecked) self.configDialog.setRefs(self.activeControl, True) self.configDialog.show() else: self.configDialog.close() def dataVisualConfig(self): """Show a TreeLine file to visualize the config structure. """ structure = ( self.activeControl.structure.treeFormats.visualConfigStructure( str(self.activeControl.filePathObj))) self.createLocalControl(treeStruct=structure, forceNewWindow=True) self.activeControl.filePathObj = pathlib.Path('structure.trln') self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True win = self.activeControl.activeWindow win.rightTabs.setCurrentWidget(win.outputSplitter) def dataSortDialog(self, show): """Show or hide the non-modal data sort nodes dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.sortDialog: self.sortDialog = miscdialogs.SortDialog() dataSortAct = self.allActions['DataSortNodes'] self.sortDialog.dialogShown.connect(dataSortAct.setChecked) self.sortDialog.show() else: self.sortDialog.close() def dataNumberingDialog(self, show): """Show or hide the non-modal update node numbering dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.numberingDialog: self.numberingDialog = miscdialogs.NumberingDialog() dataNumberingAct = self.allActions['DataNumbering'] self.numberingDialog.dialogShown.connect( dataNumberingAct.setChecked) self.numberingDialog.show() if not self.numberingDialog.checkForNumberingFields(): self.numberingDialog.close() else: self.numberingDialog.close() def toolsFindTextDialog(self, show): """Show or hide the non-modal find text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findTextDialog: self.findTextDialog = miscdialogs.FindFilterDialog() toolsFindTextAct = self.allActions['ToolsFindText'] self.findTextDialog.dialogShown.connect( toolsFindTextAct.setChecked) self.findTextDialog.selectAllText() self.findTextDialog.show() else: self.findTextDialog.close() def toolsFindConditionDialog(self, show): """Show or hide the non-modal conditional find dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findConditionDialog: dialogType = conditional.FindDialogType.findDialog self.findConditionDialog = (conditional.ConditionDialog( dialogType, _('Conditional Find'))) toolsFindConditionAct = self.allActions['ToolsFindCondition'] (self.findConditionDialog.dialogShown.connect( toolsFindConditionAct.setChecked)) else: self.findConditionDialog.loadTypeNames() self.findConditionDialog.show() else: self.findConditionDialog.close() def toolsFindReplaceDialog(self, show): """Show or hide the non-modal find and replace text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findReplaceDialog: self.findReplaceDialog = miscdialogs.FindReplaceDialog() toolsFindReplaceAct = self.allActions['ToolsFindReplace'] self.findReplaceDialog.dialogShown.connect( toolsFindReplaceAct.setChecked) else: self.findReplaceDialog.loadTypeNames() self.findReplaceDialog.show() else: self.findReplaceDialog.close() def toolsFilterTextDialog(self, show): """Show or hide the non-modal filter text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.filterTextDialog: self.filterTextDialog = miscdialogs.FindFilterDialog(True) toolsFilterTextAct = self.allActions['ToolsFilterText'] self.filterTextDialog.dialogShown.connect( toolsFilterTextAct.setChecked) self.filterTextDialog.selectAllText() self.filterTextDialog.show() else: self.filterTextDialog.close() def toolsFilterConditionDialog(self, show): """Show or hide the non-modal conditional filter dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.filterConditionDialog: dialogType = conditional.FindDialogType.filterDialog self.filterConditionDialog = (conditional.ConditionDialog( dialogType, _('Conditional Filter'))) toolsFilterConditionAct = ( self.allActions['ToolsFilterCondition']) (self.filterConditionDialog.dialogShown.connect( toolsFilterConditionAct.setChecked)) else: self.filterConditionDialog.loadTypeNames() self.filterConditionDialog.show() else: self.filterConditionDialog.close() def toolsGenOptions(self): """Set general user preferences for all files. """ oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] dialog = options.OptionDialog(globalref.genOptions, QApplication.activeWindow()) dialog.setWindowTitle(_('General Options')) if (dialog.exec_() == QDialog.Accepted and globalref.genOptions.modified): globalref.genOptions.writeFile() self.recentFiles.updateOptions() if globalref.genOptions['MinToSysTray']: self.createTrayIcon() elif self.trayIcon: self.trayIcon.hide() autoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] for control in self.localControls: for window in control.windowList: window.updateWinGenOptions() control.structure.undoList.setNumLevels() control.updateAll(False) if autoSaveMinutes != oldAutoSaveMinutes: control.resetAutoSave() def toolsCustomShortcuts(self): """Show dialog to customize keyboard commands. """ actions = self.activeControl.activeWindow.allActions dialog = miscdialogs.CustomShortcutsDialog(actions, QApplication.activeWindow()) dialog.exec_() def toolsCustomToolbars(self): """Show dialog to customize toolbar buttons. """ actions = self.activeControl.activeWindow.allActions dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars, QApplication.activeWindow()) dialog.exec_() def updateToolbars(self): """Update toolbars after changes in custom toolbar dialog. """ for control in self.localControls: for window in control.windowList: window.setupToolbars() def toolsCustomFonts(self): """Show dialog to customize fonts in various views. """ dialog = miscdialogs.CustomFontDialog(QApplication.activeWindow()) dialog.updateRequired.connect(self.updateCustomFonts) dialog.exec_() def toolsCustomColors(self): """Show dialog to customize GUI colors ans themes. """ self.colorSet.showDialog(QApplication.activeWindow()) def updateCustomFonts(self): """Update fonts in all windows based on a dialog signal. """ self.updateAppFont() for control in self.localControls: for window in control.windowList: window.updateFonts() control.printData.setDefaultFont() for control in self.localControls: control.updateAll(False) def updateAppFont(self): """Update application default font from settings. """ appFont = QFont(self.systemFont) appFontName = globalref.miscOptions['AppFont'] if appFontName: appFont.fromString(appFontName) QApplication.setFont(appFont) def formatSelectAll(self): """Select all text in any currently focused editor. """ try: QApplication.focusWidget().selectAll() except AttributeError: pass def helpViewBasic(self): """Display basic usage instructions. """ if not self.basicHelpView: path = self.findResourceFile('basichelp.html', 'doc', docPath) if not path: QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - basic help file not found')) return self.basicHelpView = helpview.HelpView(path, _('TreeLine Basic Usage'), globalref.toolIcons) self.basicHelpView.show() def helpViewFull(self): """Open a TreeLine file with full documentation. """ path = self.findResourceFile('documentation.trln', 'doc', docPath) if not path: QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - documentation file not found')) return self.createLocalControl(path, forceNewWindow=True) self.activeControl.filePathObj = pathlib.Path('documentation.trln') self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True win = self.activeControl.activeWindow win.rightTabs.setCurrentWidget(win.outputSplitter) def helpAbout(self): """ Display version info about this program. """ pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]]) textLines = [ _('TreeLine version {0}').format(__version__), _('written by {0}').format(__author__), '', _('Library versions:'), ' Python: {0}'.format(pyVersion), ' Qt: {0}'.format(qVersion()), ' PyQt: {0}'.format(PYQT_VERSION_STR), ' OS: {0}'.format(platform.platform()) ] dialog = miscdialogs.AboutDialog('TreeLine', textLines, QApplication.windowIcon(), QApplication.activeWindow()) dialog.exec_()
def __init__(self, _id, _viewer_id, *argv): if sys.platform.startswith( "darwin") and mp.current_process().name == "WebLCDs": import AppKit info = AppKit.NSBundle.mainBundle().infoDictionary( ) # @UndefinedVariable info["LSBackgroundOnly"] = "1" super(QtSingleApplication, self).__init__(*argv) self._id = _id self._viewer_id = _viewer_id self._activationWindow = None self._activateOnMessage = False self._outSocket = None self._isRunning = False self._server = None # we exclude the WebLCDs parallel process from participating any Artisan inter-app communication if mp.current_process().name != "WebLCDs": # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected(-1) if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') # Is there another viewer running? self._outSocketViewer = QLocalSocket() self._outSocketViewer.connectToServer(self._viewer_id) self._isRunningViewer = self._outSocketViewer.waitForConnected( -1) if self._isRunningViewer: self._outStreamViewer = QTextStream(self._outSocketViewer) self._outStreamViewer.setCodec('UTF-8') else: # app is running, we announce us as viewer app # First we remove existing servers of that name that might not have been properly closed as the server died QLocalServer.removeServer(self._viewer_id) self._outSocketViewer = None self._outStreamViewer = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.listen(self._viewer_id) self._server.newConnection.connect(self._onNewConnection) else: self._isRunningViewer = False # No, there isn't. # First we remove existing servers of that name that might not have been properly closed as the server died QLocalServer.removeServer(self._id) self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection)
class QSingleApplication(QApplication): messageReceived = pyqtSignal(str) def __init__(self, id, *argv): super(QSingleApplication, self).__init__(*argv) self._id = id self._activationWindow = None self._activateOnMessage = False self._server = None # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._outSocket.error.connect(self.handleError) self._isRunning = self._outSocket.waitForConnected() if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't. self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) def handleError(self, msg): print(msg) def server(self): return self._server def isRunning(self): return self._isRunning def id(self): return self._id def activationWindow(self): return self._activationWindow def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage def activateWindow(self): if not self._activationWindow: print("No registered ActivationWindow") return # Unfortunately this *doesn't* do much of any use, as it won't # bring the window to the foreground under KDE... sigh. self._activationWindow.setWindowState( self._activationWindow.windowState() & ~Qt.WindowMinimized) self._activationWindow.raise_() self._activationWindow.requestActivate() def sendMessage(self, msg, msecs=5000): if not self._outStream: return False self._outStream << msg << '' if not self._outSocket.waitForBytesWritten(msecs): raise RuntimeError("Bytes not written within %ss" % (msecs / 1000.)) def _onNewConnection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._onReadyRead) self._inSocket = self._server.nextPendingConnection() if not self._inSocket: return self._inStream = QTextStream(self._inSocket) self._inStream.setCodec('UTF-8') self._inSocket.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() def _onReadyRead(self): while True: msg = self._inStream.readLine() if not msg: break print("Message received") self.messageReceived.emit(msg) def removeServer(self): self._server.close() self._server.removeServer(self._id)
class QSingleApplication(QApplication): messageReceived = pyqtSignal(str) def __init__(self, *args, **kwargs): super(QSingleApplication, self).__init__(*args, **kwargs) appid = QApplication.applicationFilePath().lower().split("/")[-1] self._socketName = "qtsingleapp-" + appid print("socketName", self._socketName) self._activationWindow = None self._activateOnMessage = False self._socketServer = None self._socketIn = None self._socketOut = None self._running = False # 先尝试连接 self._socketOut = QLocalSocket(self) self._socketOut.connectToServer(self._socketName) self._socketOut.error.connect(self.handleError) self._running = self._socketOut.waitForConnected() if not self._running: # 程序未运行 self._socketOut.close() del self._socketOut self._socketServer = QLocalServer(self) self._socketServer.listen(self._socketName) self._socketServer.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) def handleError(self, message): print("handleError message: ", message) def isRunning(self): return self._running def activationWindow(self): return self._activationWindow def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage def activateWindow(self): if not self._activationWindow: return self._activationWindow.setWindowState( self._activationWindow.windowState() & ~Qt.WindowMinimized) self._activationWindow.raise_() self._activationWindow.activateWindow() def sendMessage(self, message, msecs=5000): if not self._socketOut: return False if not isinstance(message, bytes): message = str(message).encode() self._socketOut.write(message) if not self._socketOut.waitForBytesWritten(msecs): raise RuntimeError("Bytes not written within %ss" % (msecs / 1000.)) return True def _onNewConnection(self): if self._socketIn: self._socketIn.readyRead.disconnect(self._onReadyRead) self._socketIn = self._socketServer.nextPendingConnection() if not self._socketIn: return self._socketIn.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() def _onReadyRead(self): while 1: message = self._socketIn.readLine() if not message: break print("Message received: ", message) self.messageReceived.emit(message.data().decode()) def removeServer(self): self._socketServer.close() self._socketServer.removeServer(self._socketName)
def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(SOCKETNAME) if not ok: raise IPCError( "Error while removing server {}!".format(SOCKETNAME))
class MainWindow(QtWidgets.QMainWindow): """ A main window class. """ def __init__(self, *args, **kwargs): """ A function for connecting actions and creating a main window. """ super(MainWindow, self).__init__(*args, **kwargs) # absolute path to icon: path_to_main = os.path.abspath(os.getcwd()) self.icon_path = os.path.join(path_to_main, 'atomize/main', 'Icon.png') self.setWindowIcon(QIcon(self.icon_path)) #self.destroyed.connect(MainWindow._on_destroyed) # connect some actions to exit self.destroyed.connect( lambda: self._on_destroyed()) # connect some actions to exit # Load the UI Page uic.loadUi('atomize/main/gui/main_window.ui', self) # Design file # important attribures if len(sys.argv) > 1 and sys.argv[1] != '': # for bash option self.script = sys.argv[1] self.open_file(self.script) elif len(sys.argv) == 1: self.script = '' # for not opened script self.test_flag = 0 # flag for not running script if test is failed self.flag_opened_script_changed = 0 # flag for saving changes in the opened script self.path = os.path.join(path_to_main, 'atomize/tests') # Connection of different action to different Menus and Buttons self.tabwidget.tabBar().setTabTextColor(0, QColor(193, 202, 227)) self.tabwidget.tabBar().setTabTextColor(1, QColor(193, 202, 227)) self.button_open.clicked.connect(self.open_file_dialog) self.button_open.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); ; border-style: inset}" ) self.button_edit.clicked.connect(self.edit_file) self.button_edit.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); ; border-style: inset}" ) self.button_test.clicked.connect(self.test) self.button_test.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); ; border-style: inset}" ) self.button_reload.clicked.connect(self.reload) self.button_reload.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227); }\ QPushButton:pressed {background-color: rgb(211, 194, 78); ; border-style: inset}" ) self.button_start.clicked.connect(self.start_experiment) self.button_start.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); border-style: inset}" ) self.button_help.clicked.connect(self.help) self.button_help.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); border-style: inset}" ) self.button_quit.clicked.connect(lambda: self.quit()) self.button_quit.setStyleSheet( "QPushButton {border-radius: 4px; background-color: rgb(63, 63, 97);\ border-style: outset; color: rgb(193, 202, 227);}\ QPushButton:pressed {background-color: rgb(211, 194, 78); border-style: inset}" ) self.textEdit.setStyleSheet( "QPlainTextEdit {background-color: rgb(42, 42, 64); color: rgb(211, 194, 78); }\ QScrollBar:vertical {background-color: rgb(42, 42, 64);}") self.textEdit.textChanged.connect(self.save_edited_text) self.text_errors.setStyleSheet( "QPlainTextEdit {background-color: rgb(42, 42, 64); color: rgb(211, 194, 78); }" ) self.text_errors.setCenterOnScroll(True) self.text_errors.ensureCursorVisible() # Liveplot tab setting self.dockarea = DockArea() self.namelist = NameList(self) self.tab_liveplot.setStyleSheet( "background-color: rgb(42, 42, 64); color: rgb(211, 194, 78); ") self.gridLayout_tab_liveplot.setColumnMinimumWidth(0, 200) self.gridLayout_tab_liveplot.setColumnStretch(1, 2000) self.gridLayout_tab_liveplot.addWidget(self.namelist, 0, 0) self.gridLayout_tab_liveplot.setAlignment(self.namelist, QtConst.AlignLeft) self.gridLayout_tab_liveplot.addWidget(self.dockarea, 0, 1) #self.gridLayout_tab_liveplot.setAlignment(self.dockarea, QtConst.AlignRight) self.namelist.setStyleSheet( "background-color: rgb(42, 42, 64); color: rgb(211, 194, 78); border: 2px solid rgb(40, 30, 45)" ) self.namelist.namelist_view.setStyleSheet( "QListView::item:selected:active {background-color: rgb(63, 63, 97);\ color: rgb(211, 194, 78); } QListView::item:hover {background-color: rgb(48, 48, 75); }" ) self.namelist.namelist_view.setStyleSheet( "QMenu::item:selected {background-color: rgb(48, 48, 75); }") # Liveplot server settings self.server = QLocalServer() self.server.removeServer('LivePlot') self.server.listen('LivePlot') self.server.newConnection.connect(self.accept) self.bytes = bytearray() self.target_size = 0 self.meta = None self.insert_dock_right = True self.conns = [] self.shared_mems = [] signal.signal(signal.SIGINT, self.close) # configuration data path_config_file = os.path.join(path_to_main, 'atomize/config.ini') config = configparser.ConfigParser() config.read(path_config_file) # for running different processes using QProcess self.process = QtCore.QProcess(self) self.process_text_editor = QtCore.QProcess(self) self.process_python = QtCore.QProcess(self) # check where we are self.system = platform.system() if self.system == 'Windows': self.process_text_editor.setProgram( str(config['DEFAULT']['editorW'])) self.process.setProgram('python.exe') self.process_python.setProgram('python.exe') elif self.system == 'Linux': self.editor = str(config['DEFAULT']['editor']) if self.editor == 'nano' or self.editor == 'vi': self.process_text_editor.setProgram('xterm') else: self.process_text_editor.setProgram( str(config['DEFAULT']['editor'])) self.process.setProgram('python3') self.process_python.setProgram('python3') self.process.finished.connect(self.on_finished_checking) self.process_python.finished.connect(self.on_finished_script) ############################################## Liveplot Functions def close(self, sig=None, frame=None): #print('closing') for conn in self.conns: conn.close() for shm in self.shared_mems: shm.detach() self._on_destroyed() #QApplication.instance().exit() def accept(self): logging.debug('connection accepted') conn = self.server.nextPendingConnection() conn.waitForReadyRead() key = str(conn.read(36).decode()) memory = QSharedMemory() memory.setKey(key) memory.attach() logging.debug('attached to memory %s with size %s' % (key, memory.size())) #11-04-2021; Should be uncommented in case of problems #atexit.register(memory.detach) self.conns.append(conn) self.shared_mems.append(memory) conn.readyRead.connect(lambda: self.read_from(conn, memory)) conn.disconnected.connect(memory.detach) conn.write(b'ok') # noinspection PyNoneFunctionAssignment def read_from(self, conn, memory): logging.debug('reading data') self.meta = json.loads(conn.read(300).decode()) if self.meta['arrsize'] != 0: memory.lock() raw_data = memory.data() if raw_data != None: ba = raw_data[:self.meta['arrsize']] arr = np.frombuffer(memoryview(ba), dtype=self.meta['dtype']) memory.unlock() conn.write(b'ok') arr = arr.reshape(self.meta['shape']).copy() else: arr = None else: arr = None self.do_operation(arr) if conn.bytesAvailable(): self.read_from(conn, memory) def do_operation(self, arr=None): def clear(name): self.namelist[name].clear() def close(name): self.namelist[name].close() def remove(name): del self.namelist[name] meta = self.meta operation = meta['operation'] name = meta['name'] if name in self.namelist: pw = self.namelist[name] if pw.closed: pw.closed = False self.dockarea.addDock(pw) elif name == "*": if operation == 'clear': list(map(clear, list(self.namelist.keys()))) elif operation == 'close': list(map(close, list(self.namelist.keys()))) elif operation == 'remove': list(map(remove, list(self.namelist.keys()))) return else: if operation in ('clear', 'close', 'remove', 'none'): return pw = self.add_new_plot(meta['rank'], name) if operation == 'clear': pw.clear() elif operation == 'close': pw.close() elif operation == 'none': pass elif operation == 'remove': del self.namelist[name] elif operation == 'plot_y': start_step = meta['start_step'] label = meta['label'] if start_step is not None: x0, dx = start_step nx = len(arr) xs = np.linspace(x0, x0 + (nx - 1) * dx, nx) pw.plot(xs, arr, name=label, scatter='False') else: pw.plot(arr, name=label, scatter='False') elif operation == 'plot_xy': label = meta['label'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] scat = meta['Scatter'] taxis = meta['TimeAxis'] pw.plot(arr[0], arr[1], parametric=True, name=label, xname=xnam, xscale =xscal,\ yname=ynam, yscale =yscal, scatter=scat, timeaxis=taxis) elif operation == 'plot_z': start_step = meta['start_step'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] znam = meta['Zname'] zscal = meta['Z'] if start_step is not None: (x0, dx), (y0, dy) = start_step pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal,\ zname=znam, zscale =zscal) pw.setImage(arr, pos=(x0, y0), scale=(dx, dy), axes={ 'y': 0, 'x': 1 }) else: pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal,\ zname=znam, zscale =zscal) pw.setImage(arr, axes={'y': 0, 'x': 1}) elif operation == 'append_y': label = meta['label'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] scat = meta['Scatter'] taxis = meta['TimeAxis'] xs, ys = pw.get_data(label) new_ys = list(ys) new_ys.append(meta['value']) start_step = meta['start_step'] if start_step is not None: x0, dx = start_step nx = len(new_ys) xs = np.linspace(x0, x0 + (nx - 1) * dx, nx) pw.plot(xs, new_ys, name=label, xname=xnam, xscale =xscal, yname=ynam,\ yscale =yscal, scatter=scat, timeaxis=taxis) else: pw.plot(new_ys, name=label, xname=xnam, xscale =xscal, yname=ynam,\ yscale =yscal, scatter=scat, timeaxis=taxis) elif operation == 'append_xy': label = meta['label'] xs, ys = pw.get_data(label) xn, yn = meta['value'] new_xs = list(xs) new_xs.append(xn) new_ys = list(ys) new_ys.append(yn) pw.plot(new_xs, new_ys, parametric=True, name=label, scatter='False') elif operation == 'append_z': image = pw.get_data() if image is None: image = np.array([arr]) else: try: image = np.vstack((np.transpose(image), [arr])) except ValueError: image = np.array([arr]) start_step = meta['start_step'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] znam = meta['Zname'] zscal = meta['Z'] if start_step is not None: (x0, dx), (y0, dy) = start_step pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal,\ zname=znam, zscale =zscal) pw.setImage(image, pos=(x0, y0), scale=(dx, dy), axes={ 'y': 0, 'x': 1 }) else: pw.setAxisLabels(xname=xnam, xscale=xscal, yname=ynam, yscale=yscal) pw.setImage(image, axes={'y': 0, 'x': 1}) elif operation == 'label': pw.setTitle(meta['value']) def add_new_plot(self, rank, name): pw = widgets.get_widget(rank, name) self.add_plot(pw) self.namelist[name] = pw return pw def add_plot(self, pw): self.insert_dock_right = not self.insert_dock_right self.dockarea.addDock(pw, position=['bottom', 'bottom'][self.insert_dock_right]) #print(['bottom', 'right'][self.insert_dock_right]) #self.dockarea.moveDock(pw, 'above', self.dock_list[-1]) ## move d6 to stack on top of d4 ##################################################### def _on_destroyed(self): """ A function to do some actions when the main window is closing. """ self.process_python.close() def quit(self): """ A function to quit the programm """ self.process_python.terminate() sys.exit() #### #### QProcess: Destroyed while process ("python3") is still running. #### def start_experiment(self): """ A function to run an experimental script using python.exe. """ if self.script != '': stamp = os.stat(self.script).st_mtime else: self.text_errors.appendPlainText( 'No experimental script is opened') return self.test() exec_code = self.process.waitForFinished() if self.test_flag == 1: self.text_errors.appendPlainText( "Experiment cannot be started, since test is not passed") return # stop current function elif self.test_flag == 0 and exec_code == True: self.process_python.setArguments([self.script]) self.process_python.start() def message_box_clicked(self, btn): """ Message Box fow warning """ if btn.text() == "Discrad and Run Experiment": self.start_experiment() elif btn.text() == "Update Script": self.reload() else: return def test(self): """ A function to run a syntax check using pylint. """ if self.script != '': stamp = os.stat(self.script).st_mtime else: self.text_errors.appendPlainText( 'No experimental script is opened') return if stamp != self.cached_stamp and self.flag_opened_script_changed == 0: self.cached_stamp = stamp message = QMessageBox(self) # Message Box for warning of updated file message.setWindowTitle("Your script has been changed!") message.setStyleSheet( "QWidget { background-color : rgb(42, 42, 64); color: rgb(211, 194, 78); }" ) message.addButton( QtWidgets.QPushButton('Discrad and Run Experiment'), QtWidgets.QMessageBox.YesRole) message.addButton(QtWidgets.QPushButton('Update Script'), QtWidgets.QMessageBox.NoRole) message.setText("Your experimental script has been changed ") message.show() message.buttonClicked.connect( self.message_box_clicked ) # connect function clicked to button; get the button name return # stop current function #self.text_errors.appendPlainText("Testing... Please, wait!") #self.process.setArguments(['--errors-only', self.script]) self.process.setArguments([self.script, 'test']) self.process.start() def reload(self): """ A function to reload an experimental script. """ self.cached_stamp = os.stat(self.script).st_mtime text = open(self.script).read() self.textEdit.setPlainText(text) def on_finished_checking(self): """ A function to add the information about errors found during syntax checking to a dedicated text box in the main window of the programm. """ #text = self.process.readAllStandardOutput().data().decode() #if text == '': # self.text_errors.appendPlainText("No errors are found!") #else: # self.text_errors.appendPlainText(text) # self.text_errors.verticalScrollBar().setValue(self.text_errors.verticalScrollBar().maximum()) # Version for real tests text = self.process.readAllStandardOutput().data().decode() text_errors_script = self.process.readAllStandardError().data().decode( ) if text_errors_script == '': # if text == '' and text_errors_script == '': self.text_errors.appendPlainText("No errors are found") self.test_flag = 0 elif text_errors_script != '': self.test_flag = 1 self.text_errors.appendPlainText(text_errors_script) #self.text_errors.verticalScrollBar().setValue(self.text_errors.verticalScrollBar().maximum()) def on_finished_script(self): """ A function to add the information about errors found during syntax checking to a dedicated text box in the main window of the programm. """ text = self.process_python.readAllStandardOutput().data().decode() text_errors_script = self.process_python.readAllStandardError().data( ).decode() if text_errors_script == '': #if text == '' and text_errors_script == '': self.text_errors.appendPlainText("Script done!") elif text_errors_script != '': self.text_errors.appendPlainText("Script done!") self.text_errors.appendPlainText(text_errors_script) #self.text_errors.verticalScrollBar().setValue(self.text_errors.verticalScrollBar().maximum()) def help(self): """ A function to open a documentation """ pass def edit_file(self): """ A function to open an experimental script in a text editor. """ if self.system == 'Linux': if self.editor == 'nano': self.process_text_editor.setArguments( ['-e', 'nano', self.script]) elif self.editor == 'vi': self.process_text_editor.setArguments( ['-e', 'vi', self.script]) else: self.process_text_editor.setArguments([self.script]) elif self.system == 'Windows': self.process_text_editor.setArguments([self.script]) self.process_text_editor.start() def open_file(self, filename): """ A function to open an experimental script. :param filename: string """ self.cached_stamp = os.stat(filename).st_mtime text = open(filename).read() self.path = os.path.dirname( filename) # for memorizing the path to the last used folder self.script = filename self.textEdit.setPlainText(text) def save_file(self, filename): """ A function to save a new experimental script. :param filename: string """ with open(filename, 'w') as file: file.write(self.textEdit.toPlainText()) self.cached_stamp = os.stat(filename).st_mtime self.script = filename def open_file_dialog(self): """ A function to open a new window for choosing an experimental script. """ filedialog = QFileDialog(self, 'Open File', directory = self.path, filter ="python (*.py)",\ options=QtWidgets.QFileDialog.DontUseNativeDialog) # use QFileDialog.DontUseNativeDialog to change directory filedialog.setStyleSheet( "QWidget { background-color : rgb(42, 42, 64); color: rgb(211, 194, 78);}" ) filedialog.setFileMode(QtWidgets.QFileDialog.AnyFile) filedialog.fileSelected.connect(self.open_file) filedialog.show() def save_file_dialog(self): """ A function to open a new window for choosing a name for a new experimental script. """ filedialog = QFileDialog(self, 'Save File', directory = self.path, filter ="python (*.py)",\ options=QtWidgets.QFileDialog.DontUseNativeDialog) filedialog.setAcceptMode(QFileDialog.AcceptSave) # use QFileDialog.DontUseNativeDialog to change directory filedialog.setStyleSheet( "QWidget { background-color : rgb(42, 42, 64); color: rgb(211, 194, 78);}" ) filedialog.setFileMode(QtWidgets.QFileDialog.AnyFile) filedialog.fileSelected.connect(self.save_file) filedialog.show() def save_edited_text(self): if self.script: self.flag_opened_script_changed = 1 with open(self.script, 'w') as file: file.write(self.textEdit.toPlainText()) else: self.flag_opened_script_changed = 1 if self.textEdit.toPlainText( ) != '': # save file dialog will be opened after at least one character is added self.save_file_dialog() @QtCore.pyqtSlot(str) def add_error_message(self, data): """ A function for adding an error message to a dedicated text box in the main window of the programm; This function runs when Helper.changedSignal.emit(str) is emitted. :param data: string """ self.text_errors.appendPlainText(str(data)) if data == 'Script stopped': path_to_main = os.path.abspath(os.getcwd()) lib_path = os.path.join(path_to_main, 'atomize/general_modules', 'libspinapi.so') lib_path2 = os.path.join(path_to_main, 'atomize/general_modules', 'spinapi64.dll') if os.path.exists(lib_path) == False and os.path.exists( lib_path2) == False: self.process_python.close() else: # check on windows?! import atomize.device_modules.PB_ESR_500_pro as pb_pro pb = pb_pro.PB_ESR_500_Pro() pb.pulser_stop() # AWG #hCard = spcm_hOpen (create_string_buffer (b'/dev/spcm0')) #if hCard == None: # sys.stdout.write("no card found...\n") # exit () #spcm_dwSetParam_i32 (hCard, SPC_M2CMD, M2CMD_CARD_STOP) # clean up #spcm_vClose (hCard) ### self.process_python.close()
class SingleApplication(QtWidgets.QApplication): ''' Inheriting from QApplication, executing main App instead. Watching whether the app is already running. If so, quit befor execution. ''' messageReceived = QtCore.pyqtSignal(str) def __init__(self, id, *argv): super(SingleApplication, self).__init__(*argv) self._id = id self._activationWindow = None self._activateOnMessage = False # Check if another instance is running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected() if self._isRunning: self._outStream = QtCore.QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.removeServer(self._id) # if existing after crash-exit self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection) def isRunning(self): return self._isRunning def id(self): return self._id def activationWindow(self): return self._activationWindow def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage def activateWindow(self): if not self._activationWindow: return self._activationWindow.setWindowState( self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized) self._activationWindow.show() self._activationWindow.activateWindow() def sendMessage(self, msg): if not self._outStream: return False self._outStream << msg << '\n' self._outStream.flush() return self._outSocket.waitForBytesWritten() def _onNewConnection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._onReadyRead) self._inSocket = self._server.nextPendingConnection() if not self._inSocket: return self._inStream = QtCore.QTextStream(self._inSocket) self._inStream.setCodec('UTF-8') self._inSocket.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() def _onReadyRead(self): while True: msg = self._inStream.readLine() if not msg: break self.messageReceived.emit(msg)
def __init__(self, parent = None): QDialog.__init__(self, parent) # If a Nemu instance is already running, this is as far as we go self.connectToRunning() self.holdOpen = False self.menuItems = [] self.allItems = [] self.favorites = [] self.currentItem = None self.menuFile = os.path.expanduser('~/.nemu/menu') self.favoritesFile = os.path.expanduser('~/.nemu/favorites') # NOTE: If you change this, also update migrate-settings self.settingsFile = os.path.expanduser('~/.nemu/settings') self.initSettings() self.server = QLocalServer() self.server.newConnection.connect(self.handleConnection) QLocalServer.removeServer('nemuSocket') self.server.listen('nemuSocket') self.configDir = os.path.expanduser('~/.nemu') if not os.path.isdir(self.configDir): os.mkdir(self.configDir) self.menuItems = self.loadConfig(self.menuFile, self.menuItems) self.favorites = self.loadConfig(self.favoritesFile, self.favorites) # Don't load directly into self.settings so we can add new default values as needed try: tempSettings = self.loadConfig(self.settingsFile, self.settings) for key, value in tempSettings.items(): self.settings[key] = value except SystemError: print('ERROR: Failed to load settings. You may need to run migrate-settings.') raise # This should never happen, but unfortunately bugs do, so clean up orphaned items. # We need to do this because these items won't show up in the UI, but may interfere with # merges if they duplicate something that is being merged in. self.menuItems[:] = [i for i in self.menuItems if i.parent == None or i.parent in self.menuItems] # Look for broken icon paths needSave = False for i in self.menuItems + self.favorites: if not os.path.exists(i.icon): i.findIcon() needSave = True if needSave: self.saveMenu() for i in self.menuItems: if not hasattr(i, 'imported'): i.imported = False self.setupUI() self.setContextMenuPolicy(Qt.ActionsContextMenu) self.createMenu(self) self.refresh(False) if len(self.menuItems) == 0: self.firstRun() self.show() self.keepaliveTimer = QTimer(self) self.keepaliveTimer.timeout.connect(self.keepalive) self.keepaliveTimer.start(60000)
class QSingleApplication(QApplication): # https://github.com/PyQt5/PyQt/blob/master/Demo/Lib/Application.py messageReceived = pyqtSignal(str) def __init__(self, *args, **kwargs): # logger.debug("{} init...".format(self.__class__.__name__)) super(QSingleApplication, self).__init__(*args, **kwargs) # 使用路径作为appid # appid = QApplication.applicationFilePath().lower().split("/")[-1] # 使用固定的appid appid = "SHL_LHX_Wallet" # logger.debug("{} appid name: {}".format(self.__class__.__name__,appid)) # self._socketName = "qtsingleapp-" + appid self._socketName = appid # print("socketName", self._socketName) self._activationWindow = None self._activateOnMessage = False self._socketServer = None self._socketIn = None self._socketOut = None self._running = False # 先尝试连接 self._socketOut = QLocalSocket(self) self._socketOut.connectToServer(self._socketName) self._socketOut.error.connect(self.handleError) self._running = self._socketOut.waitForConnected() # logger.debug("local socket connect error: {}".format(self._socketOut.errorString())) if not self._running: # 程序未运行 # logger.debug("start init QLocalServer.") self._socketOut.close() del self._socketOut self._socketServer = QLocalServer(self) # 设置连接权限 self._socketServer.setSocketOptions(QLocalServer.UserAccessOption) self._socketServer.listen(self._socketName) self._socketServer.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) # logger.debug("QLocalServer finished init.") def handleError(self, message): print("handleError message: ", message) def isRunning(self): return self._running def activationWindow(self): return self._activationWindow def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage def activateWindow(self): if not self._activationWindow: return self._activationWindow.setWindowState( self._activationWindow.windowState() & ~Qt.WindowMinimized) self._activationWindow.raise_() self._activationWindow.activateWindow() # 增加了显示功能。 # self._activationWindow.show() def sendMessage(self, message, msecs=5000): if not self._socketOut: return False if not isinstance(message, bytes): message = str(message).encode() self._socketOut.write(message) if not self._socketOut.waitForBytesWritten(msecs): raise RuntimeError("Bytes not written within %ss" % (msecs / 1000.)) return True def _onNewConnection(self): if self._socketIn: self._socketIn.readyRead.disconnect(self._onReadyRead) self._socketIn = self._socketServer.nextPendingConnection() if not self._socketIn: return self._socketIn.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() def _onReadyRead(self): while 1: message = self._socketIn.readLine() if not message: break # print("Message received: ", message) self.messageReceived.emit(message.data().decode()) def removeServer(self): if self._socketServer is not None: self._socketServer.close() self._socketServer.removeServer(self._socketName)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setStyleSheet("background-color: rgb(24, 25, 26); color: rgb(255, 170, 0); ") self.setWindowTitle("Liveplot - Plotting dashboard!") self.setWindowIcon(QIcon('icon.ico')) self.dockarea = DockArea() self.setCentralWidget(self.dockarea) self.namelist = NameList(self) self.addDockWidget(QtConst.LeftDockWidgetArea, self.namelist) self.server = QLocalServer() self.server.removeServer('LivePlot') self.server.listen('LivePlot') self.server.newConnection.connect(self.accept) self.bytes = bytearray() self.target_size = 0 self.meta = None self.insert_dock_right = True self.conns = [] self.shared_mems = [] signal.signal(signal.SIGINT, self.close) def close(self, sig=None, frame=None): print('closing') for conn in self.conns: conn.close() for shm in self.shared_mems: shm.detach() QApplication.instance().exit() def accept(self): logging.debug('connection accepted') conn = self.server.nextPendingConnection() conn.waitForReadyRead() key = str(conn.read(36).decode()) memory = QSharedMemory() memory.setKey(key) memory.attach() logging.debug('attached to memory %s with size %s'%(key, memory.size())) atexit.register(memory.detach) self.conns.append(conn) self.shared_mems.append(memory) conn.readyRead.connect(lambda: self.read_from(conn, memory)) conn.disconnected.connect(memory.detach) conn.write(b'ok') # noinspection PyNoneFunctionAssignment def read_from(self, conn, memory): logging.debug('reading data') self.meta = json.loads(conn.read(300).decode()) if self.meta['arrsize'] != 0: memory.lock() ba = memory.data()[0:self.meta['arrsize']] arr = np.frombuffer(memoryview(ba)) memory.unlock() conn.write(b'ok') arr = arr.reshape(self.meta['shape']).copy() else: arr = None self.do_operation(arr) if conn.bytesAvailable(): self.read_from(conn, memory) # if not self.target_size: # self.meta = conn._socket.recv_json() # self.target_size = self.meta['arrsize'] # if self.target_size > 0: # n = self.target_size - len(self.bytes) # data = bytearray(conn.read(n)) # self.bytes.extend(data) # if len(self.bytes) == self.target_size: # self.process_bytes() # if conn.bytesAvailable(): # self.read_from(conn) # # def process_bytes(self): # self.target_size = 0 # if len(self.bytes) > 0: # arr = np.frombuffer(buffer(self.bytes), dtype=self.meta['dtype']) # try: # arr.resize(self.meta['shape']) # except ValueError: # arr = arr.reshape(self.meta['shape']) # else: # arr = None # self.bytes = bytearray() # self.do_operation(arr) def do_operation(self, arr=None): def clear(name): self.namelist[name].clear() def close(name): self.namelist[name].close() def remove(name): del self.namelist[name] meta = self.meta operation = meta['operation'] name = meta['name'] if name in self.namelist: pw = self.namelist[name] if pw.closed: pw.closed = False self.dockarea.addDock(pw) elif name == "*": if operation == 'clear': list(map(clear, list(self.namelist.keys()))) elif operation == 'close': list(map(close, list(self.namelist.keys()))) elif operation == 'remove': list(map(remove, list(self.namelist.keys()))) return else: if operation in ('clear', 'close', 'remove','none'): return pw = self.add_new_plot(meta['rank'], name) if operation == 'clear': pw.clear() elif operation == 'close': pw.close() elif operation == 'none': pass elif operation == 'remove': del self.namelist[name] elif operation == 'plot_y': start_step = meta['start_step'] label = meta['label'] if start_step is not None: x0, dx = start_step nx = len(arr) xs = np.linspace(x0, x0 + (nx - 1)*dx, nx) pw.plot(xs, arr, name=label) else: pw.plot(arr, name=label) elif operation == 'plot_xy': label = meta['label'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] scat = meta['Scatter'] pw.plot(arr[0], arr[1], parametric=True, name=label, xname=xnam, xscale =xscal, yname=ynam, yscale =yscal, scatter=scat) elif operation == 'plot_z': start_step = meta['start_step'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] znam = meta['Zname'] zscal = meta['Z'] if start_step is not None: (x0, dx), (y0, dy) = start_step pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal, zname=znam, zscale =zscal) pw.setImage(arr, pos=(x0, y0), scale=(dx, dy), axes={'y':0, 'x':1}) else: pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal, zname=znam, zscale =zscal) pw.setImage(arr, axes={'y':0, 'x':1}) elif operation == 'append_y': label = meta['label'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] xs, ys = pw.get_data(label) new_ys = list(ys) new_ys.append(meta['value']) start_step = meta['start_step'] if start_step is not None: x0, dx = start_step nx = len(new_ys) xs = np.linspace(x0, x0 + (nx - 1)*dx, nx) pw.plot(xs, new_ys, name=label, xname=xnam, xscale =xscal, yname=ynam, yscale =yscal) else: pw.plot(new_ys, name=label, xname=xnam, xscale =xscal, yname=ynam, yscale =yscal) elif operation == 'append_xy': label = meta['label'] xs, ys = pw.get_data(label) xn, yn = meta['value'] new_xs = list(xs) new_xs.append(xn) new_ys = list(ys) new_ys.append(yn) pw.plot(new_xs, new_ys, parametric=True, name=label) elif operation == 'append_z': image = pw.get_data() if image is None: image = np.array([arr]) else: try: image = np.vstack((np.transpose(image), [arr])) except ValueError: image = np.array([arr]) start_step = meta['start_step'] xnam = meta['Xname'] xscal = meta['X'] ynam = meta['Yname'] yscal = meta['Y'] znam = meta['Zname'] zscal = meta['Z'] if start_step is not None: (x0, dx), (y0, dy) = start_step pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal, zname=znam, zscale =zscal) pw.setImage(image, pos=(x0, y0), scale=(dx, dy), axes={'y':0, 'x':1}) else: pw.setAxisLabels(xname=xnam, xscale =xscal, yname=ynam, yscale =yscal) pw.setImage(image, axes={'y':0, 'x':1}) elif operation == 'label': pw.setTitle(meta['value']) def add_new_plot(self, rank, name): pw = widgets.get_widget(rank, name) self.add_plot(pw) self.namelist[name] = pw return pw def add_plot(self, pw): self.insert_dock_right = not self.insert_dock_right self.dockarea.addDock(pw, position=['bottom', 'right'][self.insert_dock_right]) def sizeHint(self): return QSize(1000, 600)
class QtSingleApplication(QApplication): """ Adapted from https://stackoverflow.com/a/12712362/11038610 Copyright 2013 Johan Råde Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ message_received_event = pyqtSignal(str) def __init__(self, id, *argv): super().__init__(*argv) self._id = id # Is there another instance running? self._outSocket = QLocalSocket() self._outSocket.connectToServer(self._id) self._isRunning = self._outSocket.waitForConnected() if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) self._outStream.setCodec('UTF-8') else: # No, there isn't. self._outSocket = None self._outStream = None self._inSocket = None self._inStream = None self._server = QLocalServer() self._server.removeServer(self._id) self._server.listen(self._id) self._server.newConnection.connect(self._onNewConnection) def isRunning(self): return self._isRunning def id(self): return self._id def sendMessage(self, msg): if not self._outStream: return False self._outStream << msg << '\n' self._outStream.flush() return self._outSocket.waitForBytesWritten() def _onNewConnection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._onReadyRead) self._inSocket = self._server.nextPendingConnection() if not self._inSocket: return self._inStream = QTextStream(self._inSocket) self._inStream.setCodec('UTF-8') self._inSocket.readyRead.connect(self._onReadyRead) def _onReadyRead(self): while True: msg = self._inStream.readLine() if not msg: break self.message_received_event.emit(msg)
def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(self._socketname) if not ok: raise Error("Error while removing server {}!".format(self._socketname))
class QSingleApplication(QApplication): messageReceived = pyqtSignal(str) def __init__(self, name, *args, **kwargs): super(QSingleApplication, self).__init__(*args, **kwargs) self._socketName = name self._activationWindow = None self._socketServer = None self._socketIn = None self._socketOut = None self._running = False # 先尝试连接 self._socketOut = QLocalSocket(self) self._socketOut.connectToServer(self._socketName) self._socketOut.error.connect(self.handleError) self._running = self._socketOut.waitForConnected() if not self._running: # 程序未运行 self._socketOut.close() del self._socketOut # 创建本地server self._socketServer = QLocalServer(self) self._socketServer.listen(self._socketName) self._socketServer.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) def handleError(self, message): print("handleError message: ", message) def isRunning(self): return self._running def setActivationWindow(self, activationWindow): # 设置当前窗口 self._activationWindow = activationWindow def activateWindow(self): # 激活当前窗口 try: self._activationWindow.setWindowState( self._activationWindow.windowState() & ~Qt.WindowMinimized) #self._activationWindow.raise_() # 提升窗口到最上面 self._activationWindow.showNormal() self._activationWindow.activateWindow() except Exception as e: print(e) def sendMessage(self, message, msecs=5000): if not self._socketOut: return False if not isinstance(message, bytes): message = str(message).encode() self._socketOut.write(message) if not self._socketOut.waitForBytesWritten(msecs): raise Exception("Bytes not written within %ss" % (msecs / 1000.)) return True def _onNewConnection(self): if self._socketIn: self._socketIn.readyRead.disconnect(self._onReadyRead) self._socketIn = self._socketServer.nextPendingConnection() if not self._socketIn: return self._socketIn.readyRead.connect(self._onReadyRead) def _onReadyRead(self): while 1: message = self._socketIn.readLine() if not message: break if message == b'show': self.activateWindow() self.messageReceived.emit(message.data().decode()) def removeServer(self): self._socketServer.close() self._socketServer.removeServer(self._socketName)
def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(self._socketname) if not ok: raise Error("Error while removing server {}!".format( self._socketname))
def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(SOCKETNAME) if not ok: raise IPCError("Error while removing server {}!".format( SOCKETNAME))