def f_main(): """主程序,保证程序单实例运行""" app = QtWidgets.QApplication(sys.argv) app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) servername = "TransTools" socket = QLocalSocket() socket.connectToServer(servername) if socket.waitForConnected(500): # 程序只允许单例运行 showmsg("程序已运行!") return(app.quit()) # 没有实例运行,创建服务器 localServer = QLocalServer() localServer.listen(servername) try: main = Main() main.show() sys.exit(app.exec_()) except Exception as e: showmsg(str(e), type=QMessageBox.Critical) finally: localServer.close()
def main(): # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("AirMemo_appid") app = QApplication(sys.argv) serverName = 'rest_clock' socket = QLocalSocket() socket.connectToServer(serverName) # 如果连接成功,表明server已经存在,当前已有实例在运行 if socket.waitForConnected(500): sys.exit(app.quit()) # 没有实例运行,创建服务器 localServer = QLocalServer() localServer.listen(serverName) try: set_app(app) tray = clock_tray() timeout_win = Ui_Timeout(tray) settings_win = Ui_Settings(tray) tran_win = TranSignalWidget() tran_win.showSignal.connect(timeout_win.show) win_dict = {"timeout_win": timeout_win, 'settings_win': settings_win} tray.set_menu(win_dict) set_aps(tran_win) timeout_win.show() TIME_MISSION.set_aps_mission() MYAPS.start() logging.debug('APS jobs {}'.format(MYAPS.get_jobs())) sys.exit(app.exec_()) finally: localServer.close()
def main(): import sys app = QApplication(sys.argv) translator = QTranslator() locale = QLocale.system().name() translateFile = os.path.join(BASEDIR, 'i18n\\translations', '{}.qm'.format(locale)) if translator.load(translateFile): app.installTranslator(translator) # QApplication.setStyle(QStyleFactory.create('Fusion')) if boolean(conf.value('General/LargeFont')): font = QFont('Courier New', 14) app.setFont(font) serverName = 'Tafor' socket = QLocalSocket() socket.connectToServer(serverName) # 如果连接成功,表明server已经存在,当前已有实例在运行 if socket.waitForConnected(500): return(app.quit()) # 没有实例运行,创建服务器 localServer = QLocalServer() localServer.listen(serverName) try: window = MainWindow() window.show() sys.exit(app.exec_()) except Exception as e: logger.error(e, exc_info=True) finally: localServer.close()
def main(): # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("AirMemo_appid") app = QApplication(sys.argv) serverName = 'AirMemo_client' socket = QLocalSocket() socket.connectToServer(serverName) # 如果连接成功,表明server已经存在,当前已有实例在运行 if socket.waitForConnected(500): sys.exit(app.quit()) # 没有实例运行,创建服务器 localServer = QLocalServer() localServer.listen(serverName) try: setApp(app) link_db(config.LDB_FILENAME) tray = AirTray() mainWindow = Ui_MainWindow(tray) setting_win = Ui_Settings(tray) dict = {'main_win': mainWindow, 'setting_win': setting_win} tray.set_menu(dict) tray.show() # mainWindow.show() setting_win.show() sys.exit(app.exec_()) finally: localServer.close()
class SingleApplication(QApplication): messageAvailable = pyqtSignal(type(u'')) def __init__(self, argv, key): QApplication.__init__(self, argv) self._key = key self._timeout = 1000 socket = QLocalSocket(self) socket.connectToServer(self._key) if socket.waitForConnected(self._timeout): self._isRunning = True socket.abort() return socket.abort() self._isRunning = False self._server = QLocalServer(self) self._server.newConnection.connect(self.__onNewConnection) self._server.listen(self._key) self.aboutToQuit.connect(self.__onAboutToQuit) def __onAboutToQuit(self): if self._server: self._server.close() self._server = None def __onNewConnection(self): socket = self._server.nextPendingConnection() if socket.waitForReadyRead(self._timeout): self.messageAvailable.emit(socket.readAll().data().decode('utf-8')) socket.disconnectFromServer() else: pass def isRunning(self): return self._isRunning def sendMessage(self, message): assert (self._isRunning) if self.isRunning(): socket = QLocalSocket(self) socket.connectToServer(self._key, QIODevice.WriteOnly) if not socket.waitForConnected(self._timeout): return False socket.write(message.encode('utf-8')) if not socket.waitForBytesWritten(self._timeout): return False socket.disconnectFromServer() return True return False
class Server(QObject): dataReceived = pyqtSignal(list) quit = pyqtSignal() def __init__(self): super().__init__() self.conn = None self.server = None 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 close(self): self.server.close() def notify(self): logger.info("1 new conn") # WARNING: when multiple connections, each will overwrite previous! self.conn = self.server.nextPendingConnection() self.conn.readyRead.connect(self.receiveData) self.conn.disconnected.connect(self.conn.deleteLater) def receiveData(self): logger.info("waits for data") ins = QDataStream(self.conn) ins.setVersion(QDataStream.Qt_5_0) if ins.atEnd(): return argv = ins.readQVariant() logger.info("reads '%s'", str(argv)) # Must be setted up on 'show' action. Move from beginning to appropriate. action.search_dst_window() self.dataReceived.emit(argv)
class Server(QObject): dataReceived = pyqtSignal(list) quit = pyqtSignal() def __init__(self): super().__init__() self.conn = None self.server = None 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 close(self): self.server.close() def notify(self): logger.info("1 new conn") # WARNING: when multiple connections, each will overwrite previous! self.conn = self.server.nextPendingConnection() self.conn.readyRead.connect(self.receiveData) self.conn.disconnected.connect(self.conn.deleteLater) def receiveData(self): logger.info("waits for data") ins = QDataStream(self.conn) ins.setVersion(QDataStream.Qt_5_0) if ins.atEnd(): return argv = ins.readQVariant() logger.info("reads '%s'", str(argv)) # Must be setted up on 'show' action. Move from beginning to appropriate. action.search_dst_window() self.dataReceived.emit(argv)
class IDE(QMainWindow): """This class is like the Sauron's Ring: One ring to rule them all, One ring to find them, One ring to bring them all and in the darkness bind them. This Class knows all the containers, and its know by all the containers, but the containers don't need to know between each other, in this way we can keep a better api without the need to tie the behaviour between the widgets, and let them just consume the 'actions' they need.""" ############################################################################### # SIGNALS # # goingDown() ############################################################################### __IDESERVICES = {} __IDECONNECTIONS = {} __IDESHORTCUTS = {} __IDEBARCATEGORIES = {} __IDEMENUS = {} __IDETOOLBAR = {} # CONNECTIONS structure: # ({'target': service_name, 'signal_name': string, 'slot': function_obj},) # On modify add: {connected: True} __instance = None __created = False MessageStatusChanged = pyqtSignal(str) goingDown = pyqtSignal() # # ns_preferences_editor_font = pyqtSignal() # ns_preferences_editor_showTabsAndSpaces = pyqtSignal() # ns_preferences_editor_showIndentationGuide = pyqtSignal() # ns_preferences_editor_indent = pyqtSignal() # ns_preferences_editor_marginLine = pyqtSignal()#podría tener un argumento # ns_preferences_editor_showLineNumbers = pyqtSignal() # ns_preferences_editor_showMigrationTips = pyqtSignal() # ns_preferences_editor_checkStyle = pyqtSignal() # ns_preferences_editor_errors = pyqtSignal() # ds_lastSession_projects = pyqtSignal() # ds_lastSession_openedFiles = pyqtSignal() # ds_lastSession_currentFile = pyqtSignal() # ds_lastSession_recentFiles = pyqtSignal() # ns_preferences_editor_bookmarks = pyqtSignal() # ns_preferences_editor_breakpoints = pyqtSignal() # ns_window_maximized = pyqtSignal() # ns_preferences_general_loadFiles = pyqtSignal() # ns_preferences_general_activatePlugins = pyqtSignal() # ns_preferences_general_notifyUpdates = pyqtSignal() # ns_preferences_general_showStartPage = pyqtSignal(bool) # ns_preferences_general_confirmExit = pyqtSignal(bool) # ns_preferences_general_workspace = pyqtSignal() ns_preferences_general_supportedExtensions = pyqtSignal("QStringList") #ns_preferences_general_notification_position = pyqtSignal(int) #... ns_preferences_general_loadFiles = pyqtSignal(bool)# dato: 'True' ns_preferences_general_activatePlugins = pyqtSignal(bool)# dato: 'True' ns_preferences_general_notifyUpdates = pyqtSignal(bool)# dato: 'True' ns_preferences_general_showStartPage = pyqtSignal(bool)# dato: 'True' ns_preferences_general_confirmExit = pyqtSignal(bool)# dato: 'True' ns_preferences_general_workspace = pyqtSignal(str)# dato: '' #ns_preferences_general_supportedExtensions = pyqtSignal(list)# dato: '['.py', '.pyw', '.html', '.jpg','.png', '.ui', '.css', '.json', '.js', '.ini']' ns_preferences_general_notification_position = pyqtSignal(int)# dato: '0' ns_preferences_general_notification_color = pyqtSignal(str)# dato: '#000' ns_pythonPath = pyqtSignal(str)# dato: 'D:\Python34\python.exe' ns_executionOptions = pyqtSignal(str)# dato: '' ns_Show_Code_Nav = pyqtSignal(str)# dato: 'Ctrl+3' ns_Follow_mode = pyqtSignal(str)# dato: 'Ctrl+F10' ns_Change_Tab = pyqtSignal(str)# dato: 'Ctrl+PgDown' ns_Change_Tab_Reverse = pyqtSignal(str)# dato: 'Ctrl+PgUp' ns_Close_file = pyqtSignal(str)# dato: 'Ctrl+W' ns_Close_Split = pyqtSignal(str)# dato: 'Shift+F9' ns_Comment = pyqtSignal(str)# dato: 'Ctrl+G' ns_Complete_Declarations = pyqtSignal(str)# dato: 'Alt+Return' ns_copy = pyqtSignal(str)# dato: 'Ctrl+C' ns_History_Copy = pyqtSignal(str)# dato: 'Ctrl+Alt+C' ns_New_project = pyqtSignal(str)# dato: 'Ctrl+Shift+N' ns_New_file = pyqtSignal(str)# dato: 'Ctrl+N' ns_cut = pyqtSignal(str)# dato: 'Ctrl+X' ns_Debug = pyqtSignal(str)# dato: 'F7' ns_Duplicate = pyqtSignal(str)# dato: 'Ctrl+R' ns_Run_file = pyqtSignal(str)# dato: 'Ctrl+F6' ns_Run_project = pyqtSignal(str)# dato: 'F6' ns_expand_file_combo = pyqtSignal(str)# dato: 'Ctrl+Tab' ns_expand_symbol_combo = pyqtSignal(str)# dato: 'Ctrl+2' ns_Find = pyqtSignal(str)# dato: 'Ctrl+F' ns_Find_replace = pyqtSignal(str)# dato: 'Ctrl+H' ns_Find_in_files = pyqtSignal(str)# dato: 'Ctrl+L' ns_Find_next = pyqtSignal(str)# dato: 'Ctrl+F3' ns_Find_previous = pyqtSignal(str)# dato: 'Shift+F3' ns_Find_with_word = pyqtSignal(str)# dato: 'Ctrl+Shift+F' ns_Full_screen = pyqtSignal(str)# dato: 'Ctrl+F11' ns_Go_to_definition = pyqtSignal(str)# dato: 'Ctrl+Return' ns_Hide_all = pyqtSignal(str)# dato: 'F11' ns_Hide_editor = pyqtSignal(str)# dato: 'F3' ns_Hide_explorer = pyqtSignal(str)# dato: 'F2' ns_Hide_misc = pyqtSignal(str)# dato: 'F4' ns_Highlight_Word = pyqtSignal(str)# dato: 'Ctrl+Down' ns_Import = pyqtSignal(str)# dato: 'Ctrl+I' ns_Indent_less = pyqtSignal(str)# dato: 'Shift+Tab' ns_Indent_more = pyqtSignal(str)# dato: 'Tab' ns_Add_Bookmark_or_Breakpoint = pyqtSignal(str)# dato: 'Ctrl+B' ns_Title_comment = pyqtSignal(str)# dato: '' ns_Horizontal_line = pyqtSignal(str)# dato: '' ns_Move_down = pyqtSignal(str)# dato: 'Alt+Down' ns_Move_up = pyqtSignal(str)# dato: 'Alt+Up' ns_Move_Tab_to_left = pyqtSignal(str)# dato: 'Ctrl+Shift+9' ns_Move_Tab_to_right = pyqtSignal(str)# dato: 'Ctrl+Shift+0' ns_Navigate_back = pyqtSignal(str)# dato: 'Alt+Left' ns_Navigate_forward = pyqtSignal(str)# dato: 'Alt+Right' ns_Open_file = pyqtSignal(str)# dato: 'Ctrl+O' ns_Open_project = pyqtSignal(str)# dato: 'Ctrl+Shift+O' ns_Open_recent_closed = pyqtSignal(str)# dato: 'Ctrl+Shift+T' ns_paste = pyqtSignal(str)# dato: 'Ctrl+V' ns_History_Paste = pyqtSignal(str)# dato: 'Ctrl+Alt+V' ns_Print_file = pyqtSignal(str)# dato: 'Ctrl+P' ns_Redo = pyqtSignal(str)# dato: 'Ctrl+Y' ns_Reload_file = pyqtSignal(str)# dato: 'F5' ns_Remove_line = pyqtSignal(str)# dato: 'Ctrl+E' ns_Save_file = pyqtSignal(str)# dato: 'Ctrl+S' ns_Save_project = pyqtSignal(str)# dato: 'Ctrl+Shift+S' ns_Code_locator = pyqtSignal(str)# dato: 'Ctrl+K' ns_Show_Paste_History = pyqtSignal(str)# dato: 'Ctrl+4' ns_File_Opener = pyqtSignal(str)# dato: 'Ctrl+Alt+O' ns_Help = pyqtSignal(str)# dato: 'F1' ns_Show_Selector = pyqtSignal(str)# dato: 'Ctrl+`' ns_Split_assistance = pyqtSignal(str)# dato: 'F10' ns_change_tab_visibility = pyqtSignal(str)# dato: 'Shift+F1' ns_Split_horizontal = pyqtSignal(str)# dato: 'F9' ns_Split_vertical = pyqtSignal(str)# dato: 'Ctrl+F9' ns_Stop_execution = pyqtSignal(str)# dato: 'Ctrl+Shift+F6' ns_Uncomment = pyqtSignal(str)# dato: 'Ctrl+Shift+G' ns_undo = pyqtSignal(str)# dato: 'Ctrl+Z' ns_preferences_interface_showProjectExplorer = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showSymbolsList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showWebInspector = pyqtSignal(bool)# dato: 'False' ns_preferences_interface_showErrorsList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showMigrationList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_language = pyqtSignal(str)# dato: 'English' ns_preferences_editor_font = pyqtSignal(QFont)# dato: '<PyQt5.QtGui.QFont object at 0x089D32F0>' ns_preferences_editor_minimapMaxOpacity = pyqtSignal(float)# dato: '0.8' ns_preferences_editor_minimapMinOpacity = pyqtSignal(float)# dato: '0.1' ns_preferences_editor_minimapSizeProportion = pyqtSignal(float)# dato: '0.17' ns_preferences_editor_minimapShow = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_scheme = pyqtSignal(str)# dato: 'default' ns_preferences_editor_useTabs = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_marginLine = pyqtSignal(int)# dato: '80' ns_preferences_editor_showMarginLine = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_indent = pyqtSignal(int)# dato: '4' ns_preferences_editor_platformEndOfLine = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_errorsUnderlineBackground = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_errors = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_errorsInLine = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkStyle = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_showMigrationTips = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkStyleInline = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_centerOnScroll = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_removeTrailingSpaces = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_allowWordWrap = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showTabsAndSpaces = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showIndentationGuide = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkForDocstrings = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showLineNumbers = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_parentheses = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_brackets = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_keys = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_simpleQuotes = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_doubleQuotes = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_codeCompletion = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_completeDeclarations = pyqtSignal(bool)# dato: 'True' ns_preferences_theme_skin = pyqtSignal(str)# dato: 'Default' ds_lastSession_projects = pyqtSignal(list)# dato: '[]' ds_lastSession_openedFiles = pyqtSignal(list)# dato: '[]' ds_lastSession_currentFile = pyqtSignal(str)# dato: '' ds_lastSession_recentFiles = pyqtSignal(list)# dato: '[]' ns_preferences_editor_bookmarks = pyqtSignal(dict)# dato: '{}' ns_preferences_editor_breakpoints = pyqtSignal(dict)# dato: '{}' ns_window_maximized = pyqtSignal(bool)# dato: 'True' ns_window_central_baseSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x03\x84\x00\x00\x00\xc8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x01\x01'' ns_window_central_insideSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01B\x00\x00\x00\xa8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x02\x01'' ns_window_central_lateralVisible = pyqtSignal(bool)# dato: 'True' ns_window_hide_toolbar = pyqtSignal(bool)# dato: 'False' ns_tools_dock_visible = pyqtSignal(bool)# dato: 'True' #... ds_recentProjects = pyqtSignal(dict) ns_window_size = pyqtSignal(QSize) ns_window_pos = pyqtSignal(QPoint) def __init__(self, start_server=False): super(IDE, self).__init__() self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') self.setMinimumSize(750, 500) QToolTip.setFont(QFont(settings.FONT.family(), 10)) #Load the size and the position of the main window self.load_window_geometry() self.__project_to_open = 0 IDE.__instance = self wid = QWidget()#adjustSize wid.setContentsMargins(0, 0, 0, 0) box = QHBoxLayout(wid) box.setContentsMargins(0, 0, 0, 0) # l1 = QLabel("Info Here") # l1.setObjectName("Info") # l1.setStyleSheet("background-color: rgb(88, 255, 85);") # box.addWidget(l1) space = QSpacerItem(10,10, QSizePolicy.Expanding)#, QSizePolicy.Maximum) box.addSpacerItem(space) l2 = QLabel("Tab Size: "+str(settings.INDENT))#int(qsettings.value('preferences/editor/indent', 4, type=int)))) l2.setObjectName("Det1") font = l2.font() font.setPointSize(8) l2.setFont(font) box.addWidget(l2) box.addSpacing(50) l3 = QLabel("Python") l3.setObjectName("Det2") font.setPointSize(9) l3.setFont(font) box.addWidget(l3) box.addSpacing(30) status = self.statusBar() status.setMaximumHeight(20) status.addPermanentWidget(wid) # wid.show() # self.__wid = wid status.reformat() status.showMessage("Info Here") status.setStyleSheet("background-color: rgb(85, 85, 85);") #Editables self.__neditables = {} #Filesystem self.filesystem = nfilesystem.NVirtualFileSystem() #Sessions handler self._session = None #Opacity self.opacity = settings.MAX_OPACITY #ToolBar self.toolbar = QToolBar(self) if settings.IS_MAC_OS: self.toolbar.setIconSize(QSize(36, 36)) else: self.toolbar.setIconSize(QSize(24, 24)) self.toolbar.setToolTip(translations.TR_IDE_TOOLBAR_TOOLTIP) self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) # Set toggleViewAction text and tooltip self.toggleView = self.toolbar.toggleViewAction() self.toggleView.setText(translations.TR_TOOLBAR_VISIBILITY) self.toggleView.setToolTip(translations.TR_TOOLBAR_VISIBILITY) self.addToolBar(settings.TOOLBAR_AREA, self.toolbar) if settings.HIDE_TOOLBAR: self.toolbar.hide() #Notificator self.notification = notification.Notification(self) self.statusBar().messageChanged[str].connect(self.MessageStatusChanged.emit) #Plugin Manager # CHECK ACTIVATE PLUGINS SETTING #services = { #'editor': plugin_services.MainService(), #'toolbar': plugin_services.ToolbarService(self.toolbar), ##'menuApp': plugin_services.MenuAppService(self.pluginsMenu), #'menuApp': plugin_services.MenuAppService(None), #'explorer': plugin_services.ExplorerService(), #'misc': plugin_services.MiscContainerService(self.misc)} #serviceLocator = plugin_manager.ServiceLocator(services) serviceLocator = plugin_manager.ServiceLocator(None) self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS, serviceLocator) self.plugin_manager.discover() #load all plugins! self.plugin_manager.load_all() #Tray Icon self.trayIcon = updates.TrayIconUpdates(self) self.trayIcon.closeTrayIcon.connect(self._close_tray_icon) self.trayIcon.show() key = Qt.Key_1 for i in range(10): if settings.IS_MAC_OS: short = ui_tools.TabShortcuts( QKeySequence(Qt.CTRL + Qt.ALT + key), self, i) else: short = ui_tools.TabShortcuts( QKeySequence(Qt.ALT + key), self, i) key += 1 short.activated.connect(self._change_tab_index) short = ui_tools.TabShortcuts(QKeySequence(Qt.ALT + Qt.Key_0), self, 10) short.activated.connect(self._change_tab_index) # Register menu categories IDE.register_bar_category(translations.TR_MENU_FILE, 100) IDE.register_bar_category(translations.TR_MENU_EDIT, 110) IDE.register_bar_category(translations.TR_MENU_VIEW, 120) IDE.register_bar_category(translations.TR_MENU_SOURCE, 130) IDE.register_bar_category(translations.TR_MENU_PROJECT, 140) IDE.register_bar_category(translations.TR_MENU_EXTENSIONS, 150) IDE.register_bar_category(translations.TR_MENU_ABOUT, 160) # Register General Menu Items ui_tools.install_shortcuts(self, actions.ACTIONS_GENERAL, self) self.register_service('ide', self) self.register_service('toolbar', self.toolbar) self.register_service('filesystem', self.filesystem) #Register signals connections connections = ( {'target': 'main_container', 'signal_name': 'fileSaved',#(QString) 'slot': self.show_message}, {'target': 'main_container', 'signal_name': 'currentEditorChanged',#(QString) 'slot': self.change_window_title}, {'target': 'main_container', 'signal_name': 'openPreferences',#() 'slot': self.show_preferences}, {'target': 'main_container', 'signal_name': 'allTabsClosed',#() 'slot': self._last_tab_closed}, {'target': 'explorer_container', 'signal_name': 'changeWindowTitle',#(QString) 'slot': self.change_window_title}, {'target': 'explorer_container', 'signal_name': 'projectClosed',#(QString) 'slot': self.close_project}, ) self.register_signals('ide', connections) # Central Widget MUST always exists self.central = IDE.get_service('central_container') print("self.central:", self.central) self.setCentralWidget(self.central) # Install Services for service_name in self.__IDESERVICES: self.install_service(service_name) IDE.__created = True # Place Status Bar main_container = IDE.get_service('main_container') status_bar = IDE.get_service('status_bar') main_container.add_status_bar(status_bar) # Load Menu Bar menu_bar = IDE.get_service('menu_bar') if menu_bar: menu_bar.load_menu(self) #These two are the same service, I think that's ok menu_bar.load_toolbar(self) #Start server if needed self.s_listener = None if start_server: self.s_listener = QLocalServer() self.s_listener.listen("ninja_ide") self.s_listener.newConnection.connect(self._process_connection) @classmethod def hasCreated(clss): return clss.__created @classmethod def getInstance(clss): return clss.__instance @classmethod def get_service(cls, service_name): """Return the instance of a registered service.""" return cls.__IDESERVICES.get(service_name, None) def get_menuitems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDEMENUS def get_bar_categories(self): """Get the registered Categories for the Application menus.""" return IDE.__IDEBARCATEGORIES def get_toolbaritems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDETOOLBAR @classmethod def register_service(cls, service_name, obj): """Register a service providing the service name and the instance.""" cls.__IDESERVICES[service_name] = obj if cls.hasCreated(): cls.getInstance().install_service(service_name) def install_service(self, service_name): """Activate the registered service.""" obj = IDE.__IDESERVICES.get(service_name, None) func = getattr(obj, 'install', None) if isinstance(func, collections.Callable): func() self._connect_signals() def place_me_on(self, name, obj, region="central", top=False): """Place a widget in some of the areas in the IDE. @name: id to access to that widget later if needed. @obj: the instance of the widget to be placed. @region: the area where to put the widget [central, lateral] @top: place the widget as the first item in the split.""" self.central.add_to_region(name, obj, region, top) @classmethod def register_signals(cls, service_name, connections): """Register all the signals that a particular service wants to be attached of. @service_name: id of the service @connections: list of dictionaries for the connection with: - 'target': 'the_other_service_name', - 'signal_name': 'name of the signal in the other service', - 'slot': function object in this service""" cls.__IDECONNECTIONS[service_name] = connections if cls.hasCreated(): cls.getInstance()._connect_signals() def _connect_signals(self): """Connect the signals between the different services.""" for service_name in IDE.__IDECONNECTIONS: connections = IDE.__IDECONNECTIONS[service_name] for connection in connections: if connection.get('connected', False): continue target = IDE.__IDESERVICES.get( connection['target'], None) slot = connection['slot'] signal_name = connection['signal_name'] if target and isinstance(slot, collections.Callable): getattr(target, signal_name).connect(slot) connection['connected'] = True @classmethod def register_shortcut(cls, shortcut_name, shortcut, action=None): """Register a shortcut and action.""" cls.__IDESHORTCUTS[shortcut_name] = (shortcut, action) @classmethod def register_menuitem(cls, menu_action, section, weight): """Register a QAction or QMenu in the IDE to be loaded later in the menubar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @menu_action: QAction or QMenu @section: String (name) @weight: int""" cls.__IDEMENUS[menu_action] = (section, weight) @classmethod def register_toolbar(cls, action, section, weight): """Register a QAction in the IDE to be loaded later in the toolbar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @action: QAction @section: String (name) @weight: int""" cls.__IDETOOLBAR[action] = (section, weight) @classmethod def register_bar_category(cls, category_name, weight): """Register a Menu Category to be created with the proper weight. @category_name: string @weight: int""" cls.__IDEBARCATEGORIES[category_name] = weight @classmethod def update_shortcut(cls, shortcut_name): """Update all the shortcuts of the application.""" short = resources.get_shortcut shortcut, action = cls.__IDESHORTCUTS.get(shortcut_name) if shortcut: shortcut.setKey(short(shortcut_name)) if action: action.setShortcut(short(shortcut_name)) def get_or_create_nfile(self, filename): """For convenience access to files from ide""" return self.filesystem.get_file(nfile_path=filename) def get_or_create_editable(self, filename="", nfile=None): if nfile is None: nfile = self.filesystem.get_file(nfile_path=filename) editable = self.__neditables.get(nfile) if editable is None: editable = neditable.NEditable(nfile) editable.fileClosing.connect(self._unload_neditable) self.__neditables[nfile] = editable return editable def _unload_neditable(self, editable): self.__neditables.pop(editable.nfile) editable.nfile.deleteLater() editable.editor.deleteLater() editable.deleteLater() @property def opened_files(self): return tuple(self.__neditables.keys()) def get_project_for_file(self, filename): project = None if filename: project = self.filesystem.get_project_for_file(filename) return project def create_project(self, path): nproj = nproject.NProject(path) self.filesystem.open_project(nproj) return nproj def close_project(self, project_path): self.filesystem.close_project(project_path) def get_projects(self): return self.filesystem.get_projects() def get_current_project(self): current_project = None projects = self.filesystem.get_projects() for project in projects: if projects[project].is_current: current_project = projects[project] break return current_project def showMessageStatus(self, msg): QTimer.singleShot(1, Qt.PreciseTimer, lambda: self.statusBar().showMessage(msg)) # self.statusBar().showMessage(msg) @classmethod def select_current(cls, widget): """Show the widget with a 4px lightblue border line.""" widget.setProperty("highlight", True) widget.style().unpolish(widget) widget.style().polish(widget) @classmethod def unselect_current(cls, widget): """Remove the 4px lightblue border line from the widget.""" widget.setProperty("highlight", False) widget.style().unpolish(widget) widget.style().polish(widget) def _close_tray_icon(self): """Close the System Tray Icon.""" self.trayIcon.hide() self.trayIcon.deleteLater() def _change_tab_index(self): """Change the tabs of the current TabWidget using alt+numbers.""" widget = QApplication.focusWidget() shortcut_index = getattr(widget, 'shortcut_index', None) if shortcut_index: obj = self.sender() shortcut_index(obj.index) def _process_connection(self): """Read the ipc input from another instance of ninja.""" connection = self.s_listener.nextPendingConnection() connection.waitForReadyRead() data = connection.readAll() connection.close() if data: files, projects = str(data).split(ipc.project_delimiter, 1) files = [(x.split(':')[0], int(x.split(':')[1])) for x in files.split(ipc.file_delimiter)] projects = projects.split(ipc.project_delimiter) self.load_session_files_projects(files, [], projects, None) def fullscreen_mode(self): """Change to fullscreen mode.""" if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def change_toolbar_visibility(self): """Switch the toolbar visibility""" if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def load_external_plugins(self, paths): """Load external plugins, the ones added to ninja throw the cmd.""" for path in paths: self.plugin_manager.add_plugin_dir(path) #load all plugins! self.plugin_manager.discover() self.plugin_manager.load_all() def _last_tab_closed(self): """ Called when the last tasb is closed """ self.explorer.cleanup_tabs() def show_preferences(self): """Open the Preferences Dialog.""" pref = preferences.Preferences(self) main_container = IDE.get_service("main_container") print("\n\npreferences!!") if main_container: main_container.show_dialog(pref) print("\n\nmain_container---") else: pref.show() print("\n\nNONE---") def load_session_files_projects(self, files, projects, current_file, recent_files=None): """Load the files and projects from previous session.""" main_container = IDE.get_service('main_container') projects_explorer = IDE.get_service('projects_explorer') if main_container and files: for fileData in files: if file_manager.file_exists(fileData[0]): mtime = os.stat(fileData[0]).st_mtime ignore_checkers = (mtime == fileData[2]) line, col = fileData[1][0], fileData[1][1] main_container.open_file(fileData[0], line, col, ignore_checkers=ignore_checkers) #if current_file: #main_container.open_file(current_file) if projects_explorer and projects: projects_explorer.load_session_projects(projects) #if recent_files is not None: #menu_file = IDE.get_service('menu_file') #menu_file.update_recent_files(recent_files) #def _set_editors_project_data(self): #self.__project_to_open -= 1 #if self.__project_to_open == 0: #self.disconnect(self.explorer, SIGNAL("projectOpened(QString)"), #self._set_editors_project_data) #self.mainContainer.update_editor_project() #def open_file(self, filename): #if filename: #self.mainContainer.open_file(filename) #def open_project(self, project): #if project: #self.actions.open_project(project) def __get_session(self): return self._session def __set_session(self, sessionName): self._session = sessionName if self._session is not None: self.setWindowTitle(translations.TR_SESSION_IDE_HEADER % {'session': self._session}) else: self.setWindowTitle( 'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') Session = property(__get_session, __set_session) def change_window_title(self, title): """Change the title of the Application.""" if self._session is None: self.setWindowTitle('NINJA-IDE - %s' % title) else: self.setWindowTitle((translations.TR_SESSION_IDE_HEADER % {'session': self._session}) + ' - %s' % title) def wheelEvent(self, event): """Change the opacity of the application.""" if event.modifiers() == Qt.ShiftModifier: if event.delta() == 120 and self.opacity < settings.MAX_OPACITY: self.opacity += 0.1 elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY: self.opacity -= 0.1 self.setWindowOpacity(self.opacity) event.ignore() else: super(IDE, self).wheelEvent(event) @classmethod def ninja_settings(cls): qsettings = nsettings.NSettings(resources.SETTINGS_PATH, prefix="ns") if cls.hasCreated(): qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed) return qsettings @classmethod def data_settings(cls): qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH, prefix="ds") if cls.hasCreated(): qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed) return qsettings def _settings_value_changed(self, key, value): # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_") # self.emit(SIGNAL(signal_name), value) key = key.replace("/", "_").replace("-", "_") try: getattr(self, key).emit(value) except TypeError as reason: print("\n:::", key, value, type(value)) print("\n\nerrors:-:", reason) getattr(self, key).emit() except AttributeError: print("\n:::", key, value, type(value)) # if not value: # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError: # getattr(self, key.replace("/", "_")).emit() # return # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError as e: # print("\n\nerrors", e) # getattr(self, key.replace("/", "_")).emit() ##getattr(self, key.replace("/", "_").replace("-", "_")).emit(value) def save_settings(self): """Save the settings before the application is closed with QSettings. Info saved: Tabs and projects opened, windows state(size and position). """ qsettings = IDE.ninja_settings() data_qsettings = IDE.data_settings() main_container = self.get_service("main_container") editor_widget = None if main_container: editor_widget = main_container.get_current_editor() current_file = '' if editor_widget is not None: current_file = editor_widget.file_path if qsettings.value('preferences/general/loadFiles', True, type=bool): openedFiles = self.filesystem.get_files() projects_obj = self.filesystem.get_projects() projects = [projects_obj[proj].path for proj in projects_obj] data_qsettings.setValue('lastSession/projects', projects) files_info = [] for path in openedFiles: if not openedFiles[path]._exists(): print("\n\ncontinue", path) continue editable = self.__neditables.get(openedFiles[path]) if editable is not None and editable.is_dirty: stat_value = 0 else: stat_value = os.stat(path).st_mtime files_info.append([path, editable.editor.getCursorPosition(), stat_value]) data_qsettings.setValue('lastSession/openedFiles', files_info) if current_file is not None: data_qsettings.setValue('lastSession/currentFile', current_file) data_qsettings.setValue('lastSession/recentFiles', settings.LAST_OPENED_FILES) qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS) qsettings.setValue('preferences/editor/breakpoints', settings.BREAKPOINTS) # Session if self._session is not None: val = QMessageBox.question( self, translations.TR_SESSION_ACTIVE_IDE_CLOSING_TITLE, (translations.TR_SESSION_ACTIVE_IDE_CLOSING_BODY % {'session': self.Session}), QMessageBox.Yes, QMessageBox.No) if val == QMessageBox.Yes: session_manager.SessionsManager.save_session_data( self.Session, self) #qsettings.setValue('preferences/general/toolbarArea', #self.toolBarArea(self.toolbar)) #Save if the windows state is maximixed if(self.isMaximized()): qsettings.setValue("window/maximized", True) else: qsettings.setValue("window/maximized", False) #Save the size and position of the mainwindow qsettings.setValue("window/size", self.size()) qsettings.setValue("window/pos", self.pos()) self.central.save_configuration() #Save the toolbar visibility qsettings.setValue("window/hide_toolbar", not self.toolbar.isVisible()) #else: #qsettings.setValue("window/hide_toolbar", False) #Save Misc state #qsettings.setValue("window/show_region1", self.misc.isVisible()) #Save Profiles #if self.profile is not None: #self.actions.save_profile(self.profile) #else: #qsettings.setValue('ide/profiles', settings.PROFILES) def activate_profile(self): """Show the Session Manager dialog.""" profilesLoader = session_manager.SessionsManager(self) profilesLoader.show() def deactivate_profile(self): """Close the Session Session.""" self.Session = None def load_window_geometry(self): """Load from QSettings the window size of Ninja IDE""" qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) if qsettings.value("window/maximized", True, type=bool): self.setWindowState(Qt.WindowMaximized) else: self.resize(qsettings.value( "window/size", QSize(800, 600), type='QSize')) self.move(qsettings.value( "window/pos", QPoint(100, 100), type='QPoint')) def _get_unsaved_files(self): """Return an array with the path of the unsaved files.""" unsaved = [] files = self.opened_files for f in files: editable = self.__neditables.get(f) print("\n\neditable::", editable, getattr(editable, "editor", "-")) if editable is not None and editable.editor is not None and editable.editor.is_modified: unsaved.append(f) return unsaved def _save_unsaved_files(self, files): """Save the files from the paths in the array.""" for f in files: editable = self.get_or_create_editable(f) editable.ignore_checkers = True editable.save_content() def closeEvent(self, event): """Saves some global settings before closing.""" if self.s_listener: self.s_listener.close() main_container = self.get_service("main_container") unsaved_files = self._get_unsaved_files() if (settings.CONFIRM_EXIT and unsaved_files): txt = '\n'.join([nfile.file_name for nfile in unsaved_files]) val = QMessageBox.question( self, translations.TR_IDE_CONFIRM_EXIT_TITLE, (translations.TR_IDE_CONFIRM_EXIT_BODY % {'files': txt}), QMessageBox.Yes | QMessageBox.No, QMessageBox.Cancel) if val == QMessageBox.Yes: #Saves all open files self._save_unsaved_files(unsaved_files) if val == QMessageBox.Cancel: event.ignore() return self.save_settings() self.goingDown.emit() #close python documentation server (if running) main_container.close_python_doc() #Shutdown PluginManager self.plugin_manager.shutdown() #completion_daemon.shutdown_daemon() super(IDE, self).closeEvent(event) def notify_plugin_errors(self): #TODO: Check if the Plugin Error dialog can be improved errors = self.plugin_manager.errors if errors: plugin_error_dialog = traceback_widget.PluginErrorDialog() for err_tuple in errors: plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1]) #show the dialog plugin_error_dialog.exec_() def show_message(self, message, duration=3000): """Show status message.""" self.notification.set_message(message, duration) self.notification.show() def show_plugins_store(self): """Open the Plugins Manager to install/uninstall plugins.""" store = plugins_store.PluginsStore(self) main_container = IDE.get_service("main_container") print("\nshow_plugins_store") if main_container: print("\nshow_plugins_store::main_container") main_container.show_dialog(store) else: store.show() def show_languages(self): """Open the Language Manager to install/uninstall languages.""" manager = language_manager.LanguagesManagerWidget(self) manager.show() def show_schemes(self): """Open the Schemes Manager to install/uninstall schemes.""" manager = schemes_manager.SchemesManagerWidget(self) manager.show() def show_about_qt(self): """Show About Qt Dialog.""" QMessageBox.aboutQt(self, translations.TR_ABOUT_QT) def show_about_ninja(self): """Show About NINJA-IDE Dialog.""" about = about_ninja.AboutNinja(self) about.show() def show_python_detection(self): """Show Python detection dialog for windows.""" #TODO: Notify the user when no python version could be found suggested = settings.detect_python_path() if suggested: dialog = python_detect_dialog.PythonDetectDialog(suggested, self) dialog.show()
class IDE(QMainWindow): """This class is like the Sauron's Ring: One ring to rule them all, One ring to find them, One ring to bring them all and in the darkness bind them. This Class knows all the containers, and its know by all the containers, but the containers don't need to know between each other, in this way we can keep a better api without the need to tie the behaviour between the widgets, and let them just consume the 'actions' they need.""" ############################################################################### # SIGNALS # # goingDown() ############################################################################### __IDESERVICES = {} __IDECONNECTIONS = {} __IDESHORTCUTS = {} __IDEBARCATEGORIES = {} __IDEMENUS = {} __IDETOOLBAR = {} # CONNECTIONS structure: # ({'target': service_name, 'signal_name': string, 'slot': function_obj},) # On modify add: {connected: True} __instance = None __created = False MessageStatusChanged = pyqtSignal(str) goingDown = pyqtSignal() # # ns_preferences_editor_font = pyqtSignal() # ns_preferences_editor_showTabsAndSpaces = pyqtSignal() # ns_preferences_editor_showIndentationGuide = pyqtSignal() # ns_preferences_editor_indent = pyqtSignal() # ns_preferences_editor_marginLine = pyqtSignal()#podría tener un argumento # ns_preferences_editor_showLineNumbers = pyqtSignal() # ns_preferences_editor_showMigrationTips = pyqtSignal() # ns_preferences_editor_checkStyle = pyqtSignal() # ns_preferences_editor_errors = pyqtSignal() # ds_lastSession_projects = pyqtSignal() # ds_lastSession_openedFiles = pyqtSignal() # ds_lastSession_currentFile = pyqtSignal() # ds_lastSession_recentFiles = pyqtSignal() # ns_preferences_editor_bookmarks = pyqtSignal() # ns_preferences_editor_breakpoints = pyqtSignal() # ns_window_maximized = pyqtSignal() # ns_preferences_general_loadFiles = pyqtSignal() # ns_preferences_general_activatePlugins = pyqtSignal() # ns_preferences_general_notifyUpdates = pyqtSignal() # ns_preferences_general_showStartPage = pyqtSignal(bool) # ns_preferences_general_confirmExit = pyqtSignal(bool) # ns_preferences_general_workspace = pyqtSignal() ns_preferences_general_supportedExtensions = pyqtSignal("QStringList") #ns_preferences_general_notification_position = pyqtSignal(int) #... ns_preferences_general_loadFiles = pyqtSignal(bool) # dato: 'True' ns_preferences_general_activatePlugins = pyqtSignal(bool) # dato: 'True' ns_preferences_general_notifyUpdates = pyqtSignal(bool) # dato: 'True' ns_preferences_general_showStartPage = pyqtSignal(bool) # dato: 'True' ns_preferences_general_confirmExit = pyqtSignal(bool) # dato: 'True' ns_preferences_general_workspace = pyqtSignal(str) # dato: '' #ns_preferences_general_supportedExtensions = pyqtSignal(list)# dato: '['.py', '.pyw', '.html', '.jpg','.png', '.ui', '.css', '.json', '.js', '.ini']' ns_preferences_general_notification_position = pyqtSignal(int) # dato: '0' ns_preferences_general_notification_color = pyqtSignal(str) # dato: '#000' ns_pythonPath = pyqtSignal(str) # dato: 'D:\Python34\python.exe' ns_executionOptions = pyqtSignal(str) # dato: '' ns_Show_Code_Nav = pyqtSignal(str) # dato: 'Ctrl+3' ns_Follow_mode = pyqtSignal(str) # dato: 'Ctrl+F10' ns_Change_Tab = pyqtSignal(str) # dato: 'Ctrl+PgDown' ns_Change_Tab_Reverse = pyqtSignal(str) # dato: 'Ctrl+PgUp' ns_Close_file = pyqtSignal(str) # dato: 'Ctrl+W' ns_Close_Split = pyqtSignal(str) # dato: 'Shift+F9' ns_Comment = pyqtSignal(str) # dato: 'Ctrl+G' ns_Complete_Declarations = pyqtSignal(str) # dato: 'Alt+Return' ns_copy = pyqtSignal(str) # dato: 'Ctrl+C' ns_History_Copy = pyqtSignal(str) # dato: 'Ctrl+Alt+C' ns_New_project = pyqtSignal(str) # dato: 'Ctrl+Shift+N' ns_New_file = pyqtSignal(str) # dato: 'Ctrl+N' ns_cut = pyqtSignal(str) # dato: 'Ctrl+X' ns_Debug = pyqtSignal(str) # dato: 'F7' ns_Duplicate = pyqtSignal(str) # dato: 'Ctrl+R' ns_Run_file = pyqtSignal(str) # dato: 'Ctrl+F6' ns_Run_project = pyqtSignal(str) # dato: 'F6' ns_expand_file_combo = pyqtSignal(str) # dato: 'Ctrl+Tab' ns_expand_symbol_combo = pyqtSignal(str) # dato: 'Ctrl+2' ns_Find = pyqtSignal(str) # dato: 'Ctrl+F' ns_Find_replace = pyqtSignal(str) # dato: 'Ctrl+H' ns_Find_in_files = pyqtSignal(str) # dato: 'Ctrl+L' ns_Find_next = pyqtSignal(str) # dato: 'Ctrl+F3' ns_Find_previous = pyqtSignal(str) # dato: 'Shift+F3' ns_Find_with_word = pyqtSignal(str) # dato: 'Ctrl+Shift+F' ns_Full_screen = pyqtSignal(str) # dato: 'Ctrl+F11' ns_Go_to_definition = pyqtSignal(str) # dato: 'Ctrl+Return' ns_Hide_all = pyqtSignal(str) # dato: 'F11' ns_Hide_editor = pyqtSignal(str) # dato: 'F3' ns_Hide_explorer = pyqtSignal(str) # dato: 'F2' ns_Hide_misc = pyqtSignal(str) # dato: 'F4' ns_Highlight_Word = pyqtSignal(str) # dato: 'Ctrl+Down' ns_Import = pyqtSignal(str) # dato: 'Ctrl+I' ns_Indent_less = pyqtSignal(str) # dato: 'Shift+Tab' ns_Indent_more = pyqtSignal(str) # dato: 'Tab' ns_Add_Bookmark_or_Breakpoint = pyqtSignal(str) # dato: 'Ctrl+B' ns_Title_comment = pyqtSignal(str) # dato: '' ns_Horizontal_line = pyqtSignal(str) # dato: '' ns_Move_down = pyqtSignal(str) # dato: 'Alt+Down' ns_Move_up = pyqtSignal(str) # dato: 'Alt+Up' ns_Move_Tab_to_left = pyqtSignal(str) # dato: 'Ctrl+Shift+9' ns_Move_Tab_to_right = pyqtSignal(str) # dato: 'Ctrl+Shift+0' ns_Navigate_back = pyqtSignal(str) # dato: 'Alt+Left' ns_Navigate_forward = pyqtSignal(str) # dato: 'Alt+Right' ns_Open_file = pyqtSignal(str) # dato: 'Ctrl+O' ns_Open_project = pyqtSignal(str) # dato: 'Ctrl+Shift+O' ns_Open_recent_closed = pyqtSignal(str) # dato: 'Ctrl+Shift+T' ns_paste = pyqtSignal(str) # dato: 'Ctrl+V' ns_History_Paste = pyqtSignal(str) # dato: 'Ctrl+Alt+V' ns_Print_file = pyqtSignal(str) # dato: 'Ctrl+P' ns_Redo = pyqtSignal(str) # dato: 'Ctrl+Y' ns_Reload_file = pyqtSignal(str) # dato: 'F5' ns_Remove_line = pyqtSignal(str) # dato: 'Ctrl+E' ns_Save_file = pyqtSignal(str) # dato: 'Ctrl+S' ns_Save_project = pyqtSignal(str) # dato: 'Ctrl+Shift+S' ns_Code_locator = pyqtSignal(str) # dato: 'Ctrl+K' ns_Show_Paste_History = pyqtSignal(str) # dato: 'Ctrl+4' ns_File_Opener = pyqtSignal(str) # dato: 'Ctrl+Alt+O' ns_Help = pyqtSignal(str) # dato: 'F1' ns_Show_Selector = pyqtSignal(str) # dato: 'Ctrl+`' ns_Split_assistance = pyqtSignal(str) # dato: 'F10' ns_change_tab_visibility = pyqtSignal(str) # dato: 'Shift+F1' ns_Split_horizontal = pyqtSignal(str) # dato: 'F9' ns_Split_vertical = pyqtSignal(str) # dato: 'Ctrl+F9' ns_Stop_execution = pyqtSignal(str) # dato: 'Ctrl+Shift+F6' ns_Uncomment = pyqtSignal(str) # dato: 'Ctrl+Shift+G' ns_undo = pyqtSignal(str) # dato: 'Ctrl+Z' ns_preferences_interface_showProjectExplorer = pyqtSignal( bool) # dato: 'True' ns_preferences_interface_showSymbolsList = pyqtSignal(bool) # dato: 'True' ns_preferences_interface_showWebInspector = pyqtSignal( bool) # dato: 'False' ns_preferences_interface_showErrorsList = pyqtSignal(bool) # dato: 'True' ns_preferences_interface_showMigrationList = pyqtSignal( bool) # dato: 'True' ns_preferences_interface_language = pyqtSignal(str) # dato: 'English' ns_preferences_editor_font = pyqtSignal( QFont) # dato: '<PyQt5.QtGui.QFont object at 0x089D32F0>' ns_preferences_editor_minimapMaxOpacity = pyqtSignal(float) # dato: '0.8' ns_preferences_editor_minimapMinOpacity = pyqtSignal(float) # dato: '0.1' ns_preferences_editor_minimapSizeProportion = pyqtSignal( float) # dato: '0.17' ns_preferences_editor_minimapShow = pyqtSignal(bool) # dato: 'False' ns_preferences_editor_scheme = pyqtSignal(str) # dato: 'default' ns_preferences_editor_useTabs = pyqtSignal(bool) # dato: 'False' ns_preferences_editor_marginLine = pyqtSignal(int) # dato: '80' ns_preferences_editor_showMarginLine = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_indent = pyqtSignal(int) # dato: '4' ns_preferences_editor_platformEndOfLine = pyqtSignal(bool) # dato: 'False' ns_preferences_editor_errorsUnderlineBackground = pyqtSignal( bool) # dato: 'True' ns_preferences_editor_errors = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_errorsInLine = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_checkStyle = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_showMigrationTips = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_checkStyleInline = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_centerOnScroll = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_removeTrailingSpaces = pyqtSignal( bool) # dato: 'True' ns_preferences_editor_allowWordWrap = pyqtSignal(bool) # dato: 'False' ns_preferences_editor_showTabsAndSpaces = pyqtSignal(bool) # dato: 'False' ns_preferences_editor_showIndentationGuide = pyqtSignal( bool) # dato: 'True' ns_preferences_editor_checkForDocstrings = pyqtSignal( bool) # dato: 'False' ns_preferences_editor_showLineNumbers = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_parentheses = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_brackets = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_keys = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_simpleQuotes = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_doubleQuotes = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_codeCompletion = pyqtSignal(bool) # dato: 'True' ns_preferences_editor_completeDeclarations = pyqtSignal( bool) # dato: 'True' ns_preferences_theme_skin = pyqtSignal(str) # dato: 'Default' ds_lastSession_projects = pyqtSignal(list) # dato: '[]' ds_lastSession_openedFiles = pyqtSignal(list) # dato: '[]' ds_lastSession_currentFile = pyqtSignal(str) # dato: '' ds_lastSession_recentFiles = pyqtSignal(list) # dato: '[]' ns_preferences_editor_bookmarks = pyqtSignal(dict) # dato: '{}' ns_preferences_editor_breakpoints = pyqtSignal(dict) # dato: '{}' ns_window_maximized = pyqtSignal(bool) # dato: 'True' ns_window_central_baseSplitterSize = pyqtSignal( QByteArray ) # dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x03\x84\x00\x00\x00\xc8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x01\x01'' ns_window_central_insideSplitterSize = pyqtSignal( QByteArray ) # dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01B\x00\x00\x00\xa8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x02\x01'' ns_window_central_lateralVisible = pyqtSignal(bool) # dato: 'True' ns_window_hide_toolbar = pyqtSignal(bool) # dato: 'False' ns_tools_dock_visible = pyqtSignal(bool) # dato: 'True' #... ds_recentProjects = pyqtSignal(dict) ns_window_size = pyqtSignal(QSize) ns_window_pos = pyqtSignal(QPoint) def __init__(self, start_server=False): super(IDE, self).__init__() self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') self.setMinimumSize(750, 500) QToolTip.setFont(QFont(settings.FONT.family(), 10)) #Load the size and the position of the main window self.load_window_geometry() self.__project_to_open = 0 IDE.__instance = self wid = QWidget() #adjustSize wid.setContentsMargins(0, 0, 0, 0) box = QHBoxLayout(wid) box.setContentsMargins(0, 0, 0, 0) # l1 = QLabel("Info Here") # l1.setObjectName("Info") # l1.setStyleSheet("background-color: rgb(88, 255, 85);") # box.addWidget(l1) space = QSpacerItem(10, 10, QSizePolicy.Expanding) #, QSizePolicy.Maximum) box.addSpacerItem(space) l2 = QLabel( "Tab Size: " + str(settings.INDENT) ) #int(qsettings.value('preferences/editor/indent', 4, type=int)))) l2.setObjectName("Det1") font = l2.font() font.setPointSize(8) l2.setFont(font) box.addWidget(l2) box.addSpacing(50) l3 = QLabel("Python") l3.setObjectName("Det2") font.setPointSize(9) l3.setFont(font) box.addWidget(l3) box.addSpacing(30) status = self.statusBar() status.setMaximumHeight(20) status.addPermanentWidget(wid) # wid.show() # self.__wid = wid status.reformat() status.showMessage("Info Here") status.setStyleSheet("background-color: rgb(85, 85, 85);") #Editables self.__neditables = {} #Filesystem self.filesystem = nfilesystem.NVirtualFileSystem() #Sessions handler self._session = None #Opacity self.opacity = settings.MAX_OPACITY #ToolBar self.toolbar = QToolBar(self) if settings.IS_MAC_OS: self.toolbar.setIconSize(QSize(36, 36)) else: self.toolbar.setIconSize(QSize(24, 24)) self.toolbar.setToolTip(translations.TR_IDE_TOOLBAR_TOOLTIP) self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) # Set toggleViewAction text and tooltip self.toggleView = self.toolbar.toggleViewAction() self.toggleView.setText(translations.TR_TOOLBAR_VISIBILITY) self.toggleView.setToolTip(translations.TR_TOOLBAR_VISIBILITY) self.addToolBar(settings.TOOLBAR_AREA, self.toolbar) if settings.HIDE_TOOLBAR: self.toolbar.hide() #Notificator self.notification = notification.Notification(self) self.statusBar().messageChanged[str].connect( self.MessageStatusChanged.emit) #Plugin Manager # CHECK ACTIVATE PLUGINS SETTING #services = { #'editor': plugin_services.MainService(), #'toolbar': plugin_services.ToolbarService(self.toolbar), ##'menuApp': plugin_services.MenuAppService(self.pluginsMenu), #'menuApp': plugin_services.MenuAppService(None), #'explorer': plugin_services.ExplorerService(), #'misc': plugin_services.MiscContainerService(self.misc)} #serviceLocator = plugin_manager.ServiceLocator(services) serviceLocator = plugin_manager.ServiceLocator(None) self.plugin_manager = plugin_manager.PluginManager( resources.PLUGINS, serviceLocator) self.plugin_manager.discover() #load all plugins! self.plugin_manager.load_all() #Tray Icon self.trayIcon = updates.TrayIconUpdates(self) self.trayIcon.closeTrayIcon.connect(self._close_tray_icon) self.trayIcon.show() key = Qt.Key_1 for i in range(10): if settings.IS_MAC_OS: short = ui_tools.TabShortcuts( QKeySequence(Qt.CTRL + Qt.ALT + key), self, i) else: short = ui_tools.TabShortcuts(QKeySequence(Qt.ALT + key), self, i) key += 1 short.activated.connect(self._change_tab_index) short = ui_tools.TabShortcuts(QKeySequence(Qt.ALT + Qt.Key_0), self, 10) short.activated.connect(self._change_tab_index) # Register menu categories IDE.register_bar_category(translations.TR_MENU_FILE, 100) IDE.register_bar_category(translations.TR_MENU_EDIT, 110) IDE.register_bar_category(translations.TR_MENU_VIEW, 120) IDE.register_bar_category(translations.TR_MENU_SOURCE, 130) IDE.register_bar_category(translations.TR_MENU_PROJECT, 140) IDE.register_bar_category(translations.TR_MENU_EXTENSIONS, 150) IDE.register_bar_category(translations.TR_MENU_ABOUT, 160) # Register General Menu Items ui_tools.install_shortcuts(self, actions.ACTIONS_GENERAL, self) self.register_service('ide', self) self.register_service('toolbar', self.toolbar) self.register_service('filesystem', self.filesystem) #Register signals connections connections = ( { 'target': 'main_container', 'signal_name': 'fileSaved', #(QString) 'slot': self.show_message }, { 'target': 'main_container', 'signal_name': 'currentEditorChanged', #(QString) 'slot': self.change_window_title }, { 'target': 'main_container', 'signal_name': 'openPreferences', #() 'slot': self.show_preferences }, { 'target': 'main_container', 'signal_name': 'allTabsClosed', #() 'slot': self._last_tab_closed }, { 'target': 'explorer_container', 'signal_name': 'changeWindowTitle', #(QString) 'slot': self.change_window_title }, { 'target': 'explorer_container', 'signal_name': 'projectClosed', #(QString) 'slot': self.close_project }, ) self.register_signals('ide', connections) # Central Widget MUST always exists self.central = IDE.get_service('central_container') print("self.central:", self.central) self.setCentralWidget(self.central) # Install Services for service_name in self.__IDESERVICES: self.install_service(service_name) IDE.__created = True # Place Status Bar main_container = IDE.get_service('main_container') status_bar = IDE.get_service('status_bar') main_container.add_status_bar(status_bar) # Load Menu Bar menu_bar = IDE.get_service('menu_bar') if menu_bar: menu_bar.load_menu(self) #These two are the same service, I think that's ok menu_bar.load_toolbar(self) #Start server if needed self.s_listener = None if start_server: self.s_listener = QLocalServer() self.s_listener.listen("ninja_ide") self.s_listener.newConnection.connect(self._process_connection) @classmethod def hasCreated(clss): return clss.__created @classmethod def getInstance(clss): return clss.__instance @classmethod def get_service(cls, service_name): """Return the instance of a registered service.""" return cls.__IDESERVICES.get(service_name, None) def get_menuitems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDEMENUS def get_bar_categories(self): """Get the registered Categories for the Application menus.""" return IDE.__IDEBARCATEGORIES def get_toolbaritems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDETOOLBAR @classmethod def register_service(cls, service_name, obj): """Register a service providing the service name and the instance.""" cls.__IDESERVICES[service_name] = obj if cls.hasCreated(): cls.getInstance().install_service(service_name) def install_service(self, service_name): """Activate the registered service.""" obj = IDE.__IDESERVICES.get(service_name, None) func = getattr(obj, 'install', None) if isinstance(func, collections.Callable): func() self._connect_signals() def place_me_on(self, name, obj, region="central", top=False): """Place a widget in some of the areas in the IDE. @name: id to access to that widget later if needed. @obj: the instance of the widget to be placed. @region: the area where to put the widget [central, lateral] @top: place the widget as the first item in the split.""" self.central.add_to_region(name, obj, region, top) @classmethod def register_signals(cls, service_name, connections): """Register all the signals that a particular service wants to be attached of. @service_name: id of the service @connections: list of dictionaries for the connection with: - 'target': 'the_other_service_name', - 'signal_name': 'name of the signal in the other service', - 'slot': function object in this service""" cls.__IDECONNECTIONS[service_name] = connections if cls.hasCreated(): cls.getInstance()._connect_signals() def _connect_signals(self): """Connect the signals between the different services.""" for service_name in IDE.__IDECONNECTIONS: connections = IDE.__IDECONNECTIONS[service_name] for connection in connections: if connection.get('connected', False): continue target = IDE.__IDESERVICES.get(connection['target'], None) slot = connection['slot'] signal_name = connection['signal_name'] if target and isinstance(slot, collections.Callable): getattr(target, signal_name).connect(slot) connection['connected'] = True @classmethod def register_shortcut(cls, shortcut_name, shortcut, action=None): """Register a shortcut and action.""" cls.__IDESHORTCUTS[shortcut_name] = (shortcut, action) @classmethod def register_menuitem(cls, menu_action, section, weight): """Register a QAction or QMenu in the IDE to be loaded later in the menubar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @menu_action: QAction or QMenu @section: String (name) @weight: int""" cls.__IDEMENUS[menu_action] = (section, weight) @classmethod def register_toolbar(cls, action, section, weight): """Register a QAction in the IDE to be loaded later in the toolbar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @action: QAction @section: String (name) @weight: int""" cls.__IDETOOLBAR[action] = (section, weight) @classmethod def register_bar_category(cls, category_name, weight): """Register a Menu Category to be created with the proper weight. @category_name: string @weight: int""" cls.__IDEBARCATEGORIES[category_name] = weight @classmethod def update_shortcut(cls, shortcut_name): """Update all the shortcuts of the application.""" short = resources.get_shortcut shortcut, action = cls.__IDESHORTCUTS.get(shortcut_name) if shortcut: shortcut.setKey(short(shortcut_name)) if action: action.setShortcut(short(shortcut_name)) def get_or_create_nfile(self, filename): """For convenience access to files from ide""" return self.filesystem.get_file(nfile_path=filename) def get_or_create_editable(self, filename="", nfile=None): if nfile is None: nfile = self.filesystem.get_file(nfile_path=filename) editable = self.__neditables.get(nfile) if editable is None: editable = neditable.NEditable(nfile) editable.fileClosing.connect(self._unload_neditable) self.__neditables[nfile] = editable return editable def _unload_neditable(self, editable): self.__neditables.pop(editable.nfile) editable.nfile.deleteLater() editable.editor.deleteLater() editable.deleteLater() @property def opened_files(self): return tuple(self.__neditables.keys()) def get_project_for_file(self, filename): project = None if filename: project = self.filesystem.get_project_for_file(filename) return project def create_project(self, path): nproj = nproject.NProject(path) self.filesystem.open_project(nproj) return nproj def close_project(self, project_path): self.filesystem.close_project(project_path) def get_projects(self): return self.filesystem.get_projects() def get_current_project(self): current_project = None projects = self.filesystem.get_projects() for project in projects: if projects[project].is_current: current_project = projects[project] break return current_project def showMessageStatus(self, msg): QTimer.singleShot(1, Qt.PreciseTimer, lambda: self.statusBar().showMessage(msg)) # self.statusBar().showMessage(msg) @classmethod def select_current(cls, widget): """Show the widget with a 4px lightblue border line.""" widget.setProperty("highlight", True) widget.style().unpolish(widget) widget.style().polish(widget) @classmethod def unselect_current(cls, widget): """Remove the 4px lightblue border line from the widget.""" widget.setProperty("highlight", False) widget.style().unpolish(widget) widget.style().polish(widget) def _close_tray_icon(self): """Close the System Tray Icon.""" self.trayIcon.hide() self.trayIcon.deleteLater() def _change_tab_index(self): """Change the tabs of the current TabWidget using alt+numbers.""" widget = QApplication.focusWidget() shortcut_index = getattr(widget, 'shortcut_index', None) if shortcut_index: obj = self.sender() shortcut_index(obj.index) def _process_connection(self): """Read the ipc input from another instance of ninja.""" connection = self.s_listener.nextPendingConnection() connection.waitForReadyRead() data = connection.readAll() connection.close() if data: files, projects = str(data).split(ipc.project_delimiter, 1) files = [(x.split(':')[0], int(x.split(':')[1])) for x in files.split(ipc.file_delimiter)] projects = projects.split(ipc.project_delimiter) self.load_session_files_projects(files, [], projects, None) def fullscreen_mode(self): """Change to fullscreen mode.""" if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def change_toolbar_visibility(self): """Switch the toolbar visibility""" if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def load_external_plugins(self, paths): """Load external plugins, the ones added to ninja throw the cmd.""" for path in paths: self.plugin_manager.add_plugin_dir(path) #load all plugins! self.plugin_manager.discover() self.plugin_manager.load_all() def _last_tab_closed(self): """ Called when the last tasb is closed """ self.explorer.cleanup_tabs() def show_preferences(self): """Open the Preferences Dialog.""" pref = preferences.Preferences(self) main_container = IDE.get_service("main_container") print("\n\npreferences!!") if main_container: main_container.show_dialog(pref) print("\n\nmain_container---") else: pref.show() print("\n\nNONE---") def load_session_files_projects(self, files, projects, current_file, recent_files=None): """Load the files and projects from previous session.""" main_container = IDE.get_service('main_container') projects_explorer = IDE.get_service('projects_explorer') if main_container and files: for fileData in files: if file_manager.file_exists(fileData[0]): mtime = os.stat(fileData[0]).st_mtime ignore_checkers = (mtime == fileData[2]) line, col = fileData[1][0], fileData[1][1] main_container.open_file(fileData[0], line, col, ignore_checkers=ignore_checkers) #if current_file: #main_container.open_file(current_file) if projects_explorer and projects: projects_explorer.load_session_projects(projects) #if recent_files is not None: #menu_file = IDE.get_service('menu_file') #menu_file.update_recent_files(recent_files) #def _set_editors_project_data(self): #self.__project_to_open -= 1 #if self.__project_to_open == 0: #self.disconnect(self.explorer, SIGNAL("projectOpened(QString)"), #self._set_editors_project_data) #self.mainContainer.update_editor_project() #def open_file(self, filename): #if filename: #self.mainContainer.open_file(filename) #def open_project(self, project): #if project: #self.actions.open_project(project) def __get_session(self): return self._session def __set_session(self, sessionName): self._session = sessionName if self._session is not None: self.setWindowTitle(translations.TR_SESSION_IDE_HEADER % {'session': self._session}) else: self.setWindowTitle( 'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') Session = property(__get_session, __set_session) def change_window_title(self, title): """Change the title of the Application.""" if self._session is None: self.setWindowTitle('NINJA-IDE - %s' % title) else: self.setWindowTitle((translations.TR_SESSION_IDE_HEADER % { 'session': self._session }) + ' - %s' % title) def wheelEvent(self, event): """Change the opacity of the application.""" if event.modifiers() == Qt.ShiftModifier: if event.delta() == 120 and self.opacity < settings.MAX_OPACITY: self.opacity += 0.1 elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY: self.opacity -= 0.1 self.setWindowOpacity(self.opacity) event.ignore() else: super(IDE, self).wheelEvent(event) @classmethod def ninja_settings(cls): qsettings = nsettings.NSettings(resources.SETTINGS_PATH, prefix="ns") if cls.hasCreated(): qsettings.valueChanged.connect( cls.getInstance()._settings_value_changed) return qsettings @classmethod def data_settings(cls): qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH, prefix="ds") if cls.hasCreated(): qsettings.valueChanged.connect( cls.getInstance()._settings_value_changed) return qsettings def _settings_value_changed(self, key, value): # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_") # self.emit(SIGNAL(signal_name), value) key = key.replace("/", "_").replace("-", "_") try: getattr(self, key).emit(value) except TypeError as reason: print("\n:::", key, value, type(value)) print("\n\nerrors:-:", reason) getattr(self, key).emit() except AttributeError: print("\n:::", key, value, type(value)) # if not value: # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError: # getattr(self, key.replace("/", "_")).emit() # return # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError as e: # print("\n\nerrors", e) # getattr(self, key.replace("/", "_")).emit() ##getattr(self, key.replace("/", "_").replace("-", "_")).emit(value) def save_settings(self): """Save the settings before the application is closed with QSettings. Info saved: Tabs and projects opened, windows state(size and position). """ qsettings = IDE.ninja_settings() data_qsettings = IDE.data_settings() main_container = self.get_service("main_container") editor_widget = None if main_container: editor_widget = main_container.get_current_editor() current_file = '' if editor_widget is not None: current_file = editor_widget.file_path if qsettings.value('preferences/general/loadFiles', True, type=bool): openedFiles = self.filesystem.get_files() projects_obj = self.filesystem.get_projects() projects = [projects_obj[proj].path for proj in projects_obj] data_qsettings.setValue('lastSession/projects', projects) files_info = [] for path in openedFiles: if not openedFiles[path]._exists(): print("\n\ncontinue", path) continue editable = self.__neditables.get(openedFiles[path]) if editable is not None and editable.is_dirty: stat_value = 0 else: stat_value = os.stat(path).st_mtime files_info.append( [path, editable.editor.getCursorPosition(), stat_value]) data_qsettings.setValue('lastSession/openedFiles', files_info) if current_file is not None: data_qsettings.setValue('lastSession/currentFile', current_file) data_qsettings.setValue('lastSession/recentFiles', settings.LAST_OPENED_FILES) qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS) qsettings.setValue('preferences/editor/breakpoints', settings.BREAKPOINTS) # Session if self._session is not None: val = QMessageBox.question( self, translations.TR_SESSION_ACTIVE_IDE_CLOSING_TITLE, (translations.TR_SESSION_ACTIVE_IDE_CLOSING_BODY % { 'session': self.Session }), QMessageBox.Yes, QMessageBox.No) if val == QMessageBox.Yes: session_manager.SessionsManager.save_session_data( self.Session, self) #qsettings.setValue('preferences/general/toolbarArea', #self.toolBarArea(self.toolbar)) #Save if the windows state is maximixed if (self.isMaximized()): qsettings.setValue("window/maximized", True) else: qsettings.setValue("window/maximized", False) #Save the size and position of the mainwindow qsettings.setValue("window/size", self.size()) qsettings.setValue("window/pos", self.pos()) self.central.save_configuration() #Save the toolbar visibility qsettings.setValue("window/hide_toolbar", not self.toolbar.isVisible()) #else: #qsettings.setValue("window/hide_toolbar", False) #Save Misc state #qsettings.setValue("window/show_region1", self.misc.isVisible()) #Save Profiles #if self.profile is not None: #self.actions.save_profile(self.profile) #else: #qsettings.setValue('ide/profiles', settings.PROFILES) def activate_profile(self): """Show the Session Manager dialog.""" profilesLoader = session_manager.SessionsManager(self) profilesLoader.show() def deactivate_profile(self): """Close the Session Session.""" self.Session = None def load_window_geometry(self): """Load from QSettings the window size of Ninja IDE""" qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) if qsettings.value("window/maximized", True, type=bool): self.setWindowState(Qt.WindowMaximized) else: self.resize( qsettings.value("window/size", QSize(800, 600), type='QSize')) self.move( qsettings.value("window/pos", QPoint(100, 100), type='QPoint')) def _get_unsaved_files(self): """Return an array with the path of the unsaved files.""" unsaved = [] files = self.opened_files for f in files: editable = self.__neditables.get(f) print("\n\neditable::", editable, getattr(editable, "editor", "-")) if editable is not None and editable.editor is not None and editable.editor.is_modified: unsaved.append(f) return unsaved def _save_unsaved_files(self, files): """Save the files from the paths in the array.""" for f in files: editable = self.get_or_create_editable(f) editable.ignore_checkers = True editable.save_content() def closeEvent(self, event): """Saves some global settings before closing.""" if self.s_listener: self.s_listener.close() main_container = self.get_service("main_container") unsaved_files = self._get_unsaved_files() if (settings.CONFIRM_EXIT and unsaved_files): txt = '\n'.join([nfile.file_name for nfile in unsaved_files]) val = QMessageBox.question( self, translations.TR_IDE_CONFIRM_EXIT_TITLE, (translations.TR_IDE_CONFIRM_EXIT_BODY % { 'files': txt }), QMessageBox.Yes | QMessageBox.No, QMessageBox.Cancel) if val == QMessageBox.Yes: #Saves all open files self._save_unsaved_files(unsaved_files) if val == QMessageBox.Cancel: event.ignore() return self.save_settings() self.goingDown.emit() #close python documentation server (if running) main_container.close_python_doc() #Shutdown PluginManager self.plugin_manager.shutdown() #completion_daemon.shutdown_daemon() super(IDE, self).closeEvent(event) def notify_plugin_errors(self): #TODO: Check if the Plugin Error dialog can be improved errors = self.plugin_manager.errors if errors: plugin_error_dialog = traceback_widget.PluginErrorDialog() for err_tuple in errors: plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1]) #show the dialog plugin_error_dialog.exec_() def show_message(self, message, duration=3000): """Show status message.""" self.notification.set_message(message, duration) self.notification.show() def show_plugins_store(self): """Open the Plugins Manager to install/uninstall plugins.""" store = plugins_store.PluginsStore(self) main_container = IDE.get_service("main_container") print("\nshow_plugins_store") if main_container: print("\nshow_plugins_store::main_container") main_container.show_dialog(store) else: store.show() def show_languages(self): """Open the Language Manager to install/uninstall languages.""" manager = language_manager.LanguagesManagerWidget(self) manager.show() def show_schemes(self): """Open the Schemes Manager to install/uninstall schemes.""" manager = schemes_manager.SchemesManagerWidget(self) manager.show() def show_about_qt(self): """Show About Qt Dialog.""" QMessageBox.aboutQt(self, translations.TR_ABOUT_QT) def show_about_ninja(self): """Show About NINJA-IDE Dialog.""" about = about_ninja.AboutNinja(self) about.show() def show_python_detection(self): """Show Python detection dialog for windows.""" #TODO: Notify the user when no python version could be found suggested = settings.detect_python_path() if suggested: dialog = python_detect_dialog.PythonDetectDialog(suggested, self) dialog.show()
class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. got_args: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) got_invalid_data = pyqtSignal() def __init__(self, socketname, parent=None): """Start the IPC server and listen to commands. Args: socketname: The socketname to use. parent: The parent to be used. """ super().__init__(parent) self.ignored = False self._socketname = socketname self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) if utils.is_windows: # pragma: no cover self._atime_timer = None else: self._atime_timer = usertypes.Timer(self, 'ipc-atime') self._atime_timer.setInterval(ATIME_INTERVAL) self._atime_timer.timeout.connect(self.update_atime) self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) self._server.newConnection.connect( # type: ignore[attr-defined] self.handle_connection) self._socket = None self._old_socket = None if utils.is_windows: # pragma: no cover # As a WORKAROUND for a Qt bug, we can't use UserAccessOption on Unix. If we # do, we don't get an AddressInUseError anymore: # https://bugreports.qt.io/browse/QTBUG-48635 # # Thus, we only do so on Windows, and handle permissions manually in # listen() on Linux. log.ipc.debug("Calling setSocketOptions") self._server.setSocketOptions(QLocalServer.UserAccessOption) else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") 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 listen(self): """Start listening on self._socketname.""" log.ipc.debug("Listening as {}".format(self._socketname)) if self._atime_timer is not None: # pragma: no branch self._atime_timer.start() self._remove_server() ok = self._server.listen(self._socketname) if not ok: if self._server.serverError() == QAbstractSocket.AddressInUseError: raise AddressInUseError(self._server) raise ListenError(self._server) if not utils.is_windows: # pragma: no cover # WORKAROUND for QTBUG-48635, see the comment in __init__ for details. try: os.chmod(self._server.fullServerName(), 0o700) except FileNotFoundError: # https://github.com/qutebrowser/qutebrowser/issues/1530 # The server doesn't actually exist even if ok was reported as # True, so report this as an error. raise ListenError(self._server) @pyqtSlot('QLocalSocket::LocalSocketError') def on_error(self, err): """Raise SocketError on fatal errors.""" if self._socket is None: # Sometimes this gets called from stale sockets. log.ipc.debug("In on_error with None socket!") return self._timer.stop() log.ipc.debug("Socket 0x{:x}: error {}: {}".format( id(self._socket), self._socket.error(), self._socket.errorString())) if err != QLocalSocket.PeerClosedError: raise SocketError("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one (0x{:x}).".format( id(self._socket))) return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug( # type: ignore[unreachable] "No new connection to handle.") return log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) self._socket = socket self._timer.start() socket.readyRead.connect( # type: ignore[attr-defined] self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) # type: ignore[attr-defined] if socket.error() not in [ QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError ]: log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect( # type: ignore[attr-defined] self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected from socket 0x{:x}.".format( id(self._socket))) self._timer.stop() if self._old_socket is not None: self._old_socket.deleteLater() self._old_socket = self._socket self._socket = None # Maybe another connection is waiting. self.handle_connection() def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" assert self._socket is not None log.ipc.error("Ignoring invalid IPC data from socket 0x{:x}.".format( id(self._socket))) self.got_invalid_data.emit() self._socket.error.connect(self.on_error) self._socket.disconnectFromServer() def _handle_data(self, data): """Handle data (as bytes) we got from on_ready_read.""" try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("invalid utf-8: {!r}".format(binascii.hexlify(data))) self._handle_invalid_data() return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) self._handle_invalid_data() return for name in ['args', 'target_arg']: if name not in json_data: log.ipc.error("Missing {}: {}".format(name, decoded.strip())) self._handle_invalid_data() return try: protocol_version = int(json_data['protocol_version']) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) self._handle_invalid_data() return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, got {}".format( PROTOCOL_VERSION, protocol_version)) self._handle_invalid_data() return args = json_data['args'] target_arg = json_data['target_arg'] if target_arg is None: # https://www.riverbankcomputing.com/pipermail/pyqt/2016-April/037375.html target_arg = '' cwd = json_data.get('cwd', '') assert cwd is not None self.got_args.emit(args, target_arg, cwd) @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # pragma: no cover # This happens when doing a connection while another one is already # active for some reason. if self._old_socket is None: log.ipc.warning("In on_ready_read with None socket and " "old_socket!") return log.ipc.debug("In on_ready_read with None socket!") socket = self._old_socket else: socket = self._socket if sip.isdeleted(socket): # pragma: no cover log.ipc.warning("Ignoring deleted IPC socket") return self._timer.stop() while socket is not None and socket.canReadLine(): data = bytes(socket.readLine()) self.got_raw.emit(data) log.ipc.debug("Read from socket 0x{:x}: {!r}".format( id(socket), data)) self._handle_data(data) if self._socket is not None: self._timer.start() @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" assert self._socket is not None log.ipc.error("IPC connection timed out " "(socket 0x{:x}).".format(id(self._socket))) self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.waitForDisconnected(CONNECT_TIMEOUT) if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.abort() @pyqtSlot() def update_atime(self): """Update the atime of the socket file all few hours. From the XDG basedir spec: To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file. """ path = self._server.fullServerName() if not path: log.ipc.error("In update_atime with no server path!") return log.ipc.debug("Touching {}".format(path)) try: os.utime(path) except OSError: log.ipc.exception("Failed to update IPC socket, trying to " "re-listen...") self._server.close() self.listen() @pyqtSlot() def shutdown(self): """Shut down the IPC server cleanly.""" log.ipc.debug("Shutting down IPC (socket 0x{:x})".format( id(self._socket))) if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: self._atime_timer.timeout.disconnect(self.update_atime) except TypeError: pass self._server.close() self._server.deleteLater() self._remove_server()
class QtSingleApplication(QApplication): """ This class makes sure that we can only start one Tribler application. When a user tries to open a second Tribler instance, the current active one will be brought to front. """ messageReceived = pyqtSignal(unicode) 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') def close(self): logfunc = logging.info logfunc(sys._getframe().f_code.co_name + '()') if self._inSocket: self._inSocket.disconnectFromServer() if self._outSocket: self._outSocket.disconnectFromServer() if self._server: self._server.close() logfunc(sys._getframe().f_code.co_name + '(): returning') def is_running(self): return self._isRunning def get_id(self): return self._id def activation_window(self): return self._activation_window def set_activation_window(self, activation_window, activate_on_message=True): self._activation_window = activation_window self._activate_on_message = activate_on_message def activate_window(self): if not self._activation_window: return self._activation_window.setWindowState( self._activation_window.windowState() & ~Qt.WindowMinimized) self._activation_window.raise_() def send_message(self, msg): if not self._outStream: return False self._outStream << msg << '\n' self._outStream.flush() return self._outSocket.waitForBytesWritten() def _on_new_connection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._on_ready_read) 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._on_ready_read) if self._activate_on_message: self.activate_window() def _on_ready_read(self): while True: msg = self._inStream.readLine() if not msg: break self.messageReceived.emit(msg)
def qlocalserver(qapp): server = QLocalServer() yield server server.close() server.deleteLater()
class SingleApplication(QApplication): messageReceived = pyqtSignal(str) 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 close(self): if self._inSocket: self._inSocket.disconnectFromServer() if self._outSocket: self._outSocket.disconnectFromServer() if self._server: self._server.close() @staticmethod def getSettingsPath() -> str: if sys.platform == 'win32': settings_path = os.path.join(QDir.homePath(), 'AppData', 'Local', 'vidcutter') elif sys.platform == 'darwin': settings_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', 'vidcutter') else: if QFileInfo(__file__).absolutePath().startswith('/app/'): settings_path = QProcessEnvironment.systemEnvironment().value( 'XDG_CONFIG_HOME', '') if not len(settings_path): settings_path = os.path.join(QDir.homePath(), '.var', 'app', vidcutter.__desktopid__, 'config') else: settings_path = os.path.join(QDir.homePath(), '.config', 'vidcutter') return os.path.join(settings_path, 'vidcutter.ini') def isRunning(self): return self._isRunning def appid(self): return self._appid 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, msg): if not self._outStream: return False # noinspection PyUnresolvedReferences 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._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)
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)
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)
class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. """ def __init__(self, parent=None): """Start the IPC server and listen to commands.""" super().__init__(parent) self.ignored = False self._remove_server() self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) self._server = QLocalServer(self) ok = self._server.listen(SOCKETNAME) if not ok: raise IPCError("Error while listening to IPC server: {} " "(error {})".format(self._server.errorString(), self._server.serverError())) self._server.newConnection.connect(self.handle_connection) self._socket = None def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(SOCKETNAME) if not ok: raise IPCError( "Error while removing server {}!".format(SOCKETNAME)) @pyqtSlot(int) def on_error(self, error): """Convenience method which calls _socket_error on an error.""" self._timer.stop() log.ipc.debug("Socket error {}: {}".format(self._socket.error(), self._socket.errorString())) if error != QLocalSocket.PeerClosedError: _socket_error("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one.") return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug("No new connection to handle.") return log.ipc.debug("Client connected.") self._timer.start() self._socket = socket socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) if socket.error() not in (QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError): log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected.") self._timer.stop() self._socket.deleteLater() self._socket = None # Maybe another connection is waiting. self.handle_connection() @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # this happened once and I don't know why log.ipc.warn("In on_ready_read with None socket!") return self._timer.start() while self._socket is not None and self._socket.canReadLine(): data = bytes(self._socket.readLine()) log.ipc.debug("Read from socket: {}".format(data)) try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("invalid data: {}".format( binascii.hexlify(data))) return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("invalid json: {}".format(decoded.strip())) return try: args = json_data['args'] except KeyError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("no args: {}".format(decoded.strip())) return cwd = json_data.get('cwd', None) app = objreg.get('app') app.process_args(args, via_ipc=True, cwd=cwd) @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" log.ipc.error("IPC connection timed out.") self._socket.close() def shutdown(self): """Shut down the IPC server cleanly.""" if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() self._server.close() self._server.deleteLater() self._remove_server()
class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. _socketname: The socketname to use. _socketopts_ok: Set if using setSocketOptions is working with this OS/Qt version. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. got_args: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) got_invalid_data = pyqtSignal() def __init__(self, socketname, parent=None): """Start the IPC server and listen to commands. Args: socketname: The socketname to use. parent: The parent to be used. """ super().__init__(parent) self.ignored = False self._socketname = socketname self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) if os.name == 'nt': # pragma: no coverage self._atime_timer = None else: self._atime_timer = usertypes.Timer(self, 'ipc-atime') self._atime_timer.setInterval(ATIME_INTERVAL) self._atime_timer.timeout.connect(self.update_atime) self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) self._server.newConnection.connect(self.handle_connection) self._socket = None self._socketopts_ok = os.name == 'nt' if self._socketopts_ok: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening... log.ipc.debug("Calling setSocketOptions") self._server.setSocketOptions(QLocalServer.UserAccessOption) else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") 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 listen(self): """Start listening on self._socketname.""" log.ipc.debug("Listening as {}".format(self._socketname)) if self._atime_timer is not None: # pragma: no branch self._atime_timer.start() self._remove_server() ok = self._server.listen(self._socketname) if not ok: if self._server.serverError() == QAbstractSocket.AddressInUseError: raise AddressInUseError(self._server) else: raise ListenError(self._server) if not self._socketopts_ok: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening. # (see b135569d5c6e68c735ea83f42e4baf51f7972281) # # Also, we don't get an AddressInUseError with Qt 5.5: # https://bugreports.qt.io/browse/QTBUG-48635 # # This means we only use setSocketOption on Windows... os.chmod(self._server.fullServerName(), 0o700) @pyqtSlot(int) def on_error(self, err): """Raise SocketError on fatal errors.""" if self._socket is None: # Sometimes this gets called from stale sockets. log.ipc.debug("In on_error with None socket!") return self._timer.stop() log.ipc.debug("Socket error {}: {}".format(self._socket.error(), self._socket.errorString())) if err != QLocalSocket.PeerClosedError: raise SocketError("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one.") return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug("No new connection to handle.") return log.ipc.debug("Client connected.") self._timer.start() self._socket = socket socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) if socket.error() not in (QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError): log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected.") self._timer.stop() if self._socket is None: log.ipc.debug("In on_disconnected with None socket!") else: self._socket.deleteLater() self._socket = None # Maybe another connection is waiting. self.handle_connection() def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" log.ipc.error("Ignoring invalid IPC data.") self.got_invalid_data.emit() self._socket.error.connect(self.on_error) self._socket.disconnectFromServer() @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # This happens when doing a connection while another one is already # active for some reason. log.ipc.warning("In on_ready_read with None socket!") return self._timer.start() while self._socket is not None and self._socket.canReadLine(): data = bytes(self._socket.readLine()) self.got_raw.emit(data) log.ipc.debug("Read from socket: {}".format(data)) try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("invalid utf-8: {}".format( binascii.hexlify(data))) self._handle_invalid_data() return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) self._handle_invalid_data() return for name in ('args', 'target_arg'): if name not in json_data: log.ipc.error("Missing {}: {}".format( name, decoded.strip())) self._handle_invalid_data() return try: protocol_version = int(json_data['protocol_version']) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) self._handle_invalid_data() return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, " "got {}".format(PROTOCOL_VERSION, protocol_version)) self._handle_invalid_data() return cwd = json_data.get('cwd', None) self.got_args.emit(json_data['args'], json_data['target_arg'], cwd) @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" log.ipc.error("IPC connection timed out.") self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.waitForDisconnected(CONNECT_TIMEOUT) if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.abort() @pyqtSlot() def update_atime(self): """Update the atime of the socket file all few hours. From the XDG basedir spec: To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file. """ path = self._server.fullServerName() if not path: log.ipc.error("In update_atime with no server path!") return log.ipc.debug("Touching {}".format(path)) os.utime(path) def shutdown(self): """Shut down the IPC server cleanly.""" log.ipc.debug("Shutting down IPC") if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: self._atime_timer.timeout.disconnect(self.update_atime) except TypeError: pass self._server.close() self._server.deleteLater() self._remove_server()
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 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_()
class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. """ def __init__(self, parent=None): """Start the IPC server and listen to commands.""" super().__init__(parent) self.ignored = False self._remove_server() self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) self._server = QLocalServer(self) ok = self._server.listen(SOCKETNAME) if not ok: raise IPCError("Error while listening to IPC server: {} " "(error {})".format(self._server.errorString(), self._server.serverError())) self._server.newConnection.connect(self.handle_connection) self._socket = None def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(SOCKETNAME) if not ok: raise IPCError("Error while removing server {}!".format( SOCKETNAME)) @pyqtSlot(int) def on_error(self, error): """Convenience method which calls _socket_error on an error.""" self._timer.stop() log.ipc.debug("Socket error {}: {}".format( self._socket.error(), self._socket.errorString())) if error != QLocalSocket.PeerClosedError: _socket_error("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one.") return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug("No new connection to handle.") return log.ipc.debug("Client connected.") self._timer.start() self._socket = socket socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) if socket.error() not in (QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError): log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected.") self._timer.stop() self._socket.deleteLater() self._socket = None # Maybe another connection is waiting. self.handle_connection() @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # this happened once and I don't know why log.ipc.warn("In on_ready_read with None socket!") return self._timer.start() while self._socket is not None and self._socket.canReadLine(): data = bytes(self._socket.readLine()) log.ipc.debug("Read from socket: {}".format(data)) try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("invalid data: {}".format( binascii.hexlify(data))) return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("invalid json: {}".format(decoded.strip())) return try: args = json_data['args'] except KeyError: log.ipc.error("Ignoring invalid IPC data.") log.ipc.debug("no args: {}".format(decoded.strip())) return cwd = json_data.get('cwd', None) app = objreg.get('app') app.process_pos_args(args, via_ipc=True, cwd=cwd) @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" log.ipc.error("IPC connection timed out.") self._socket.close() def shutdown(self): """Shut down the IPC server cleanly.""" if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() self._server.close() self._server.deleteLater() self._remove_server()
class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. _socketname: The socketname to use. _socketopts_ok: Set if using setSocketOptions is working with this OS/Qt version. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. got_args: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) got_invalid_data = pyqtSignal() def __init__(self, socketname, parent=None): """Start the IPC server and listen to commands. Args: socketname: The socketname to use. parent: The parent to be used. """ super().__init__(parent) self.ignored = False self._socketname = socketname self._timer = usertypes.Timer(self, "ipc-timeout") self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) if os.name == "nt": # pragma: no coverage self._atime_timer = None else: self._atime_timer = usertypes.Timer(self, "ipc-atime") self._atime_timer.setInterval(ATIME_INTERVAL) self._atime_timer.timeout.connect(self.update_atime) self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) self._server.newConnection.connect(self.handle_connection) self._socket = None self._socketopts_ok = os.name == "nt" if self._socketopts_ok: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening... log.ipc.debug("Calling setSocketOptions") self._server.setSocketOptions(QLocalServer.UserAccessOption) else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") 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 listen(self): """Start listening on self._socketname.""" log.ipc.debug("Listening as {}".format(self._socketname)) if self._atime_timer is not None: # pragma: no branch self._atime_timer.start() self._remove_server() ok = self._server.listen(self._socketname) if not ok: if self._server.serverError() == QAbstractSocket.AddressInUseError: raise AddressInUseError(self._server) else: raise ListenError(self._server) if not self._socketopts_ok: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening. # (see b135569d5c6e68c735ea83f42e4baf51f7972281) # # Also, we don't get an AddressInUseError with Qt 5.5: # https://bugreports.qt.io/browse/QTBUG-48635 # # This means we only use setSocketOption on Windows... os.chmod(self._server.fullServerName(), 0o700) @pyqtSlot(int) def on_error(self, err): """Raise SocketError on fatal errors.""" if self._socket is None: # Sometimes this gets called from stale sockets. log.ipc.debug("In on_error with None socket!") return self._timer.stop() log.ipc.debug("Socket error {}: {}".format(self._socket.error(), self._socket.errorString())) if err != QLocalSocket.PeerClosedError: raise SocketError("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one.") return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug("No new connection to handle.") return log.ipc.debug("Client connected.") self._timer.start() self._socket = socket socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) if socket.error() not in (QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError): log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected.") self._timer.stop() if self._socket is None: log.ipc.debug("In on_disconnected with None socket!") else: self._socket.deleteLater() self._socket = None # Maybe another connection is waiting. self.handle_connection() def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" log.ipc.error("Ignoring invalid IPC data.") self.got_invalid_data.emit() self._socket.error.connect(self.on_error) self._socket.disconnectFromServer() @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # This happens when doing a connection while another one is already # active for some reason. log.ipc.warning("In on_ready_read with None socket!") return self._timer.start() while self._socket is not None and self._socket.canReadLine(): data = bytes(self._socket.readLine()) self.got_raw.emit(data) log.ipc.debug("Read from socket: {}".format(data)) try: decoded = data.decode("utf-8") except UnicodeDecodeError: log.ipc.error("invalid utf-8: {}".format(binascii.hexlify(data))) self._handle_invalid_data() return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) self._handle_invalid_data() return for name in ("args", "target_arg"): if name not in json_data: log.ipc.error("Missing {}: {}".format(name, decoded.strip())) self._handle_invalid_data() return try: protocol_version = int(json_data["protocol_version"]) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) self._handle_invalid_data() return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, " "got {}".format(PROTOCOL_VERSION, protocol_version)) self._handle_invalid_data() return cwd = json_data.get("cwd", None) self.got_args.emit(json_data["args"], json_data["target_arg"], cwd) @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" log.ipc.error("IPC connection timed out.") self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.waitForDisconnected(CONNECT_TIMEOUT) if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.abort() @pyqtSlot() def update_atime(self): """Update the atime of the socket file all few hours. From the XDG basedir spec: To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file. """ path = self._server.fullServerName() if not path: log.ipc.error("In update_atime with no server path!") return log.ipc.debug("Touching {}".format(path)) os.utime(path) def shutdown(self): """Shut down the IPC server cleanly.""" log.ipc.debug("Shutting down IPC") if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: self._atime_timer.timeout.disconnect(self.update_atime) except TypeError: pass self._server.close() self._server.deleteLater() self._remove_server()
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 QtSingleApplication(QApplication): """ This class makes sure that we can only start one Tribler application. When a user tries to open a second Tribler instance, the current active one will be brought to front. """ messageReceived = pyqtSignal(str) 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') def close(self): logfunc = logging.info logfunc(sys._getframe().f_code.co_name + '()') if self._inSocket: self._inSocket.disconnectFromServer() if self._outSocket: self._outSocket.disconnectFromServer() if self._server: self._server.close() logfunc(sys._getframe().f_code.co_name + '(): returning') def is_running(self): return self._isRunning def get_id(self): return self._id def activation_window(self): return self._activation_window def set_activation_window(self, activation_window, activate_on_message=True): self._activation_window = activation_window self._activate_on_message = activate_on_message def activate_window(self): if not self._activation_window: return self._activation_window.setWindowState(self._activation_window.windowState() & ~Qt.WindowMinimized) self._activation_window.raise_() def send_message(self, msg): if not self._outStream: return False self._outStream << msg << '\n' self._outStream.flush() return self._outSocket.waitForBytesWritten() def _on_new_connection(self): if self._inSocket: self._inSocket.readyRead.disconnect(self._on_ready_read) 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._on_ready_read) if self._activate_on_message: self.activate_window() def _on_ready_read(self): while True: msg = self._inStream.readLine() if not msg: break self.messageReceived.emit(msg)
class _s_IDE(QMainWindow): ############################################################################### # SIGNALS # # goingDown() ############################################################################### goingDown = pyqtSignal() def __init__(self, start_server=False): super(_s_IDE, self).__init__() self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') self.setMinimumSize(700, 500) #Load the size and the position of the main window self.load_window_geometry() self.__project_to_open = 0 #Start server if needed self.s_listener = None if start_server: self.s_listener = QLocalServer() self.s_listener.listen("ninja_ide") self.s_listener.newConnection.connect(self._process_connection) #Profile handler self.profile = None #Opacity self.opacity = settings.MAX_OPACITY #Define Actions object before the UI self.actions = actions.Actions() #StatusBar self.status = status_bar.StatusBar(self) self.status.hide() self.setStatusBar(self.status) #Main Widget - Create first than everything else self.central = central_widget.CentralWidget(self) self.load_ui(self.central) self.setCentralWidget(self.central) #ToolBar self.toolbar = QToolBar(self) self.toolbar.setToolTip(_translate("_s_IDE", "Press and Drag to Move")) self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) self.addToolBar(settings.TOOLBAR_AREA, self.toolbar) if settings.HIDE_TOOLBAR: self.toolbar.hide() #Install Shortcuts after the UI has been initialized self.actions.install_shortcuts(self) self.mainContainer.currentTabChanged[str].connect(self.actions.update_explorer) #Menu menubar = self.menuBar() file_ = menubar.addMenu(_translate("_s_IDE", "&File")) edit = menubar.addMenu(_translate("_s_IDE", "&Edit")) view = menubar.addMenu(_translate("_s_IDE", "&View")) source = menubar.addMenu(_translate("_s_IDE", "&Source")) project = menubar.addMenu(_translate("_s_IDE", "&Project")) self.pluginsMenu = menubar.addMenu(_translate("_s_IDE", "&Addins")) about = menubar.addMenu(_translate("_s_IDE", "Abou&t")) #The order of the icons in the toolbar is defined by this calls self._menuFile = menu_file.MenuFile(file_, self.toolbar, self) self._menuView = menu_view.MenuView(view, self.toolbar, self) self._menuEdit = menu_edit.MenuEdit(edit, self.toolbar) self._menuSource = menu_source.MenuSource(source) self._menuProject = menu_project.MenuProject(project, self.toolbar) self._menuPlugins = menu_plugins.MenuPlugins(self.pluginsMenu) self._menuAbout = menu_about.MenuAbout(about) self.load_toolbar() #Plugin Manager services = { 'editor': plugin_services.MainService(), 'toolbar': plugin_services.ToolbarService(self.toolbar), 'menuApp': plugin_services.MenuAppService(self.pluginsMenu), 'explorer': plugin_services.ExplorerService(), 'misc': plugin_services.MiscContainerService(self.misc)} serviceLocator = plugin_manager.ServiceLocator(services) self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS, serviceLocator) self.plugin_manager.discover() #load all plugins! self.plugin_manager.load_all() #Tray Icon self.trayIcon = updates.TrayIconUpdates(self) self.trayIcon.show() self._menuFile.openFile[str].connect(self.mainContainer.open_file) self.mainContainer.fileSaved[str].connect(self.show_status_message) self.mainContainer.recentTabsModified[list].connect(self._menuFile.update_recent_files)#'QStringList' self.mainContainer.currentTabChanged[str].connect(self.actions.update_migration_tips) self.mainContainer.updateFileMetadata.connect(self.actions.update_migration_tips) self.mainContainer.migrationAnalyzed.connect(self.actions.update_migration_tips) def _process_connection(self): connection = self.s_listener.nextPendingConnection() connection.waitForReadyRead() data = connection.readAll() connection.close() if data: files, projects = str(data).split(ipc.project_delimiter, 1) files = [(x.split(':')[0], int(x.split(':')[1])) for x in files.split(ipc.file_delimiter)] projects = projects.split(ipc.project_delimiter) self.load_session_files_projects(files, [], projects, None) def load_toolbar(self): self.toolbar.clear() toolbar_items = {} toolbar_items.update(self._menuFile.toolbar_items) toolbar_items.update(self._menuView.toolbar_items) toolbar_items.update(self._menuEdit.toolbar_items) toolbar_items.update(self._menuSource.toolbar_items) toolbar_items.update(self._menuProject.toolbar_items) for item in settings.TOOLBAR_ITEMS: if item == 'separator': self.toolbar.addSeparator() else: tool_item = toolbar_items.get(item, None) if tool_item is not None: self.toolbar.addAction(tool_item) #load action added by plugins, This is a special case when reload #the toolbar after save the preferences widget for toolbar_action in settings.get_toolbar_item_for_plugins(): self.toolbar.addAction(toolbar_action) def load_external_plugins(self, paths): for path in paths: self.plugin_manager.add_plugin_dir(path) #load all plugins! self.plugin_manager.discover() self.plugin_manager.load_all() def show_status_message(self, message): self.status.showMessage(message, 2000) def load_ui(self, centralWidget): #Set Application Font for ToolTips QToolTip.setFont(QFont(settings.FONT_FAMILY, 10)) #Create Main Container to manage Tabs self.mainContainer = main_container.MainContainer(self) self.mainContainer.currentTabChanged[str].connect(self.change_window_title) self.mainContainer.locateFunction[str, str, bool].connect(self.actions.locate_function) self.mainContainer.navigateCode[bool, int].connect(self.actions.navigate_code_history) self.mainContainer.addBackItemNavigation.connect(self.actions.add_back_item_navigation) self.mainContainer.updateFileMetadata.connect(self.actions.update_explorer) self.mainContainer.updateLocator[str].connect(self.actions.update_explorer) self.mainContainer.openPreferences.connect(self._show_preferences) self.mainContainer.dontOpenStartPage.connect(self._dont_show_start_page_again) self.mainContainer.currentTabChanged[str].connect(self.status.handle_tab_changed) # When close the last tab cleanup self.mainContainer.allTabsClosed.connect(self._last_tab_closed) # Update symbols self.mainContainer.updateLocator[str].connect(self.status.explore_file_code) #Create Explorer Panel self.explorer = explorer_container.ExplorerContainer(self) self.central.splitterCentralRotated.connect(self.explorer.rotate_tab_position) self.explorer.updateLocator.connect(self.status.explore_code) self.explorer.goToDefinition[int].connect(self.actions.editor_go_to_line) self.explorer.projectClosed[str].connect(self.actions.close_files_from_project) #Create Misc Bottom Container self.misc = misc_container.MiscContainer(self) self.mainContainer.findOcurrences[str].connect(self.misc.show_find_occurrences) centralWidget.insert_central_container(self.mainContainer) centralWidget.insert_lateral_container(self.explorer) centralWidget.insert_bottom_container(self.misc) if self.explorer.count() == 0: centralWidget.change_explorer_visibility(force_hide=True) self.mainContainer.cursorPositionChange[int, int].connect(self.central.lateralPanel.update_line_col) # TODO: Change current symbol on move #self.connect(self.mainContainer, #SIGNAL("cursorPositionChange(int, int)"), #self.explorer.update_current_symbol) self.mainContainer.enabledFollowMode[bool].connect(self.central.enable_follow_mode_scrollbar) if settings.SHOW_START_PAGE: self.mainContainer.show_start_page() def _last_tab_closed(self): """ Called when the last tasb is closed """ self.explorer.cleanup_tabs() def _show_preferences(self): pref = preferences.PreferencesWidget(self.mainContainer) pref.show() def _dont_show_start_page_again(self): settings.SHOW_START_PAGE = False qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) qsettings.beginGroup('preferences') qsettings.beginGroup('general') qsettings.setValue('showStartPage', settings.SHOW_START_PAGE) qsettings.endGroup() qsettings.endGroup() self.mainContainer.actualTab.close_tab() def load_session_files_projects(self, filesTab1, filesTab2, projects, current_file, recent_files=None): self.__project_to_open = len(projects) self.explorer.projectOpened[str].connect(self._set_editors_project_data) self.explorer.open_session_projects(projects, notIDEStart=False) self.mainContainer.open_files(filesTab1, notIDEStart=False) self.mainContainer.open_files(filesTab2, mainTab=False, notIDEStart=False) if current_file: self.mainContainer.open_file(current_file, notStart=False) if recent_files is not None: self._menuFile.update_recent_files(recent_files) def _set_editors_project_data(self): self.__project_to_open -= 1 if self.__project_to_open == 0: self.explorer.projectOpened[str].disconnect(self._set_editors_project_data) self.mainContainer.update_editor_project() def open_file(self, filename): if filename: self.mainContainer.open_file(filename) def open_project(self, project): if project: self.actions.open_project(project) def __get_profile(self): return self.profile def __set_profile(self, profileName): self.profile = profileName if self.profile is not None: self.setWindowTitle('NINJA-IDE (PROFILE: %s)' % self.profile) else: self.setWindowTitle( 'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') Profile = property(__get_profile, __set_profile) def change_window_title(self, title): if self.profile is None: self.setWindowTitle('NINJA-IDE - %s' % title) else: self.setWindowTitle('NINJA-IDE (PROFILE: %s) - %s' % ( self.profile, title)) currentEditor = self.mainContainer.get_actual_editor() if currentEditor is not None: line = currentEditor.textCursor().blockNumber() + 1 col = currentEditor.textCursor().columnNumber() self.central.lateralPanel.update_line_col(line, col) def wheelEvent(self, event): if event.modifiers() == Qt.ShiftModifier: if event.delta() == 120 and self.opacity < settings.MAX_OPACITY: self.opacity += 0.1 elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY: self.opacity -= 0.1 self.setWindowOpacity(self.opacity) event.ignore() else: QMainWindow.wheelEvent(self, event) def save_settings(self): """Save the settings before the application is closed with QSettings. Info saved: Tabs and projects opened, windows state(size and position). """ qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) editor_widget = self.mainContainer.get_actual_editor() current_file = '' if editor_widget is not None: current_file = editor_widget.ID if qsettings.value('preferences/general/loadFiles', True, type=bool): openedFiles = self.mainContainer.get_opened_documents() projects_obj = self.explorer.get_opened_projects() projects = [p.path for p in projects_obj] qsettings.setValue('openFiles/projects', projects) if len(openedFiles) > 0: qsettings.setValue('openFiles/mainTab', openedFiles[0]) if len(openedFiles) == 2: qsettings.setValue('openFiles/secondaryTab', openedFiles[1]) qsettings.setValue('openFiles/currentFile', current_file) qsettings.setValue('openFiles/recentFiles', self.mainContainer._tabMain.get_recent_files_list()) qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS) qsettings.setValue('preferences/editor/breakpoints', settings.BREAKPOINTS) qsettings.setValue('preferences/general/toolbarArea', self.toolBarArea(self.toolbar)) #Save if the windows state is maximixed if(self.isMaximized()): qsettings.setValue("window/maximized", True) else: qsettings.setValue("window/maximized", False) #Save the size and position of the mainwindow qsettings.setValue("window/size", self.size()) qsettings.setValue("window/pos", self.pos()) #Save the size of de splitters qsettings.setValue("window/central/areaSize", self.central.get_area_sizes()) qsettings.setValue("window/central/mainSize", self.central.get_main_sizes()) #Save the toolbar visibility if not self.toolbar.isVisible() and self.menuBar().isVisible(): qsettings.setValue("window/hide_toolbar", True) else: qsettings.setValue("window/hide_toolbar", False) #Save Misc state qsettings.setValue("window/show_misc", self.misc.isVisible()) #Save Profiles if self.profile is not None: self.actions.save_profile(self.profile) else: qsettings.setValue('ide/profiles', settings.PROFILES) def load_window_geometry(self): """Load from QSettings the window size of de Ninja IDE""" qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) if qsettings.value("window/maximized", True, type=bool): self.setWindowState(Qt.WindowMaximized) else: self.resize(qsettings.value("window/size", QSizeF(800, 600).toSize(), type='QSize')) self.move(qsettings.value("window/pos", QPointF(100, 100).toPoint(), type='QPoint')) def closeEvent(self, event): if self.s_listener: self.s_listener.close() if (settings.CONFIRM_EXIT and self.mainContainer.check_for_unsaved_tabs()): unsaved_files = self.mainContainer.get_unsaved_files() txt = '\n'.join(unsaved_files) val = QMessageBox.question(self, _translate("_s_IDE", "Some changes were not saved"), (_translate("_s_IDE", "%s\n\nDo you want to save them?") % txt), QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel) if val == QMessageBox.Yes: #Saves all open files self.mainContainer.save_all() if val == QMessageBox.Cancel: event.ignore() self.goingDown.emit() self.save_settings() completion_daemon.shutdown_daemon() #close python documentation server (if running) self.mainContainer.close_python_doc() #Shutdown PluginManager self.plugin_manager.shutdown() def notify_plugin_errors(self): errors = self.plugin_manager.errors if errors: plugin_error_dialog = traceback_widget.PluginErrorDialog() for err_tuple in errors: plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1]) #show the dialog plugin_error_dialog.exec_() def show_python_detection(self): suggested = settings.detect_python_path() if suggested: dialog = python_detect_dialog.PythonDetectDialog(suggested, self) dialog.show()