def _createContractionToolbar(self): toolbar = QToolBar(parent=self) toolbar.setFloatable(True) toolbar.setMovable(True) toolbar.hide() action = toolbar.addAction('next') action.triggered.connect(self.next_hg) action = toolbar.addAction('previous') action.triggered.connect(self.previous_hg) action = toolbar.addAction('stop') action.triggered.connect(self.stopContractionPlayerSlot) self.contraction_toolbar = toolbar
class PMGWebBrowser(QWidget): def __init__(self, parent=None, toolbar='standard'): """ :param parent: :param toolbar:多种选项:‘no’,‘standard’,'no_url_input' """ super().__init__(parent) self.webview = PMGWebEngineView() # self.webview.load(QUrl("https://cn.bing.com")) self.setLayout(QVBoxLayout()) self.toolbar = QToolBar() self.url_input = QLineEdit() # self.url_input.setText('https://cn.bing.com') # self.load_url() self.toolbar.addWidget(self.url_input) self.toolbar.addAction('go').triggered.connect( lambda b: self.load_url()) self.toolbar.addAction('back').triggered.connect(self.webview.back) self.toolbar.addAction('forward').triggered.connect( self.webview.forward) self.layout().addWidget(self.toolbar) if toolbar == 'no': self.toolbar.hide() elif toolbar == 'no_url_input': self.url_input.hide() self.layout().addWidget(self.webview) self.setWindowTitle('My Browser') self.showMaximized() # command:> # jupyter notebook --port 5000 --no-browser --ip='*' --NotebookApp.token='' # --NotebookApp.password='' c:\users\12957\ # self.webview.load(QUrl("http://127.0.0.1:5000/notebooks/desktop/Untitled.ipynb")) # 直接请求页面。 # self.webview.load(QUrl("E:\Python\pyminer_bin\PyMiner\bin\pmgwidgets\display\browser\show_formula.html")) # 直接请求页面。 # self.setCentralWidget(self.webview) def load_url(self, url: str = ''): if url == '': url = self.url_input.text().strip() # print('',url) else: self.url_input.setText(url) self.webview.load(QUrl(url))
class ToolbarUI(object): def __init__(self, main_window: QMainWindow): """ 工具导航 外观模式 :param main_window: """ self.main_window = main_window self.toolbar = QToolBar(self.main_window) # 子类 self.ui_list = [] self.server_start_ui = ServerStartUI(self.toolbar) # 服务器开关 self.file_manager_ui = FileManagerUI(self.toolbar) # 文件管理 self.terminal_ui = TerminalUI(self.toolbar) # 远程终端 self.remote_control_ui = RemoteControlUI(self.toolbar) # 远程控制 self.video_monitor_ui = VideoMonitorUI(self.toolbar) # 视频监控 self.voice_monitor_ui = VoiceMonitorUI(self.toolbar) # 语音监控 self.keyboard_ui = KeyboardUI(self.toolbar) # 键盘记录 self.make_client_ui = MakeClientUI(self.toolbar) # 创建客户端 self.service_manager_ui = ServiceManagerUI(self.toolbar) # 服务管理 self.exit_ui = ExitUI(self.toolbar, self.main_window) # 退出程序 # 子类信号 self.connect_list = [] self.server_start_connect = ServerStartConnect(self.server_start_ui) def options(self) -> None: """ 参数设置 :return: """ self.toolbar.setObjectName("toolBar") # 设置是否可以移动 self.toolbar.setMovable(True) # 设置是否可以悬浮在主窗口 self.toolbar.setFloatable(True) # 设置图标尺寸 self.toolbar.setIconSize(QSize(25, 25)) # 字体在右边 # self.tools_main.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # 字体在下面 if settings.LOAD_EFFECT_ON: self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) load_animation.load_animation(self.toolbar) # 窗口添加工具导航栏 self.main_window.addToolBar(Qt.TopToolBarArea, self.toolbar) def setup_ui(self) -> None: self.options() if not settings.TOOLBAR_SHOW: self.toolbar.hide() self.load_ui() self.load_connect() self.show_ui() # QDockWidget 位置发生变动 self.toolbar.orientationChanged.connect(self.orientation_changed) def orientation_changed(self, event) -> None: """ 位置变动事件 :param event: :return: """ if event: pass if settings.LOAD_EFFECT_ON: load_animation.load_animation(self.toolbar) # noinspection PyArgumentList def retranslate_ui(self) -> None: self.toolbar.setWindowTitle(_translate("ToolbarUI", "工具导航")) def load_ui(self) -> None: """ 加载模块 :return: """ self.ui_list.append(self.server_start_ui) self.ui_list.append(self.file_manager_ui) self.ui_list.append(self.terminal_ui) self.ui_list.append(self.remote_control_ui) self.ui_list.append(self.video_monitor_ui) self.ui_list.append(self.voice_monitor_ui) self.ui_list.append(self.keyboard_ui) self.ui_list.append(self.make_client_ui) self.ui_list.append(self.service_manager_ui) self.ui_list.append(self.exit_ui) def load_connect(self) -> None: """ 加载 链接信号 :return: """ self.connect_list.append(self.server_start_connect) def show_ui(self) -> None: """ 显示数据 :return: """ all_list = self.ui_list + self.connect_list for item in all_list: item.setup_ui() item.retranslate_ui()
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 SMLSurveyor(object): def __init__(self, iface): # save reference to the QGIS interface self.iface = iface # get plugin directory self.plugin_dir = os.path.dirname(os.path.realpath(__file__)) self.uri = None self.connection = None self.crs = None self.database = None self.datetime = datetime.now() self.required_layers = [] # 1. beacons # 2. parcels self.required_layers.append( RequiredLayer('Beacon', 'Beacons', 'beacons', 'gid', 'points')) self.required_layers.append( RequiredLayer('Parcel', 'Parcels', 'parcels', 'parcel_id', 'polygons')) def initGui(self): """ Initialize gui """ # create plugin toolbar self.create_plugin_toolbar() def unload(self): """ Uninitialize gui """ # remove plugin toolbar self.remove_plugin_toolbar() # remove layers self.refresh_layers() for l in self.required_layers: if bool(l.layer): QgsProject.instance().removeMapLayers([l.layer.id()]) def create_plugin_toolbar(self): """ Create plugin toolbar to house buttons """ # create plugin toolbar self.plugin_toolbar = QToolBar('Parcel Plugin') self.plugin_toolbar.setObjectName('Parcel Plugin') # create Database Selection button self.select_database_action = QAction( QIcon(os.path.join(self.plugin_dir, "images", "database.png")), "Select Database Connection", self.iface.mainWindow()) self.select_database_action.setWhatsThis("Select database connection") self.select_database_action.setStatusTip("Select database connection") self.select_database_action.triggered.connect( self.manage_database_connection) # create Beardist button self.bearing_distance_action = QAction( QIcon(os.path.join(self.plugin_dir, "images", "beardist.png")), "Manage Bearings and Distances", self.iface.mainWindow()) self.bearing_distance_action.setWhatsThis( "Manage bearings and distances") self.bearing_distance_action.setStatusTip( "Manage bearings and distances") self.bearing_distance_action.triggered.connect( self.manage_bearing_distance) # create Beacons button self.beacons_action = QAction( QIcon(os.path.join(self.plugin_dir, "images", "beacon.gif")), "Manage Beacons", self.iface.mainWindow()) self.beacons_action.setWhatsThis("Manage beacons") self.beacons_action.setStatusTip("Manage beacons") self.beacons_action.triggered.connect(self.manage_beacons) # create Parcels button self.parcels_action = QAction( QIcon(os.path.join(self.plugin_dir, "images", "parcel.png")), "Manage Parcels", self.iface.mainWindow()) self.parcels_action.setWhatsThis("Manage parcels") self.parcels_action.setStatusTip("Manage parcels") self.parcels_action.triggered.connect(self.manage_parcels) # populate plugin toolbar self.plugin_toolbar.addAction(self.select_database_action) self.plugin_toolbar.addAction(self.bearing_distance_action) self.plugin_toolbar.addAction(self.beacons_action) self.plugin_toolbar.addAction(self.parcels_action) # add plugin toolbar to gui self.iface.mainWindow().addToolBar(self.plugin_toolbar) def remove_plugin_toolbar(self): """ Remove plugin toolbar which houses buttons """ # remove app toolbar from gui if hasattr(self, "pluginToolBar"): self.iface.mainWindow().removeToolBar(self.plugin_toolbar) self.plugin_toolbar.hide() def set_database_connection(self, connection=None, crs=None): """ Create a database connection """ # fetch settings settings_plugin = QSettings() settings_postgis = QSettings() settings_plugin.beginGroup('CoGo Plugin') settings_postgis.beginGroup('PostgreSQL/connections') self.connection = connection if not bool(self.connection): # fetch pre-chosen database connection self.connection = settings_plugin.value("DatabaseConnection", None) # check if still exists if bool(self.connection): if self.connection not in settings_postgis.childGroups(): settings_plugin.setValue("DatabaseConnection", "") self.connection = None # fetch from user if necessary if not bool(self.connection): dialog = DatabaseConnectionDialog() dialog.show() if bool(dialog.exec_()): self.connection = dialog.get_database_connection() if dialog.get_crs(): self.crs = QgsCoordinateReferenceSystem( dialog.get_crs().get('auth_id')) settings_plugin.setValue("DatabaseConnection", self.connection) # validate database connection if bool(self.connection): db_service = settings_postgis.value(self.connection + '/service') db_host = settings_postgis.value(self.connection + '/host') db_port = settings_postgis.value(self.connection + '/port') db_name = settings_postgis.value(self.connection + '/database') db_username = settings_postgis.value(self.connection + '/username') db_password = settings_postgis.value(self.connection + '/password') max_attempts = 3 self.uri = QgsDataSourceUri() self.uri.setConnection(db_host, db_port, db_name, db_username, db_password) if db_username and db_password: for i in range(max_attempts): error_message = self.connect_to_db(db_service, db_host, db_port, db_name, db_username, db_password) if error_message: ok, db_username, db_password = ( QgsCredentials.instance().get( self.uri.connectionInfo(), db_username, db_password, error_message)) if not ok: break else: break else: msg = "Please enter the username and password." for i in range(max_attempts): ok, db_username, db_password = ( QgsCredentials.instance().get( self.uri.connectionInfo(), db_username, db_password, msg)) if not ok: break error_message = self.connect_to_db(db_service, db_host, db_port, db_name, db_username, db_password) if not error_message: break settings_plugin.endGroup() settings_postgis.endGroup() def connect_to_db(self, service, host, port, name, username, password): username.replace(" ", "") password.replace(" ", "") try: self.database = database.Manager({ "CONNECTION": self.connection, "SERVICE": service, "HOST": host, "NAME": name, "PORT": port, "USER": username, "PASSWORD": password }) self.uri.setConnection(host, port, name, username, password) self.datetime = datetime.now() return None except Exception as e: self.database = None msg = "Invalid username and password." return msg def refresh_layers(self): """ Ensure all required layers exist """ if bool(self.database): # Comment out below section because connection name is better # to be a group name than crs name IMHO. # # first, we need to check the layer group for the crs used by # # current database # query = "SELECT Find_SRID('public', 'beacons', 'the_geom');" # self.database.connect(self.database.parameters) # cursor = self.database.cursor # cursor.execute(query) # crs_id = int(cursor.fetchall()[0][0]) # del cursor # group_name = None # for key, value in crs_options.iteritems(): # if value == crs_id: # group_name = key group_name = self.connection root = QgsProject.instance().layerTreeRoot() target_group = root.findGroup(group_name) if not bool(target_group): target_group = root.insertGroup(0, group_name) target_group.setItemVisibilityChecked(Qt.Checked) for required_layer in reversed(self.required_layers): for layer_node in target_group.findLayers(): layer = layer_node.layer() if required_layer.name_plural.lower() == \ layer.name().lower(): target_group.removeLayer(layer) for required_layer in self.required_layers: self.uri.setDataSource(required_layer.schema, required_layer.table, required_layer.geometry_column, '', required_layer.primary_key) added_layer = QgsVectorLayer(self.uri.uri(), required_layer.name_plural, "postgres") QgsProject.instance().addMapLayer(added_layer, False) target_group.addLayer(added_layer) for layer_node in target_group.findLayers(): layer = layer_node.layer() if required_layer.name_plural == layer.name(): required_layer.layer = layer layer_node.setItemVisibilityChecked(Qt.Checked) if self.crs: layer.setCrs(self.crs) self.iface.zoomToActiveLayer() def manage_beacons(self): """ Portal which enables the management of beacons """ if self.datetime.date() != datetime.now().date(): self.database = None if self.database is None: self.set_database_connection() if self.database is None: return BeaconManager(self.iface, self.database, self.required_layers) validate_plugin_actions(self, self.database) self.iface.mapCanvas().refresh() def manage_parcels(self): """ Portal which enables the management of parcels """ if self.datetime.date() != datetime.now().date(): self.database = None if self.database is None: self.set_database_connection() if self.database is None: return ParcelManager(self.iface, self.database, self.required_layers) validate_plugin_actions(self, self.database) self.iface.mapCanvas().refresh() def manage_bearing_distance(self): """ Portal which enables the management of bearings and distances """ if self.datetime.date() != datetime.now().date(): self.database = None if self.database is None: self.set_database_connection() if self.database is None: return result = validate_plugin_actions(self, self.database) if not result: QMessageBox.warning( None, "SML Surveyor", ("No Beacons available in the table. " "Please use Beacon Manager tool to create a Beacon.")) else: BearDistManager(self.iface, self.database, self.required_layers) self.iface.mapCanvas().refresh() def manage_database_connection(self): """ Action to select the db connection to work with. """ database_manager = DatabaseManager() connection = database_manager.get_current_connection() crs = database_manager.get_current_crs() if connection: self.set_database_connection(connection=connection, crs=crs) self.refresh_layers() if self.database: validate_plugin_actions(self, self.database)
class MainWindow(QMainWindow): """This is the main application window class it defines the GUI window for the browser """ def parse_config(self, file_config, options): self.config = {} options = vars(options) for key, metadata in CONFIG_OPTIONS.items(): options_val = options.get(key) file_val = file_config.get(key) env_val = os.environ.get(metadata.get("env", '')) default_val = metadata.get("default") vals = metadata.get("values") debug("key: {}, default: {}, file: {}, options: {}".format( key, default_val, file_val, options_val)) if vals: options_val = (options_val in vals and options_val) or None file_val = (file_val in vals and file_val) or None env_val = (env_val in vals and env_val) or None if metadata.get("is_file"): filename = options_val or env_val if not filename: self.config[key] = default_val else: try: with open(filename, 'r') as fh: self.config[key] = fh.read() except IOError: debug("Could not open file {} for reading.".format( filename)) self.config[key] = default_val else: set_values = [ val for val in (options_val, env_val, file_val) if val is not None ] if len(set_values) > 0: self.config[key] = set_values[0] else: self.config[key] = default_val if metadata.get("type") and self.config[key]: debug("{} cast to {}".format(key, metadata.get("type"))) self.config[key] = metadata.get("type")(self.config[key]) debug(repr(self.config)) def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered"): """Return a QAction given a number of common QAction attributes Just a shortcut function Originally borrowed from 'Rapid GUI Development with PyQT' by Mark Summerset """ action = QAction(text, self) if icon is not None: action.setIcon( QIcon.fromTheme(icon, QIcon(":/{}.png".format(icon)))) if shortcut is not None and not shortcut.isEmpty(): action.setShortcut(shortcut) tip += " ({})".format(shortcut.toString()) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.__getattr__(signal).connect(slot) if checkable: action.setCheckable() return action def __init__(self, options, parent=None): """Construct a MainWindow Object.""" super(MainWindow, self).__init__(parent) # Load config file self.setWindowTitle("Browser") debug("loading configuration from '{}'".format(options.config_file)) configfile = {} if options.config_file: configfile = yaml.safe_load(open(options.config_file, 'r')) self.parse_config(configfile, options) # self.popup will hold a reference to the popup window # if it gets opened self.popup = None # Stylesheet support if self.config.get("stylesheet"): try: with open(self.config.get("stylesheet")) as ss: self.setStyleSheet(ss.read()) except: debug(('Problem loading stylesheet file "{}", ' 'using default style.').format( self.config.get("stylesheet"))) self.setObjectName("global") # Set proxy server environment variable before creating web views if self.config.get("proxy_server"): os.environ["http_proxy"] = self.config.get("proxy_server") os.environ["https_proxy"] = self.config.get("proxy_server") # If the whitelist is activated, add the bookmarks and start_url if self.config.get("whitelist"): # we can just specify whitelist = True, # which should whitelist just the start_url and bookmark urls. if type(self.config.get("whitelist")) is not list: self.whitelist = [] self.whitelist.append( str(QUrl(self.config.get("start_url")).host())) bookmarks = self.config.get("bookmarks") if bookmarks: self.whitelist += [ str(QUrl(b.get("url")).host()) for k, b in bookmarks.items() ] self.whitelist = set(self.whitelist) # uniquify and optimize debug("Generated whitelist: " + str(self.whitelist)) # create the web engine profile self.create_webprofile() # Now construct the UI self.build_ui() # ## END OF CONSTRUCTOR ## # def create_webprofile(self): """Create a webengineprofile to use in all views.""" if self.config.get("privacy_mode"): webprofile = QWebEngineProfile() else: webprofile = QWebEngineProfile.defaultProfile() debug("Browser session is private: {}".format( webprofile.isOffTheRecord())) if self.config.get("user_agent"): webprofile.setHttpUserAgent(self.config["user_agent"]) debug('Set user agent to "{}"'.format(webprofile.httpUserAgent())) self.webprofile = webprofile def build_ui(self): """Set up the user interface for the main window. Unlike the constructor, this method is re-run whenever the browser is "reset" by the user. """ debug("build_ui") inactivity_timeout = self.config.get("timeout") quit_button_tooltip = ("Click here to quit" if self.config.get('quit_button_mode') == 'close' else "Click here when you are done" "\nIt will clear your browsing history" " and return you to the start page.") qb_mode_callbacks = {'close': self.close, 'reset': self.reset_browser} to_mode_callbacks = { 'close': self.close, 'reset': self.reset_browser, 'screensaver': self.screensaver } self.screensaver_active = False # ##Start GUI configuration## # self.browser_window = AdmWebView(self.config, webprofile=self.webprofile) self.browser_window.setObjectName("web_content") self.setCentralWidget(self.browser_window) # Icon theme setting QIcon.setThemeName(self.config.get("icon_theme")) self.setCentralWidget(self.browser_window) debug("loading {}".format(self.config.get("start_url"))) self.browser_window.setUrl(QUrl(self.config.get("start_url"))) # Window size settings window_size = self.config.get("window_size", '').lower() if window_size == 'full': self.showFullScreen() elif window_size == 'max': self.showMaximized() elif window_size: size = re.match(r"(\d+)x(\d+)", window_size) if size: width, height = size.groups() self.setFixedSize(int(width), int(height)) else: debug('Ignoring invalid window size "{}"'.format(window_size)) # Set up the top navigation bar if it's configured to exist if self.config.get("navigation"): self.navigation_bar = QToolBar("Navigation") self.navigation_bar.setObjectName("navigation") self.addToolBar(Qt.TopToolBarArea, self.navigation_bar) self.navigation_bar.setMovable(False) self.navigation_bar.setFloatable(False) # Standard navigation tools self.nav_items = { "back": self.browser_window.pageAction(AdmWebPage.Back), "forward": self.browser_window.pageAction(AdmWebPage.Forward), "refresh": self.browser_window.pageAction(AdmWebPage.Reload), "stop": self.browser_window.pageAction(AdmWebPage.Stop), "quit": self.createAction( self.config.get("quit_button_text"), qb_mode_callbacks.get(self.config.get("quit_button_mode"), self.reset_browser), QKeySequence("Alt+F"), None, quit_button_tooltip), "zoom_in": self.createAction( "Zoom In", self.zoom_in, QKeySequence("Alt++"), "zoom-in", "Increase the size of the text and images on the page"), "zoom_out": self.createAction( "Zoom Out", self.zoom_out, QKeySequence("Alt+-"), "zoom-out", "Decrease the size of text and images on the page") } if self.config.get("allow_printing"): self.nav_items["print"] = self.createAction( "Print", self.browser_window.print_webpage, QKeySequence("Ctrl+p"), "document-print", "Print this page") # Add all the actions to the navigation bar. for item in self.config.get("navigation_layout"): if item == "separator": self.navigation_bar.addSeparator() elif item == "spacer": # an expanding spacer. spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.navigation_bar.addWidget(spacer) elif item == "bookmarks": # Insert bookmarks buttons here. self.bookmark_buttons = [] for bookmark in self.config.get("bookmarks", {}).items(): debug("Bookmark:\n" + bookmark.__str__()) # bookmark name will use the "name" attribute, # if present, or else just the key: bookmark_name = bookmark[1].get("name") or bookmark[0] # Create a button for the bookmark as a QAction, # which we'll add to the toolbar button = self.createAction( bookmark_name, (lambda url=bookmark[1].get( "url"): self.browser_window.load(QUrl(url))), QKeySequence.mnemonic(bookmark_name), None, bookmark[1].get("description")) self.navigation_bar.addAction(button) (self.navigation_bar.widgetForAction( button).setObjectName("navigation_button")) else: action = self.nav_items.get(item, None) if action: self.navigation_bar.addAction(action) (self.navigation_bar.widgetForAction( action).setObjectName("navigation_button")) # This removes the ability to toggle off the navigation bar: self.nav_toggle = self.navigation_bar.toggleViewAction() self.nav_toggle.setVisible(False) # End "if show_navigation is True" block # set hidden quit action # For reasons I haven't adequately ascertained, # this shortcut fails now and then claiming # "Ambiguous shortcut overload". # No idea why, as it isn't consistent. self.really_quit = self.createAction("", self.close, QKeySequence("Ctrl+Alt+Q"), None, "") self.addAction(self.really_quit) # Call a reset function after timeout if inactivity_timeout != 0: self.event_filter = InactivityFilter(inactivity_timeout) QCoreApplication.instance().installEventFilter(self.event_filter) self.browser_window.page().installEventFilter(self.event_filter) self.event_filter.timeout.connect( to_mode_callbacks.get(self.config.get("timeout_mode"), self.reset_browser)) else: self.event_filter = None # ##END OF UI SETUP## # def screensaver(self): """Enter "screensaver" mode This method puts the browser in screensaver mode, where a URL is displayed while the browser is idle. Activity causes the browser to return to the home screen. """ debug("screensaver started") self.screensaver_active = True if self.popup: self.popup.close() if self.config.get("navigation"): self.navigation_bar.hide() self.browser_window.setZoomFactor(self.config.get("zoom_factor")) self.browser_window.load(QUrl(self.config.get("screensaver_url"))) self.event_filter.timeout.disconnect() self.event_filter.activity.connect(self.reset_browser) def reset_browser(self): """Clear the history and reset the UI. Called whenever the inactivity filter times out, or when the user clicks the "finished" button in 'reset' mode. """ # Clear out the memory cache #QWebEngineSettings.clearMemoryCaches() self.browser_window.history().clear() # self.navigation_bar.clear() doesn't do its job, # so remove the toolbar first, then rebuild the UI. debug("RESET BROWSER") if self.event_filter: self.event_filter.blockSignals(True) if self.screensaver_active is True: self.screensaver_active = False self.event_filter.activity.disconnect() if self.event_filter: self.event_filter.blockSignals(False) if hasattr(self, "navigation_bar"): self.removeToolBar(self.navigation_bar) self.build_ui() def zoom_in(self): """Zoom in action callback. Note that we cap zooming in at a factor of 3x. """ if self.browser_window.zoomFactor() < 3.0: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() + 0.1) self.nav_items["zoom_out"].setEnabled(True) else: self.nav_items["zoom_in"].setEnabled(False) def zoom_out(self): """Zoom out action callback. Note that we cap zooming out at 0.1x. """ if self.browser_window.zoomFactor() > 0.1: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() - 0.1) self.nav_items["zoom_in"].setEnabled(True) else: self.nav_items["zoom_out"].setEnabled(False)
class MainWindow(QtWidgets.QMainWindow): MaxRecentFiles = 5 def __init__(self, app_title="CSV Viewer"): super().__init__() self.progress = QProgressBar() self.threadpool = QThreadPool() self.app_title = app_title self.setMinimumSize(600, 300) self.df = None self.round_num = 2 self.recentFileActs = [] # settings self.settings = QtCore.QSettings('CSV_Viewer', 'CSV_Viewer') self.round_num = self.settings.value('round_numbers', self.round_num, int) # toolbar self.toolbar = QToolBar("MainToolbar") self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.addToolBar(self.toolbar) # open action style = self.toolbar.style() icon = style.standardIcon(QStyle.SP_FileDialogStart) self.button_open = QAction(icon, "Open", self) self.button_open.setShortcut('Ctrl+O') self.button_open.setStatusTip("Open CSV file...") self.button_open.triggered.connect(self.onToolbarOpenButtonClick) self.toolbar.addAction(self.button_open) self.button_open.setEnabled(True) # summary action style_summary = self.toolbar.style() icon = style_summary.standardIcon(QStyle.SP_FileDialogListView) self.button_summary = QAction(icon, "Summary", self) self.button_summary.setShortcut('Ctrl+S') self.button_summary.setStatusTip("Show summary for the current file") self.button_summary.triggered.connect(self.onToolbarSummaryButtonClick) self.toolbar.addAction(self.button_summary) # info action style_info = self.toolbar.style() icon = style_info.standardIcon(QStyle.SP_FileIcon) self.button_info = QAction(icon, "Info", self) self.button_info.setShortcut('Ctrl+I') self.button_info.setStatusTip("Show summary for the current file") self.button_info.triggered.connect(self.onToolbarInfoButtonClick) self.toolbar.addAction(self.button_info) # resize action style_resize = self.toolbar.style() icon = style_resize.standardIcon(QStyle.SP_BrowserReload) self.button_resize = QAction(icon, "Resize columns", self) self.button_resize.setShortcut('Ctrl+R') self.button_resize.setStatusTip("Resize columns width to content") self.button_resize.triggered.connect(self.onResizeColumns) self.toolbar.addAction(self.button_resize) # export to xlsx action style_xlsx = self.toolbar.style() icon = style_xlsx.standardIcon(QStyle.SP_FileLinkIcon) self.button_xlsx = QAction(icon, "Xlsx", self) self.button_xlsx.setStatusTip("Export data to xlsx file") self.button_xlsx.triggered.connect(self.onExportXlsx) self.toolbar.addAction(self.button_xlsx) # export to sqlite action style_sqlite = self.toolbar.style() icon = style_sqlite.standardIcon(QStyle.SP_FileLinkIcon) self.button_sqlite = QAction(icon, "SQLite", self) self.button_sqlite.setStatusTip("Export data to SQLite database") self.button_sqlite.triggered.connect(self.onExportSQLite) self.toolbar.addAction(self.button_sqlite) # export to html action style_html = self.toolbar.style() icon = style_html.standardIcon(QStyle.SP_FileLinkIcon) self.button_html = QAction(icon, "HTML", self) self.button_html.setStatusTip("Export data to HTML file") self.button_html.triggered.connect(self.onExportHTML) self.toolbar.addAction(self.button_html) # export to CSV action style_csv = self.toolbar.style() icon = style_csv.standardIcon(QStyle.SP_FileLinkIcon) self.button_csv = QAction(icon, "CSV", self) self.button_csv.setStatusTip("Export data to CSV file") self.button_csv.triggered.connect(self.onExportCSV) self.toolbar.addAction(self.button_csv) # export to Markdown action style_mark = self.toolbar.style() icon = style_mark.standardIcon(QStyle.SP_FileLinkIcon) self.button_mark = QAction(icon, "Markdown", self) self.button_mark.setStatusTip("Export data to Markdown file") self.button_mark.triggered.connect(self.onExportMarkdown) self.toolbar.addAction(self.button_mark) # import data from world bank climate api style_api = self.toolbar.style() icon = style_api.standardIcon(QStyle.SP_DialogSaveButton) self.button_api = QAction(icon, "API", self) self.button_api.setStatusTip("Import data from World Bank Climate API") self.button_api.triggered.connect(self.onImportFromAPI) self.toolbar.addAction(self.button_api) self.button_api.setEnabled(True) # close action style_close = self.toolbar.style() icon = style_close.standardIcon(QStyle.SP_DialogCloseButton) self.button_close = QAction(icon, "Close", self) self.button_close.setShortcut('Ctrl+X') self.button_close.setStatusTip("Close CSV file...") self.button_close.triggered.connect(self.onToolbarCloseButtonClick) self.toolbar.addAction(self.button_close) # quit action self.button_quit = QAction("Quit", self) self.button_quit.setShortcut('Ctrl+Q') self.button_quit.setStatusTip("Quit application") self.button_quit.triggered.connect(self.close) # toolbar show/hide action self.button_tool = QAction("Show/Hide toolbar", self) self.button_tool.setShortcut('Ctrl+T') self.button_tool.setStatusTip("Show or hide toolbar") self.button_tool.triggered.connect(self.showToolbar) # remove NaN self.button_nan = QAction("Remove NaN", self) self.button_nan.setShortcut('Ctrl+R') self.button_nan.setStatusTip("Remove rows with missing values") self.button_nan.triggered.connect(self.onRemoveNaN) # settings action style_settings = self.toolbar.style() icon = style_settings.standardIcon(QStyle.SP_ComputerIcon) self.button_settings = QAction(icon, "Settings", self) self.button_settings.setStatusTip("Application settings") self.button_settings.triggered.connect(self.onSettings) self.toolbar.addAction(self.button_settings) self.button_settings.setEnabled(True) # about action style_about = self.toolbar.style() icon = style_about.standardIcon(QStyle.SP_FileDialogInfoView) self.button_about = QAction(icon, "About", self) self.button_about.setStatusTip("About application") self.button_about.triggered.connect(self.about) self.toolbar.addAction(self.button_about) self.button_about.setEnabled(True) self.setButtons(False) # recent menu action for i in range(MainWindow.MaxRecentFiles): self.recentFileActs.append( QAction(self, visible=False, triggered=self.openRecentFile) ) # menu bar menu = self.menuBar() file_menu = menu.addMenu("&File") file_menu.addAction(self.button_open) file_menu.addAction(self.button_close) file_menu.addSeparator() file_menu.addAction(self.button_nan) file_menu.addSeparator() file_menu.addAction(self.button_settings) self.separatorAct = file_menu.addSeparator() for i in range(MainWindow.MaxRecentFiles): file_menu.addAction(self.recentFileActs[i]) file_menu.addSeparator() file_menu.addAction(self.button_quit) view_menu = menu.addMenu("Vie&w") view_menu.addAction(self.button_summary) view_menu.addAction(self.button_info) view_menu.addSeparator() view_menu.addAction(self.button_resize) view_menu.addSeparator() view_menu.addAction(self.button_tool) export_menu = menu.addMenu("&Export") export_menu.addAction(self.button_xlsx) export_menu.addAction(self.button_sqlite) export_menu.addAction(self.button_html) export_menu.addAction(self.button_csv) export_menu.addAction(self.button_mark) import_menu = menu.addMenu("&Import") import_menu.addAction(self.button_api) help_menu = menu.addMenu("&Help") help_menu.addAction(self.button_about) self.updateRecentFileActions() # status bar self.my_status = QStatusBar(self) self.my_status.addPermanentWidget(self.progress) self.progress.hide() self.labelStatus = QLabel("Rows: 0 Cols: 0") self.my_status.addPermanentWidget(self.labelStatus) self.setStatusBar(self.my_status) # set TableView self.table = QtWidgets.QTableView() self.table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) self.table.setSelectionMode(QtWidgets.QTableView.SingleSelection) self.setCentralWidget(self.table) self.setWindowTitle(self.app_title) self.setMinimumSize(400, 250) self.setGeometry(200, 100, 1000, 600) def onToolbarOpenButtonClick(self) -> None: """ Show open dialog """ dlg = ParameterDialog() dlg.setWindowTitle("Open") if dlg.exec_(): file_name = dlg.filename.text() separator = dlg.separator decimal = dlg.decimal header = dlg.header index = dlg.index else: file_name = None if file_name: self.saveRecent(file_name) self.open_csv_file(file_name, separator, decimal, header, index) def onOpenRecentFile(self, file_name: str, sep=',', decimal='.') -> None: """ Open file from recent list, show open dialog """ dlg = ParameterDialog(file_name, sep, decimal) dlg.setWindowTitle("Open") if dlg.exec_(): file_name = dlg.filename.text() separator = dlg.separator decimal = dlg.decimal header = dlg.header index = dlg.index else: file_name = None if file_name: self.saveRecent(file_name) self.open_csv_file(file_name, separator, decimal, header, index) def open_csv_file(self, file_name: str, sep=',', decimal=".", header=True, index=True) -> None: """ Open csv file, load to tableview, set statusbar, enable close icon""" try: if header: my_header = 'infer' else: my_header = None if index: my_index = 0 else: my_index = False print(my_header, my_index) data = pd.read_csv(file_name, sep=sep, decimal=decimal, header=my_header, index_col=my_index) self.df = data self.model = TableModel(self.df, self.round_num) self.labelStatus.setText(f"Rows: {self.df.shape[0]} Cols: {self.df.shape[1]}") self.table.setModel(self.model) if data.shape[0] > 0: self.table.selectRow(0) self.setButtons(True) self.setWindowTitle(self.app_title + ": " + file_name) except Exception as e: QMessageBox.warning(self, 'Error', f"Error loading the file:\n {file_name}") def setButtons(self, state: bool) -> None: """ Set state of buttons/actions """ self.button_close.setEnabled(state) self.button_summary.setEnabled(state) self.button_info.setEnabled(state) self.button_resize.setEnabled(state) self.button_xlsx.setEnabled(state) self.button_sqlite.setEnabled(state) self.button_html.setEnabled(state) self.button_csv.setEnabled(state) self.button_nan.setEnabled(state) self.button_mark.setEnabled(state) def onResizeColumns(self) -> None: """Resize columns action, run from menu View->Resize columns""" self.table.resizeColumnsToContents() def onToolbarCloseButtonClick(self) -> None: """Clear tableview, set statusbar and disable toolbar close, summary and info icons""" self.table.setModel(None) self.df = None self.setButtons(False) self.setWindowTitle(self.app_title) self.labelStatus.setText("Rows: 0 Cols: 0") def onToolbarSummaryButtonClick(self) -> None: """Show Summary dialog""" dlg = SummaryDialog(self.df.describe()) dlg.setWindowTitle("Summary") dlg.exec_() def onToolbarInfoButtonClick(self) -> None: """Show Info dialog""" # buf = io.StringIO() # self.df.info(buf=buf) # tmp = buf.getvalue() dlg = InfoDialog(self.df) dlg.setWindowTitle("Info") dlg.exec_() def onExportXlsx(self) -> None: """ Export data to xlsx file """ file_name, _ = QFileDialog.getSaveFileName(self, 'Export to xlsx', '', ".xlsx(*.xlsx)") if file_name: self.df.to_excel(file_name, engine='xlsxwriter') def onExportSQLite(self) -> None: file_name, _ = QFileDialog.getSaveFileName(self, 'Export to sqlite db', '', ".sqlite(*.sqlite)") if file_name: engine = create_engine(f'sqlite:///{file_name}', echo=False) self.df.to_sql('csv_data', con=engine) def onExportHTML(self) -> None: """ Export data to HTML file """ file_name, _ = QFileDialog.getSaveFileName(self, 'Export to HTML file', '', ".html(*.html)") if file_name: self.df.to_html(file_name) def onExportCSV(self) -> None: """ Export data to new CSV file """ file_name, _ = QFileDialog.getSaveFileName(self, 'Export to CSV file', '', ".csv(*.csv)") if file_name: self.df.to_csv(file_name, sep=',', decimal='.') def onImportFromAPI(self) -> None: dlg = ApiDialog() dlg.setWindowTitle("Import a Data CSV via API") if dlg.exec_(): file_name = dlg.filename.text() address = dlg.address.text() else: file_name = None if file_name and address: res, text = dataload.import_data_by_api(address) if res: with open(file_name, "w") as f: f.write(text) self.onOpenRecentFile(file_name) else: QMessageBox.warning(self, "Error", text) def closeEvent(self, event) -> None: """ Quit application, ask user before """ if not app_test: result = QMessageBox.question( self, self.app_title, "Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, ) if app_test or result == QMessageBox.Yes: event.accept() else: event.ignore() def about(self) -> None: """ Show About dialog (info about application)""" dlg = AboutDialog() dlg.exec_() def openRecentFile(self) -> None: """ Open file from recent list action """ action = self.sender() if action: self.onOpenRecentFile(action.data()) def saveRecent(self, file_name) -> None: """ Save information about currently opened file, update recent list""" settings = QSettings('CSV_Viewer', 'CSV_Viewer') files = settings.value('recentFileList', []) try: files.remove(file_name) except ValueError: pass files.insert(0, file_name) del files[MainWindow.MaxRecentFiles:] settings.setValue('recentFileList', files) for widget in QtWidgets.QApplication.topLevelWidgets(): if isinstance(widget, MainWindow): widget.updateRecentFileActions() def updateRecentFileActions(self) -> None: """ Update recent file list """ settings = QSettings('CSV_Viewer', 'CSV_Viewer') files = settings.value('recentFileList', []) numRecentFiles = min(len(files), MainWindow.MaxRecentFiles) for i in range(numRecentFiles): text = "&%d %s" % (i + 1, self.strippedName(files[i])) self.recentFileActs[i].setText(text) self.recentFileActs[i].setData(files[i]) self.recentFileActs[i].setVisible(True) for j in range(numRecentFiles, MainWindow.MaxRecentFiles): self.recentFileActs[j].setVisible(False) self.separatorAct.setVisible((numRecentFiles > 0)) def strippedName(self, fullFileName: str) -> str: """ Return only file name, without path""" return QFileInfo(fullFileName).fileName() def showToolbar(self): """ show / hide main toolbar""" if self.toolbar.isHidden(): self.toolbar.show() else: self.toolbar.hide() def onSettings(self): """ Round numbers to """ n, result = QInputDialog.getInt(self, "Settings", "Round numbers to:", self.round_num, 0, 10, 1) if result and n != self.round_num: self.round_num = n self.settings.setValue('round_numbers', self.round_num) if self.df.shape[0] > 0: index = self.table.currentIndex() self.model = TableModel(self.df, self.round_num) self.table.setModel(self.model) self.table.selectRow(index.row()) def onRemoveNaN(self): """ Remove rows with missing values """ if self.df.shape[0] > 0: button = QMessageBox.question(self, "Remove NaN", "Remove rows with missing values?") if button == QMessageBox.Yes: self.df.dropna(axis=0, how='any', inplace=True) self.model = TableModel(self.df, self.round_num) self.table.setModel(self.model) self.table.selectRow(0) self.labelStatus.setText(f"Rows: {self.df.shape[0]} Cols: {self.df.shape[1]}") def onExportMarkdown(self): """ export to markdown table in thread """ file_name, _ = QFileDialog.getSaveFileName(self, 'Export to Markdown file', '', ".md(*.md)") if file_name: worker = MarkdownWorker(file_name, self.df) worker.signals.progress.connect(self.update_progress) worker.signals.status.connect(self.update_status) self.threadpool.start(worker) def update_progress(self, progress): self.progress.setValue(progress) def update_status(self, status): if status: self.progress.show() else: self.progress.hide()
class MainWindow(QMainWindow): """This is the main application window class it defines the GUI window for the browser """ def parse_config(self, file_config, options): self.config = {} options = vars(options) for key, metadata in CONFIG_OPTIONS.items(): options_val = options.get(key) file_val = file_config.get(key) env_val = os.environ.get(metadata.get("env", '')) default_val = metadata.get("default") vals = metadata.get("values") debug("key: {}, default: {}, file: {}, options: {}".format( key, default_val, file_val, options_val )) if vals: options_val = (options_val in vals and options_val) or None file_val = (file_val in vals and file_val) or None env_val = (env_val in vals and env_val) or None if metadata.get("is_file"): filename = options_val or env_val if not filename: self.config[key] = default_val else: try: with open(filename, 'r') as fh: self.config[key] = fh.read() except IOError: debug("Could not open file {} for reading.".format( filename) ) self.config[key] = default_val else: set_values = [ val for val in (options_val, env_val, file_val) if val is not None ] if len(set_values) > 0: self.config[key] = set_values[0] else: self.config[key] = default_val if metadata.get("type") and self.config[key]: debug("{} cast to {}".format(key, metadata.get("type"))) self.config[key] = metadata.get("type")(self.config[key]) debug(repr(self.config)) def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered"): """Return a QAction given a number of common QAction attributes Just a shortcut function Originally borrowed from 'Rapid GUI Development with PyQT' by Mark Summerset """ action = QAction(text, self) if icon is not None: action.setIcon(QIcon.fromTheme( icon, QIcon(":/{}.png".format(icon)) )) if shortcut is not None and not shortcut.isEmpty(): action.setShortcut(shortcut) tip += " ({})".format(shortcut.toString()) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.__getattr__(signal).connect(slot) if checkable: action.setCheckable() return action def __init__(self, options, parent=None): """Construct a MainWindow Object.""" super(MainWindow, self).__init__(parent) # Load config file self.setWindowTitle("Browser") debug("loading configuration from '{}'".format(options.config_file)) configfile = {} if options.config_file: configfile = yaml.safe_load(open(options.config_file, 'r')) self.parse_config(configfile, options) # self.popup will hold a reference to the popup window # if it gets opened self.popup = None # Stylesheet support if self.config.get("stylesheet"): try: with open(self.config.get("stylesheet")) as ss: self.setStyleSheet(ss.read()) except: debug( """Problem loading stylesheet file "{}", """ """using default style.""" .format(self.config.get("stylesheet")) ) self.setObjectName("global") # If the whitelist is activated, add the bookmarks and start_url if self.config.get("whitelist"): # we can just specify whitelist = True, # which should whitelist just the start_url and bookmark urls. whitelist = self.config.get("whitelist") if type(whitelist) is not list: whitelist = [] whitelist.append(str(QUrl( self.config.get("start_url") ).host())) bookmarks = self.config.get("bookmarks") if bookmarks: whitelist += [ str(QUrl(b.get("url")).host()) for k, b in bookmarks.items() ] self.config["whitelist"] = set(whitelist) # uniquify and optimize debug("Generated whitelist: " + str(whitelist)) # If diagnostic is enabled, connect CTRL+ALT+? to show some diagnistic info if (self.config.get("enable_diagnostic")): self.diagnostic_action = self.createAction( "Show Diagnostic", self.show_diagnostic, QKeySequence("Ctrl+Alt+/"), tip='' ) self.addAction(self.diagnostic_action) self.build_ui() # ## END OF CONSTRUCTOR ## # def build_ui(self): """Set up the user interface for the main window. Unlike the constructor, this method is re-run whenever the browser is "reset" by the user. """ debug("build_ui") inactivity_timeout = self.config.get("timeout") quit_button_tooltip = ( self.config.get("quit_button_mode") == 'close' and "Click here to quit the browser." or """Click here when you are done. It will clear your browsing history""" """ and return you to the start page.""") qb_mode_callbacks = {'close': self.close, 'reset': self.reset_browser} to_mode_callbacks = {'close': self.close, 'reset': self.reset_browser, 'screensaver': self.screensaver} self.screensaver_active = False # ##Start GUI configuration## # self.browser_window = WcgWebView(self.config) self.browser_window.setObjectName("web_content") if ( self.config.get("icon_theme") is not None and QT_VERSION_STR > '4.6' ): QIcon.setThemeName(self.config.get("icon_theme")) self.setCentralWidget(self.browser_window) debug("loading {}".format(self.config.get("start_url"))) self.browser_window.setUrl(QUrl(self.config.get("start_url"))) if self.config.get("fullscreen"): self.showFullScreen() elif ( self.config.get("window_size") and self.config.get("window_size").lower() == 'max' ): self.showMaximized() elif self.config.get("window_size"): size = re.match(r"(\d+)x(\d+)", self.config.get("window_size")) if size: width, height = size.groups() self.setFixedSize(int(width), int(height)) else: debug('Ignoring invalid window size "{}"'.format( self.config.get("window_size") )) # Set up the top navigation bar if it's configured to exist if self.config.get("navigation"): self.navigation_bar = QToolBar("Navigation") self.navigation_bar.setObjectName("navigation") self.addToolBar(Qt.TopToolBarArea, self.navigation_bar) self.navigation_bar.setMovable(False) self.navigation_bar.setFloatable(False) # Standard navigation tools self.nav_items = {} self.nav_items["back"] = self.browser_window.pageAction(QWebPage.Back) self.nav_items["forward"] = self.browser_window.pageAction(QWebPage.Forward) self.nav_items["refresh"] = self.browser_window.pageAction(QWebPage.Reload) self.nav_items["stop"] = self.browser_window.pageAction(QWebPage.Stop) # The "I'm finished" button. self.nav_items["quit"] = self.createAction( self.config.get("quit_button_text"), qb_mode_callbacks.get(self.config.get("quit_button_mode"), self.reset_browser), QKeySequence("Alt+F"), None, quit_button_tooltip) # Zoom buttons self.nav_items["zoom_in"] = self.createAction( "Zoom In", self.zoom_in, QKeySequence("Alt++"), "zoom-in", "Increase the size of the text and images on the page") self.nav_items["zoom_out"] = self.createAction( "Zoom Out", self.zoom_out, QKeySequence("Alt+-"), "zoom-out", "Decrease the size of text and images on the page") if self.config.get("allow_printing"): self.nav_items["print"] = self.createAction( "Print", self.browser_window.print_webpage, QKeySequence("Ctrl+p"), "document-print", "Print this page") # Add all the actions to the navigation bar. for item in self.config.get("navigation_layout"): if item == "separator": self.navigation_bar.addSeparator() elif item == "spacer": # an expanding spacer. spacer = QWidget() spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.navigation_bar.addWidget(spacer) elif item == "bookmarks": # Insert bookmarks buttons here. self.bookmark_buttons = [] for bookmark in self.config.get("bookmarks", {}).items(): debug("Bookmark:\n" + bookmark.__str__()) # bookmark name will use the "name" attribute, if present # or else just the key: bookmark_name = bookmark[1].get("name") or bookmark[0] # Create a button for the bookmark as a QAction, # which we'll add to the toolbar button = self.createAction( bookmark_name, lambda url=bookmark[1].get("url"): self.browser_window.load(QUrl(url)), QKeySequence.mnemonic(bookmark_name), None, bookmark[1].get("description") ) self.navigation_bar.addAction(button) self.navigation_bar.widgetForAction(button).setObjectName("navigation_button") else: action = self.nav_items.get(item, None) if action: self.navigation_bar.addAction(action) self.navigation_bar.widgetForAction(action).setObjectName("navigation_button") # This removes the ability to toggle off the navigation bar: self.nav_toggle = self.navigation_bar.toggleViewAction() self.nav_toggle.setVisible(False) # End "if show_navigation is True" block # set hidden quit action # For reasons I haven't adequately ascertained, # this shortcut fails now and then claiming # "Ambiguous shortcut overload". # No idea why, as it isn't consistent. self.really_quit = self.createAction( "", self.close, QKeySequence("Ctrl+Alt+Q"), None, "" ) self.addAction(self.really_quit) # Call a reset function after timeout if inactivity_timeout != 0: self.event_filter = InactivityFilter(inactivity_timeout) QCoreApplication.instance().installEventFilter(self.event_filter) self.browser_window.page().installEventFilter(self.event_filter) self.event_filter.timeout.connect( to_mode_callbacks.get(self.config.get("timeout_mode"), self.reset_browser)) else: self.event_filter = None # ##END OF UI SETUP## # def screensaver(self): """Enter "screensaver" mode This method puts the browser in screensaver mode, where a URL is displayed while the browser is idle. Activity causes the browser to return to the home screen. """ debug("screensaver started") self.screensaver_active = True if self.popup: self.popup.close() if self.config.get("navigation"): self.navigation_bar.hide() self.browser_window.setZoomFactor(self.config.get("zoom_factor")) self.browser_window.load(QUrl(self.config.get("screensaver_url"))) self.event_filter.timeout.disconnect() self.event_filter.activity.connect(self.reset_browser) def reset_browser(self): """Clear the history and reset the UI. Called whenever the inactivity filter times out, or when the user clicks the "finished" button in 'reset' mode. """ # Clear out the memory cache QWebSettings.clearMemoryCaches() self.browser_window.history().clear() # self.navigation_bar.clear() doesn't do its job, # so remove the toolbar first, then rebuild the UI. debug("RESET BROWSER") if self.event_filter: self.event_filter.blockSignals(True) if self.screensaver_active is True: self.screensaver_active = False self.event_filter.activity.disconnect() if self.event_filter: self.event_filter.blockSignals(False) if hasattr(self, "navigation_bar"): self.removeToolBar(self.navigation_bar) self.build_ui() def zoom_in(self): """Zoom in action callback. Note that we cap zooming in at a factor of 3x. """ if self.browser_window.zoomFactor() < 3.0: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() + 0.1 ) self.nav_items["zoom_out"].setEnabled(True) else: self.nav_items["zoom_in"].setEnabled(False) def zoom_out(self): """Zoom out action callback. Note that we cap zooming out at 0.1x. """ if self.browser_window.zoomFactor() > 0.1: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() - 0.1 ) self.nav_items["zoom_in"].setEnabled(True) else: self.nav_items["zoom_out"].setEnabled(False) def show_diagnostic(self): "Display a dialog box with some diagnostic info" data = { "OS": os.uname(), "USER": (os.environ.get("USER") or os.environ.get("USERNAME")), "Python": sys.version, "Qt": QT_VERSION_STR, "Script Date": ( datetime.datetime.fromtimestamp( os.stat(__file__).st_mtime).isoformat() ) } html = "\n".join([ "<h1>System Information</h1>", "<h2>Please click "", self.config.get("quit_button_text").replace("&", ''), "" when you are finished.</h2>", "<ul>", "\n".join([ "<li><b>{}</b>: {}</li>".format(k, v) for k, v in data.items() ]), "</ul>" ]) self.browser_window.setHtml(html)
class Pireal(QMainWindow): """ Main Window class This class is responsible for installing all application services. """ __SERVICES = {} __ACTIONS = {} # The name of items is the connection text TOOLBAR_ITEMS = [ 'create_database', 'open_database', 'save_database', '', # Is a separator! 'new_query', 'open_query', 'save_query', '', 'relation_menu', '', # 'create_new_relation', # 'remove_relation', # '', # 'add_tuple', # 'delete_tuple', # 'add_column', # 'delete_column', # '', 'execute_queries' ] def __init__(self): QMainWindow.__init__(self) self.setWindowTitle(self.tr("Pireal")) self.setMinimumSize(880, 600) # Load window geometry qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) window_maximized = qsettings.value('window_max', True, type=bool) if window_maximized: self.showMaximized() else: size = qsettings.value('window_size') self.resize(size) position = qsettings.value('window_pos') self.move(position) # Toolbar self.toolbar = QToolBar(self) self.toolbar.setFixedWidth(38) self.toolbar.setIconSize(QSize(38, 38)) self.toolbar.setMovable(False) self.addToolBar(Qt.RightToolBarArea, self.toolbar) # Menu bar menubar = self.menuBar() self.__load_menubar(menubar) # Load notification widget after toolbar actions notification_widget = Pireal.get_service("notification") self.toolbar.addWidget(notification_widget) # Message error # self._msg_error_widget = message_error.MessageError(self) # Central widget central_widget = Pireal.get_service("central") central_widget.databaseSaved.connect(notification_widget.show_text) central_widget.querySaved.connect(notification_widget.show_text) central_widget.databaseConected.connect(self.change_title) self.setCentralWidget(central_widget) central_widget.add_start_page() # Install service Pireal.load_service("pireal", self) @classmethod def get_service(cls, service): """ Return the instance of a loaded service """ return cls.__SERVICES.get(service, None) @classmethod def load_service(cls, name, instance): """ Load a service providing the service name and the instance """ cls.__SERVICES[name] = instance @classmethod def get_action(cls, name): """ Return the instance of a loaded QAction """ return cls.__ACTIONS.get(name, None) @classmethod def load_action(cls, name, action): """ Load a QAction """ cls.__ACTIONS[name] = action def __load_menubar(self, menubar): """ This method installs the menubar and toolbar, menus and QAction's, also connects to a slot each QAction. """ # Keymap kmap = keymap.KEYMAP central = Pireal.get_service("central") # Load menu bar rela_actions = [] for item in menu_actions.MENU: menubar_item = menu_actions.MENU[item] menu_name = menubar_item['name'] items = menubar_item['items'] menu = menubar.addMenu(menu_name) for menu_item in items: if isinstance(menu_item, str): # Is a separator menu.addSeparator() else: action = menu_item['name'] obj_name, connection = menu_item['slot'].split(':') obj = central if obj_name.startswith('pireal'): obj = self qaction = menu.addAction(action) # Icon name is connection icon = QIcon(":img/%s" % connection) qaction.setIcon(icon) # Install shortcuts shortcut = kmap.get(connection, None) if shortcut is not None: qaction.setShortcut(shortcut) # The name of QAction is the connection if item == "relation": if connection != "execute_queries": rela_actions.append(qaction) Pireal.load_action(connection, qaction) slot = getattr(obj, connection, None) if isinstance(slot, Callable): qaction.triggered.connect(slot) # Install toolbar # self.__install_toolbar(toolbar_items, rela_actions) self.__install_toolbar(rela_actions) # Disable some actions self.set_enabled_db_actions(False) self.set_enabled_relation_actions(False) self.set_enabled_query_actions(False) self.set_enabled_editor_actions(False) def __install_toolbar(self, rela_actions): menu = QMenu() tool_button = QToolButton() tool_button.setIcon(QIcon(":img/create_new_relation")) tool_button.setMenu(menu) tool_button.setPopupMode(QToolButton.InstantPopup) for item in self.TOOLBAR_ITEMS: if item: if item == "relation_menu": # Install menu for relation menu.addActions(rela_actions) self.toolbar.addWidget(tool_button) else: self.toolbar.addAction(self.__ACTIONS[item]) else: self.toolbar.addSeparator() def __show_status_message(self, msg): status = Pireal.get_service("status") status.show_message(msg) def __on_thread_update_finished(self): self._thread.quit() # Clear notificator notification_widget = Pireal.get_service("notification") notification_widget.clear() msg = QMessageBox(self) if not self._updater.error and self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText( self.tr("Check the web site to " "download <b>Pireal {}</b>".format(version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new("http://centaurialpha.github.io/pireal") self._thread.deleteLater() self._updater.deleteLater() def change_title(self, title=''): if title: _title = title + " - Pireal " else: _title = "Pireal" self.setWindowTitle(_title) def set_enabled_db_actions(self, value): """ Public method. Enables or disables db QAction """ actions = [ 'new_query', 'open_query', 'close_database', 'save_database', 'save_database_as', 'load_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_relation_actions(self, value): """ Public method. Enables or disables relation's QAction """ actions = [ 'create_new_relation', 'remove_relation', 'add_tuple', 'delete_tuple', # 'add_column', # 'delete_column' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_query_actions(self, value): """ Public method. Enables or disables queries QAction """ actions = ['execute_queries', 'save_query'] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_editor_actions(self, value): """ Public slot. Enables or disables editor actions """ actions = [ 'undo_action', 'redo_action', 'copy_action', 'cut_action', 'paste_action', 'zoom_in', 'zoom_out', 'comment', 'uncomment', 'search' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def about_qt(self): """ Show about qt dialog """ QMessageBox.aboutQt(self) def about_pireal(self): """ Show the bout Pireal dialog """ from src.gui.dialogs import about_dialog dialog = about_dialog.AboutDialog(self) dialog.exec_() def report_issue(self): """ Open in the browser the page to create new issue """ webbrowser.open("http://github.com/centaurialpha/pireal/issues/new") def show_hide_menubar(self): """ Change visibility of menu bar """ if self.menuBar().isVisible(): self.menuBar().hide() else: self.menuBar().show() def show_hide_toolbar(self): """ Change visibility of tool bar """ if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def show_error_message(self, text, syntax_error=True): self._msg_error_widget.show_msg(text, syntax_error) self._msg_error_widget.show() def save_user_settings(self): central_widget = Pireal.get_service("central") CONFIG.set_value("lastOpenFolder", central_widget.last_open_folder) CONFIG.set_value("recentFiles", central_widget.recent_databases) # Write settings CONFIG.save_settings() def closeEvent(self, event): self.save_user_settings() # Qt settings qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) # # Save window geometry if self.isMaximized(): qsettings.setValue('window_max', True) else: qsettings.setValue('window_max', False) qsettings.setValue('window_pos', self.pos()) qsettings.setValue('window_size', self.size()) central_widget = Pireal.get_service("central") db = central_widget.get_active_db() if db is not None: # Save splitters size db.save_sizes() # Databases unsaved if db.modified: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle( self.tr("Algunos cambios no fueron guardados")) msg.setText( self.tr("Desea guardar los cambios en la base de datos?")) cancel_btn = msg.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Si"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: central_widget.save_database() if r == cancel_btn: event.ignore() # Query files unsaved_editors = central_widget.get_unsaved_queries() if unsaved_editors: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Consultas no guardadas")) text = '\n'.join([editor.name for editor in unsaved_editors]) msg.setText( self.tr( "{files}\n\nQuiere guardarlas?".format(files=text))) cancel_btn = msg.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Si"), QMessageBox.YesRole) msg.exec_() if msg.clickedButton() == yes_btn: for editor in unsaved_editors: central_widget.save_query(editor) if msg.clickedButton() == cancel_btn: event.ignore()
class FileViewWindow(QMainWindow): def __init__(self, controller: 'Controller') -> None: super().__init__() self._message_area: Optional[MessageArea] = None self.controller = controller self.actions = self.controller.actions self.make_window() self.make_menubar() self.make_toolbar() self.addToolBarBreak() self.make_location_toolbar() self.make_filter_toolbar() self.make_search_toolbar() self.make_shortcut() self.file_view.setFocus() self.resize(1024, 768) self.move(QCursor.pos().x() - self.width() / 2, QCursor.pos().y() - self.height() / 2) self.addAction(self.actions.rename) def closeEvent(self, ev): self.controller.on_exit() def make_shortcut(self): shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_L), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(self.controller.show_location_toolbar) def show_filter(): self.file_filter.setFocus(Qt.ShortcutFocusReason) self.filter_toolbar.show() shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_K), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(show_filter) shortcut = QShortcut(QKeySequence(Qt.Key_F3), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(self.controller.show_search) shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_F), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(self.controller.show_search) shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_W), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(self.controller.close_window) shortcut = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Up), self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(self.controller.parent_directory) shortcut = QShortcut(Qt.Key_Home, self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(lambda: self.file_view.ensureVisible(0, 0, 1, 1)) shortcut = QShortcut(Qt.Key_End, self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect( lambda: self.file_view.ensureVisible( 0, self.file_view._layout.get_bounding_rect().height(), 1, 1)) shortcut = QShortcut(Qt.Key_PageUp, self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(lambda: self.file_view.scroll_by(0, -self.file_view.viewport().height())) shortcut = QShortcut(Qt.Key_PageDown, self) shortcut.setContext(Qt.WindowShortcut) shortcut.activated.connect(lambda: self.file_view.scroll_by(0, self.file_view.viewport().height())) def make_window(self): self.setWindowTitle("dt-fileview") self.setWindowIcon(QIcon(resource_filename("dirtools", "fileview/dt-fileview.svg"))) self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) self.file_view = FileView(self.controller) self.search_lineedit = SearchLineEdit(self.controller) self.location_lineedit = LocationLineEdit(self.controller) self.location_buttonbar = LocationButtonBar(self.controller) self.file_filter = FilterLineEdit(self.controller) # self.file_filter.setText("File Pattern Here") self.file_view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.status_bar = self.statusBar() self.info = QLabel("") self.status_bar.addPermanentWidget(self.info) self.vbox.addWidget(self.file_view, Qt.AlignLeft) self._message_area = MessageArea() self._message_area.hide() self.vbox.addWidget(self._message_area) vbox_widget = QWidget() vbox_widget.setLayout(self.vbox) self.setCentralWidget(vbox_widget) def hide_filter(self): self.filter_toolbar.hide() def make_filter_toolbar(self): hbox = QHBoxLayout() form = QFormLayout() label = QLabel("Filter:") label.setBuddy(self.file_filter) form.addRow(label, self.file_filter) form.setContentsMargins(0, 0, 0, 0) hbox.addLayout(form) help_button = QPushButton("Help") help_button.clicked.connect(self.controller.show_filter_help) hbox.addWidget(help_button) hbox.setContentsMargins(0, 0, 0, 0) widget = QWidget() widget.setLayout(hbox) self.filter_toolbar = QToolBar("Filter") self.filter_toolbar.addWidget(widget) self.addToolBar(Qt.BottomToolBarArea, self.filter_toolbar) self.filter_toolbar.hide() def make_search_toolbar(self): hbox = QHBoxLayout() form = QFormLayout() label = QLabel("Search:") label.setBuddy(self.search_lineedit) form.addRow(label, self.search_lineedit) form.setContentsMargins(0, 0, 0, 0) hbox.addLayout(form) help_button = QPushButton("Help") help_button.clicked.connect(self.controller.show_search_help) hbox.addWidget(help_button) hbox.setContentsMargins(0, 0, 0, 0) widget = QWidget() widget.setLayout(hbox) self.search_toolbar = QToolBar("Search") self.search_toolbar.addWidget(widget) self.addToolBar(Qt.TopToolBarArea, self.search_toolbar) self.search_toolbar.hide() def make_location_toolbar(self): self.location_toolbar = self.addToolBar("Location") widget = QWidget() form = QFormLayout() label = Label("Location:") label.clicked.connect(self.controller.show_location_toolbar) def show_location_menu(pos): self.controller.on_context_menu(label.mapToGlobal(pos)) label.setContextMenuPolicy(Qt.CustomContextMenu) label.customContextMenuRequested.connect(show_location_menu) both = QVBoxLayout() both.addWidget(self.location_buttonbar) both.addWidget(self.location_lineedit) form.addRow(label, both) form.setContentsMargins(0, 0, 0, 0) widget.setLayout(form) self.location_toolbar.addWidget(widget) self.addToolBarBreak(Qt.TopToolBarArea) def make_group_menu(self): menu = QMenu("Group Options") menu.addAction(self.actions.group_by_none) menu.addAction(self.actions.group_by_day) menu.addAction(self.actions.group_by_directory) menu.addAction(self.actions.group_by_duration) return menu def make_sort_menu(self): menu = QMenu("Sort Options") menu.addSeparator().setText("Sort Options") menu.addAction(self.actions.sort_directories_first) menu.addAction(self.actions.sort_reversed) menu.addSeparator().setText("Sort by") menu.addAction(self.actions.sort_by_name) menu.addAction(self.actions.sort_by_size) menu.addAction(self.actions.sort_by_ext) menu.addAction(self.actions.sort_by_date) menu.addAction(self.actions.sort_by_duration) menu.addAction(self.actions.sort_by_aspect_ratio) menu.addAction(self.actions.sort_by_framerate) menu.addAction(self.actions.sort_by_area) menu.addAction(self.actions.sort_by_user) menu.addAction(self.actions.sort_by_group) menu.addAction(self.actions.sort_by_permission) menu.addAction(self.actions.sort_by_random) return menu def make_view_menu(self): menu = QMenu("View Options") menu.addSeparator().setText("View Options") menu.addAction(self.actions.show_abspath) menu.addAction(self.actions.show_basename) menu.addSeparator() menu.addAction(self.actions.toggle_timegaps) return menu def make_menubar(self): self.menubar = self.menuBar() file_menu = self.menubar.addMenu('&File') file_menu.addAction(self.actions.new_window) file_menu.addAction(self.actions.parent_directory) file_menu.addAction(self.actions.debug) file_menu.addAction(self.actions.save_as) file_menu.addSeparator() file_menu.addAction(self.actions.exit) edit_menu = self.menubar.addMenu('&Edit') # edit_menu.addAction(self.actions.undo) # edit_menu.addAction(self.actions.redo) # edit_menu.addSeparator() edit_menu.addAction(self.actions.edit_cut) edit_menu.addAction(self.actions.edit_copy) edit_menu.addAction(self.actions.edit_paste) edit_menu.addSeparator() edit_menu.addAction(self.actions.edit_select_all) edit_menu.addSeparator() edit_menu.addAction(self.actions.show_preferences) view_menu = self.menubar.addMenu('&View') view_menu.addSeparator().setText("View Style") view_menu.addAction(self.actions.view_detail_view) view_menu.addAction(self.actions.view_icon_view) view_menu.addAction(self.actions.view_small_icon_view) view_menu.addSeparator().setText("Filter") view_menu.addAction(self.actions.show_hidden) view_menu.addAction(self.actions.show_filtered) view_menu.addSeparator().setText("Path Options") view_menu.addAction(self.actions.show_abspath) view_menu.addAction(self.actions.show_basename) view_menu.addSeparator().setText("Sort Options") view_menu.addAction(self.actions.sort_directories_first) view_menu.addAction(self.actions.sort_reversed) view_menu.addAction(self.actions.sort_by_name) view_menu.addAction(self.actions.sort_by_size) view_menu.addAction(self.actions.sort_by_ext) view_menu.addAction(self.actions.sort_by_duration) view_menu.addAction(self.actions.sort_by_aspect_ratio) view_menu.addAction(self.actions.sort_by_framerate) view_menu.addAction(self.actions.sort_by_area) view_menu.addAction(self.actions.sort_by_user) view_menu.addAction(self.actions.sort_by_group) view_menu.addAction(self.actions.sort_by_permission) view_menu.addAction(self.actions.sort_by_random) view_menu.addSeparator().setText("Zoom") view_menu.addAction(self.actions.zoom_in) view_menu.addAction(self.actions.zoom_out) view_menu.addSeparator() self.bookmarks_menu = Menu('&Bookmarks') self.menubar.addMenu(self.bookmarks_menu) def create_bookmarks_menu(): bookmarks = self.controller.app.bookmarks entries = bookmarks.get_entries() self.bookmarks_menu.clear() if self.controller.location is None: action = self.bookmarks_menu.addAction(QIcon.fromTheme("user-bookmarks"), "Can't bookmark file lists") action.setEnabled(False) elif self.controller.location in entries: self.bookmarks_menu.addAction( QIcon.fromTheme("edit-delete"), "Remove This Location's Bookmark", lambda loc=self.controller.location: bookmarks.remove(loc)) else: self.bookmarks_menu.addAction( QIcon.fromTheme("bookmark-new"), "Bookmark This Location", lambda loc=self.controller.location: bookmarks.append(loc)) self.bookmarks_menu.addSeparator() self.bookmarks_menu.addDoubleAction( QIcon.fromTheme("folder"), "View Bookmarks", lambda: self.controller.set_location(Location("bookmarks", "/", [])), lambda: self.controller.new_controller().set_location(Location("bookmarks", "/", []))) self.bookmarks_menu.addSeparator() icon = QIcon.fromTheme("folder") for entry in entries: action = self.bookmarks_menu.addDoubleAction( icon, entry.as_url(), lambda entry=entry: self.controller.set_location(entry), lambda entry=entry: self.controller.app.show_location(entry)) if not entry.exists(): action.setEnabled(False) self.bookmarks_menu.aboutToShow.connect(create_bookmarks_menu) self.history_menu = Menu('&History') self.menubar.addMenu(self.history_menu) def create_history_menu(): history = self.controller.app.location_history self.history_menu.clear() self.history_menu.addAction(self.actions.back) self.history_menu.addAction(self.actions.forward) self.history_menu.addSeparator() self.history_menu.addDoubleAction( QIcon.fromTheme("folder"), "View File History", self.controller.show_file_history, lambda: self.controller.new_controller().show_file_history()) self.history_menu.addSection("Location History") entries = history.get_unique_entries(35) make_history_menu_entries(self.controller, self.history_menu, entries) self.history_menu.aboutToShow.connect(create_history_menu) help_menu = self.menubar.addMenu('&Help') help_menu.addAction(self.actions.about) def make_toolbar(self): self.toolbar = self.addToolBar("FileView") button = ToolButton() button.setDefaultAction(self.actions.parent_directory) button.sig_middle_click.connect(lambda: self.controller.parent_directory(new_window=True)) button.setIcon(self.actions.parent_directory.icon()) self.toolbar.addWidget(button) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.home) self.toolbar.addSeparator() history_back_btn = ToolButton() history_back_btn.setDefaultAction(self.actions.back) history_back_btn.setContextMenuPolicy(Qt.CustomContextMenu) history_back_btn.customContextMenuRequested.connect( lambda pos: self.controller.show_history_context_menu(history_back_btn, False)) self.toolbar.addWidget(history_back_btn) history_forward_btn = ToolButton() history_forward_btn.setDefaultAction(self.actions.forward) history_forward_btn.setContextMenuPolicy(Qt.CustomContextMenu) history_forward_btn.customContextMenuRequested.connect( lambda pos: self.controller.show_history_context_menu(history_forward_btn, False)) self.toolbar.addWidget(history_forward_btn) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.reload) self.toolbar.addAction(self.actions.prepare) # self.toolbar.addSeparator() # self.toolbar.addAction(self.actions.undo) # self.toolbar.addAction(self.actions.redo) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.show_hidden) button = QToolButton() button.setIcon(QIcon.fromTheme("view-restore")) button.setMenu(self.make_view_menu()) button.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(button) button = QToolButton() button.setIcon(QIcon.fromTheme("view-sort-ascending")) button.setMenu(self.make_sort_menu()) button.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(button) button = QToolButton() button.setIcon(QIcon.fromTheme("view-sort-ascending")) button.setMenu(self.make_group_menu()) button.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(button) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.view_icon_view) self.toolbar.addAction(self.actions.view_small_icon_view) self.toolbar.addAction(self.actions.view_detail_view) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.zoom_in) self.toolbar.addAction(self.actions.zoom_out) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.lod_in) self.toolbar.addAction(self.actions.lod_out) self.toolbar.addSeparator() self.toolbar.addAction(self.actions.crop_thumbnails) # Spacer to force right alignment for all following widget spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer) self.toolbar.addAction(self.actions.debug) self.toolbar.addAction(self.controller.app.actions.enable_filesystem) # Loading icon self.loading_movie = QMovie(resource_filename("dirtools", "fileview/icons/gears.gif")) self.loading_label = QLabel() self.toolbar.addWidget(self.loading_label) def show_loading(self): self.show_info("Loading...") self.loading_label.setMovie(self.loading_movie) self.loading_movie.start() self.loading_label.show() def hide_loading(self): self.show_info("") self.loading_movie.stop() self.loading_label.setMovie(None) self.loading_label.setVisible(False) def zoom_in(self): self.file_view.zoom_in() def zoom_out(self): self.file_view.zoom_out() def set_location(self, location: Location): self.location_lineedit.set_location(location) self.location_buttonbar.set_location(location) self.setWindowTitle("{} - dt-fileview".format(location.as_human())) def set_file_list(self): self.location_lineedit.set_unused_text() def show_info(self, text): self.info.setText(" " + text) def show_current_filename(self, filename): # FIXME: this causes quite substantial keyboard lag when # scrolling with PageUp/Down self.status_bar.showMessage(filename)
class WidgetDemo(QWidget): def __init__(self,mode): super(WidgetDemo,self).__init__() self.mode = mode self.initUI(mode) def initUI(self,mode): self.resize(800,800) #这里初始化,便于直接输入数据 self.data = [['']*100 for i in range(15000)] self.res_pos = [] self.focus_pos = None self.var_list = None #菜单栏 self.menu = QMenuBar() self.file = self.menu.addMenu('文件') self.edit = self.menu.addMenu('编辑') self.view = self.menu.addMenu('视图') self.help = self.menu.addMenu('帮助') #各菜单下的子菜单 #文件菜单下的子菜单 self.new = self.file.addAction('新建') self.open = self.file.addAction('打开') self.save = self.file.addAction('保存') # self.save_as = self.file.addAction('另存为') #编辑菜单下的子菜单 self.cut = self.edit.addAction('剪切') self.copy = self.edit.addAction('复制') self.paste = self.edit.addAction('粘贴') self.delete = self.edit.addAction('删除') self.find = self.edit.addAction('查找') self.replace = self.edit.addAction('替换') # 快捷键 self.open.setShortcut('Ctrl+O') self.save.setShortcut('Ctrl+S') self.new.setShortcut('Ctrl+N') self.find.setShortcut('Ctrl+F') #视图菜单下的子菜单 self.tool_view = QAction('工具栏',checkable=True) self.tool_view.setChecked(True) self.view.addAction(self.tool_view) self.statu_view = QAction('状态栏',checkable=True) self.statu_view.setChecked(True) self.view.addAction(self.statu_view) #帮助菜单下的子菜单 self.about = self.help.addAction('关于') #工具栏 self.tool_bar = QToolBar() self.tool_bar.addAction(self.new) self.tool_bar.addAction(self.open) self.tool_bar.addAction(self.save) self.tool_bar.addAction(self.cut) self.tool_bar.addAction(self.copy) self.tool_bar.addAction(self.paste) self.tool_bar.addAction(self.find) self.setting = QAction('变量设置') self.setting.setEnabled(False) self.tool_bar.addAction(self.setting) # self.tool_bar.addAction(self.replace) # #tool文本显示在下方 # self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) #findWidge self.find_widget = FindWidget() self.find_widget.hide() #表格 self.table_view = QTableView() self.table_view.setModel(self.mode) #状态栏 self.status_bar = QStatusBar() self.status_bar.showMessage('这是一个状态栏') #创建布局 layout = QVBoxLayout() layout.addWidget(self.menu) layout.addWidget(self.tool_bar) layout.addWidget(self.find_widget) layout.addWidget(self.table_view) layout.addWidget(self.status_bar) self.setLayout(layout) #关联信号 self.open.triggered.connect(self.triggeredOpen) self.save.triggered.connect(self.triggeredSave) self.mode.itemChanged.connect(self.dealItemChanged) self.tool_view.triggered.connect(self.triggeredView) self.statu_view.triggered.connect(self.triggeredView) self.new.triggered.connect(self.triggeredNew) self.find.triggered.connect(self.triggeredFind) self.find_widget.search.triggered.connect(self.dataLocation) self.find_widget.down.triggered.connect(self.downLocation) self.find_widget.up.triggered.connect(self.upLocation) self.find_widget.close.triggered.connect(self.triggeredHideFind) self.find_widget.repalce_button.clicked.connect(self.onClickReplace) self.find_widget.repalceAll_button.clicked.connect(self.onClickReplaceAll) self.setting.triggered.connect(self.triggeredSetting) #美化 icon = QIcon() icon.addPixmap(QPixmap('../image/打开.png'), QIcon.Normal, QIcon.Off) self.open.setIcon(icon) icon.addPixmap(QPixmap('../image/保存.png'), QIcon.Normal, QIcon.Off) self.save.setIcon(icon) icon.addPixmap(QPixmap('../image/新建.png'), QIcon.Normal, QIcon.Off) self.new.setIcon(icon) icon.addPixmap(QPixmap('../image/剪切.png'), QIcon.Normal, QIcon.Off) self.cut.setIcon(icon) icon.addPixmap(QPixmap('../image/复制.png'), QIcon.Normal, QIcon.Off) self.copy.setIcon(icon) icon.addPixmap(QPixmap('../image/粘贴.png'), QIcon.Normal, QIcon.Off) self.paste.setIcon(icon) icon.addPixmap(QPixmap('../image/查找1.png'), QIcon.Normal, QIcon.Off) self.find.setIcon(icon) icon.addPixmap(QPixmap('../image/设置.png'), QIcon.Normal, QIcon.Off) self.setting.setIcon(icon) # icon.addPixmap(QPixmap('../image/替换.png'), QIcon.Normal, QIcon.Off) # self.replace.setIcon(icon) def triggeredOpen(self): self.status_bar.showMessage('打开文件',5000) self.dialog = QFileDialog() self.dialog.setFileMode(QFileDialog.AnyFile) dir = r'../data' self.dialog.setDirectory(dir) self.dialog.setFilter(QDir.Files) if self.dialog.exec_(): try: start = time.time() file_name = self.dialog.selectedFiles()[0] #这里读取数据返回列表便于表格中数据的更新 data_list = read_xlsx(file_name) self.data = data_list self.mode = QStandardItemModel() for rows in data_list: row = [QStandardItem(str(cell)) for cell in rows] self.mode.appendRow(row) self.mode.itemChanged.connect(self.dealItemChanged) self.table_view.setModel(self.mode) # self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table_view.resizeColumnsToContents() self.table_view.resizeRowsToContents() end = time.time() self.status_bar.showMessage('数据加载完毕,耗时{}秒'.format(end-start)) self.setting.setEnabled(True) except Exception as e: print(e) pass def triggeredSave(self): self.status_bar.showMessage('保存文件', 5000) file_path, _ = QFileDialog.getSaveFileName(self, '保存文件', '../data', 'ALL Files(*);;xlsx(*.xlsx);;xls(*.xls);;csv(*.csv)') if file_path == '': return # 文件中写入数据 try: wb = workbook.Workbook() wb.encoding = 'utf-8' wa = wb.active for item in self.data: # 过滤无效数据 try: if ''.join(item) == '': continue except: pass wa.append(item) wb.save(file_path) self.status_bar.showMessage('保存完毕!') except Exception as e: print(e) #数据变化信号处理 def dealItemChanged(self,item): try: row,column = item.row(),item.column() self.data[row][column] = item.text() except Exception as e: print(e) pass #状态栏与工具栏的显示和隐藏 def triggeredView(self,state): sender = self.sender().text() if sender == '工具栏': if state: self.tool_bar.show() else: self.tool_bar.hide() else: if state: self.status_bar.show() else: self.status_bar.hide() def triggeredNew(self): print('New') pass def triggeredFind(self): self.find_widget.show() #重载信号,实现ESC隐藏查找窗口 def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.find_widget.hide() #聚焦到某个cell def positionFocus(self,x,y): self.table_view.verticalScrollBar().setSliderPosition(x) self.table_view.horizontalScrollBar().setSliderPosition(y) self.table_view.openPersistentEditor(self.mode.index(x, y)) self.table_view.setFocus() #得到所以匹配项的位置 def dataLocation(self): text = self.find_widget.line_edit1.text() self.res_pos = [] flag = 0 for i,row in enumerate(self.data): try: if ''.join(row) == '': continue except: pass for j,cell in enumerate(row): if text == str(cell): # print(i,j) self.res_pos.append((i,j)) item = self.mode.item(i,j) item.setBackground(QColor(255, 255, 0)) item.setForeground(QColor(255, 0, 0)) #转到到第一个匹配值的位置,并处于可编辑状态 if not flag: flag = 1 self.positionFocus(i,j) self.focus_pos = 0 #向下跳转 def downLocation(self): cnt = len(self.res_pos) if cnt == 0 or self.focus_pos == cnt-1: return try: self.table_view.closePersistentEditor( self.mode.index(self.res_pos[self.focus_pos][0],self.res_pos[self.focus_pos][1])) x, y = self.res_pos[self.focus_pos + 1] self.positionFocus(x,y) self.focus_pos += 1 except Exception as e: print(e) # 向上跳转 def upLocation(self): cnt = len(self.res_pos) if cnt == 0 or self.focus_pos == 0: return try: self.table_view.closePersistentEditor( self.mode.index(self.res_pos[self.focus_pos][0], self.res_pos[self.focus_pos][1])) x, y = self.res_pos[self.focus_pos - 1] self.positionFocus(x, y) self.focus_pos -= 1 except Exception as e: print(e) def triggeredHideFind(self): if self.res_pos is not None and len(self.res_pos): self.table_view.closePersistentEditor( self.mode.index(self.res_pos[self.focus_pos][0], self.res_pos[self.focus_pos][1])) for item in self.res_pos: x,y = item item = self.mode.item(x,y) item.setBackground(QColor(255, 255, 255)) item.setForeground(QColor(0, 0, 0)) self.find_widget.hide() #不清楚如何修改cell值,替换功能暂时无法上线 def onClickReplace(self): print('-'*50) cnt = len(self.res_pos) if self.res_pos is None or cnt == 0: return try: x, y = self.res_pos[self.focus_pos] text = self.find_widget.line_edit2.text() self.data[x][y] = text print(self.data[x][y]) except Exception as e: print(e) def onClickReplaceAll(self): pass #设置变量 def triggeredSetting(self): # 设置变量窗口 var_list = self.data[0] if self.data[0][0]!='' else self.data[0][1:] self.dialog = VariableSettingWindow.VariableSettingWindowDemo(var_list) self.dialog.signal.sender.connect(self.getVarList) self.dialog.show() def getVarList(self,lst): self.var_list = lst
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 _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()
class Pireal(QMainWindow): """ Main Window class This class is responsible for installing all application services. """ __SERVICES = {} __ACTIONS = {} # The name of items is the connection text TOOLBAR_ITEMS = [ 'create_database', 'open_database', 'save_database', '', # Is a separator! 'new_query', 'open_query', 'save_query', '', 'undo_action', 'redo_action', 'cut_action', 'copy_action', 'paste_action', '', 'create_new_relation', 'remove_relation', 'edit_relation', '', 'execute_queries' ] def __init__(self): QMainWindow.__init__(self) self.setWindowTitle(self.tr("Pireal")) self.setMinimumSize(700, 500) # Load window geometry qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) window_maximized = qsettings.value('window_max', True, type=bool) if window_maximized: self.showMaximized() else: size = qsettings.value('window_size') self.resize(size) position = qsettings.value('window_pos') self.move(position) # Toolbar self.toolbar = QToolBar(self) self.toolbar.setIconSize(QSize(22, 22)) self.toolbar.setMovable(False) self.addToolBar(self.toolbar) # Menu bar menubar = self.menuBar() self.__load_menubar(menubar) # Load notification widget after toolbar actions notification_widget = Pireal.get_service("notification") self.toolbar.addWidget(notification_widget) # Message error self._msg_error_widget = message_error.MessageError(self) # Central widget central_widget = Pireal.get_service("central") central_widget.databaseSaved.connect(notification_widget.show_text) central_widget.querySaved.connect(notification_widget.show_text) self.setCentralWidget(central_widget) central_widget.add_start_page() # Check for updates self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) self._thread.start() notification_widget.show_text( self.tr("Checking for updates..."), time_out=0) # Install service Pireal.load_service("pireal", self) @classmethod def get_service(cls, service): """ Return the instance of a loaded service """ return cls.__SERVICES.get(service, None) @classmethod def load_service(cls, name, instance): """ Load a service providing the service name and the instance """ cls.__SERVICES[name] = instance @classmethod def get_action(cls, name): """ Return the instance of a loaded QAction """ return cls.__ACTIONS.get(name, None) @classmethod def load_action(cls, name, action): """ Load a QAction """ cls.__ACTIONS[name] = action def __load_menubar(self, menubar): """ This method installs the menubar and toolbar, menus and QAction's, also connects to a slot each QAction. """ from src.gui import menu_actions from src import keymap # Keymap kmap = keymap.KEYMAP # Toolbar items toolbar_items = {} central = Pireal.get_service("central") # Load menu bar for item in menu_actions.MENU: menubar_item = menu_actions.MENU[item] menu_name = menubar_item['name'] items = menubar_item['items'] menu = menubar.addMenu(menu_name) for menu_item in items: if isinstance(menu_item, str): # Is a separator menu.addSeparator() else: action = menu_item['name'] obj, connection = menu_item['slot'].split(':') if obj.startswith('central'): obj = central else: obj = self qaction = menu.addAction(action) # Icon name is connection icon = QIcon(":img/%s" % connection) qaction.setIcon(icon) # Install shortcuts shortcut = kmap.get(connection, None) if shortcut is not None: qaction.setShortcut(shortcut) # Items for toolbar if connection in Pireal.TOOLBAR_ITEMS: toolbar_items[connection] = qaction # The name of QAction is the connection Pireal.load_action(connection, qaction) slot = getattr(obj, connection, None) if isinstance(slot, Callable): qaction.triggered.connect(slot) # Install toolbar self.__install_toolbar(toolbar_items) # Disable some actions self.set_enabled_db_actions(False) self.set_enabled_relation_actions(False) self.set_enabled_query_actions(False) self.set_enabled_editor_actions(False) def __install_toolbar(self, toolbar_items): for action in Pireal.TOOLBAR_ITEMS: qaction = toolbar_items.get(action, None) if qaction is not None: self.toolbar.addAction(qaction) else: self.toolbar.addSeparator() def __show_status_message(self, msg): status = Pireal.get_service("status") status.show_message(msg) def __on_thread_update_finished(self): self._thread.quit() # Clear notificator notification_widget = Pireal.get_service("notification") notification_widget.clear() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") self._thread.deleteLater() self._updater.deleteLater() def change_title(self, title): self.setWindowTitle("Pireal " + '[' + title + ']') def set_enabled_db_actions(self, value): """ Public method. Enables or disables db QAction """ actions = [ 'new_query', 'open_query', 'close_database', 'save_database', 'save_database_as', 'load_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_relation_actions(self, value): """ Public method. Enables or disables relation's QAction """ actions = [ 'create_new_relation', 'remove_relation', 'edit_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_query_actions(self, value): """ Public method. Enables or disables queries QAction """ actions = [ 'execute_queries', 'save_query' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_editor_actions(self, value): """ Public slot. Enables or disables editor actions """ actions = [ 'undo_action', 'redo_action', 'copy_action', 'cut_action', 'paste_action', 'zoom_in', 'zoom_out', 'comment', 'uncomment' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def about_qt(self): """ Show about qt dialog """ QMessageBox.aboutQt(self) def about_pireal(self): """ Show the bout Pireal dialog """ from src.gui.dialogs import about_dialog dialog = about_dialog.AboutDialog(self) dialog.exec_() def report_issue(self): """ Open in the browser the page to create new issue """ webbrowser.open("http://github.com/centaurialpha/pireal/issues/new") def show_hide_menubar(self): """ Change visibility of menu bar """ if self.menuBar().isVisible(): self.menuBar().hide() else: self.menuBar().show() def show_hide_toolbar(self): """ Change visibility of tool bar """ if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def show_error_message(self, text, syntax_error=True): self._msg_error_widget.show_msg(text, syntax_error) self._msg_error_widget.show() def closeEvent(self, event): qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) # Save window geometry if self.isMaximized(): qsettings.setValue('window_max', True) else: qsettings.setValue('window_max', False) qsettings.setValue('window_pos', self.pos()) qsettings.setValue('window_size', self.size()) central_widget = Pireal.get_service("central") # Save recent databases qsettings.setValue('recent_databases', central_widget.recent_databases) db = central_widget.get_active_db() if db is not None: # Save splitters size db.save_sizes() # Databases unsaved if db.modified: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Some changes where not saved")) msg.setText( self.tr("Do you want to save changes to the database?")) cancel_btn = msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: central_widget.save_database() if r == cancel_btn: event.ignore() # Query files unsaved_editors = central_widget.get_unsaved_queries() if unsaved_editors: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Unsaved Queries")) text = '\n'.join([editor.name for editor in unsaved_editors]) msg.setText(self.tr("{files}<br><br>Do you want to " "save them?".format(files=text))) cancel_btn = msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() if msg.clickedButton() == yes_btn: for editor in unsaved_editors: central_widget.save_query(editor) if msg.clickedButton() == cancel_btn: event.ignore()
class Pireal(QMainWindow): """ Main Window class This class is responsible for installing all application services. """ __SERVICES = {} __ACTIONS = {} # The name of items is the connection text TOOLBAR_ITEMS = [ 'create_database', 'open_database', 'save_database', '', # Is a separator! 'new_query', 'open_query', 'save_query', '', 'relation_menu', '', # 'create_new_relation', # 'remove_relation', # '', # 'add_tuple', # 'delete_tuple', # 'add_column', # 'delete_column', # '', 'execute_queries' ] def __init__(self): QMainWindow.__init__(self) self.setWindowTitle(self.tr("Pireal")) self.setMinimumSize(880, 600) # Load window geometry qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) window_maximized = qsettings.value('window_max', True, type=bool) if window_maximized: self.showMaximized() else: size = qsettings.value('window_size') self.resize(size) position = qsettings.value('window_pos') self.move(position) # Toolbar self.toolbar = QToolBar(self) self.toolbar.setFixedWidth(38) self.toolbar.setIconSize(QSize(38, 38)) self.toolbar.setMovable(False) self.addToolBar(Qt.RightToolBarArea, self.toolbar) # Menu bar menubar = self.menuBar() self.__load_menubar(menubar) # Load notification widget after toolbar actions notification_widget = Pireal.get_service("notification") self.toolbar.addWidget(notification_widget) # Message error # self._msg_error_widget = message_error.MessageError(self) # Central widget central_widget = Pireal.get_service("central") central_widget.databaseSaved.connect(notification_widget.show_text) central_widget.querySaved.connect(notification_widget.show_text) central_widget.databaseConected.connect(self.change_title) self.setCentralWidget(central_widget) central_widget.add_start_page() # Install service Pireal.load_service("pireal", self) @classmethod def get_service(cls, service): """ Return the instance of a loaded service """ return cls.__SERVICES.get(service, None) @classmethod def load_service(cls, name, instance): """ Load a service providing the service name and the instance """ cls.__SERVICES[name] = instance @classmethod def get_action(cls, name): """ Return the instance of a loaded QAction """ return cls.__ACTIONS.get(name, None) @classmethod def load_action(cls, name, action): """ Load a QAction """ cls.__ACTIONS[name] = action def __load_menubar(self, menubar): """ This method installs the menubar and toolbar, menus and QAction's, also connects to a slot each QAction. """ # Keymap kmap = keymap.KEYMAP central = Pireal.get_service("central") # Load menu bar rela_actions = [] for item in menu_actions.MENU: menubar_item = menu_actions.MENU[item] menu_name = menubar_item['name'] items = menubar_item['items'] menu = menubar.addMenu(menu_name) for menu_item in items: if isinstance(menu_item, str): # Is a separator menu.addSeparator() else: action = menu_item['name'] obj_name, connection = menu_item['slot'].split(':') obj = central if obj_name.startswith('pireal'): obj = self qaction = menu.addAction(action) # Icon name is connection icon = QIcon(":img/%s" % connection) qaction.setIcon(icon) # Install shortcuts shortcut = kmap.get(connection, None) if shortcut is not None: qaction.setShortcut(shortcut) # The name of QAction is the connection if item == "relation": if connection != "execute_queries": rela_actions.append(qaction) Pireal.load_action(connection, qaction) slot = getattr(obj, connection, None) if isinstance(slot, Callable): qaction.triggered.connect(slot) # Install toolbar # self.__install_toolbar(toolbar_items, rela_actions) self.__install_toolbar(rela_actions) # Disable some actions self.set_enabled_db_actions(False) self.set_enabled_relation_actions(False) self.set_enabled_query_actions(False) self.set_enabled_editor_actions(False) def __install_toolbar(self, rela_actions): menu = QMenu() tool_button = QToolButton() tool_button.setIcon(QIcon(":img/create_new_relation")) tool_button.setMenu(menu) tool_button.setPopupMode(QToolButton.InstantPopup) for item in self.TOOLBAR_ITEMS: if item: if item == "relation_menu": # Install menu for relation menu.addActions(rela_actions) self.toolbar.addWidget(tool_button) else: self.toolbar.addAction(self.__ACTIONS[item]) else: self.toolbar.addSeparator() def __show_status_message(self, msg): status = Pireal.get_service("status") status.show_message(msg) def __on_thread_update_finished(self): self._thread.quit() # Clear notificator notification_widget = Pireal.get_service("notification") notification_widget.clear() msg = QMessageBox(self) if not self._updater.error and self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new("http://centaurialpha.github.io/pireal") self._thread.deleteLater() self._updater.deleteLater() def change_title(self, title=''): if title: _title = title + " - Pireal " else: _title = "Pireal" self.setWindowTitle(_title) def set_enabled_db_actions(self, value): """ Public method. Enables or disables db QAction """ actions = [ 'new_query', 'open_query', 'close_database', 'save_database', 'save_database_as', 'load_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_relation_actions(self, value): """ Public method. Enables or disables relation's QAction """ actions = [ 'create_new_relation', 'remove_relation', 'add_tuple', 'delete_tuple', # 'add_column', # 'delete_column' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_query_actions(self, value): """ Public method. Enables or disables queries QAction """ actions = [ 'execute_queries', 'save_query' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_editor_actions(self, value): """ Public slot. Enables or disables editor actions """ actions = [ 'undo_action', 'redo_action', 'copy_action', 'cut_action', 'paste_action', 'zoom_in', 'zoom_out', 'comment', 'uncomment', 'search' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def about_qt(self): """ Show about qt dialog """ QMessageBox.aboutQt(self) def about_pireal(self): """ Show the bout Pireal dialog """ from src.gui.dialogs import about_dialog dialog = about_dialog.AboutDialog(self) dialog.exec_() def report_issue(self): """ Open in the browser the page to create new issue """ webbrowser.open("http://github.com/centaurialpha/pireal/issues/new") def show_hide_menubar(self): """ Change visibility of menu bar """ if self.menuBar().isVisible(): self.menuBar().hide() else: self.menuBar().show() def show_hide_toolbar(self): """ Change visibility of tool bar """ if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def show_error_message(self, text, syntax_error=True): self._msg_error_widget.show_msg(text, syntax_error) self._msg_error_widget.show() def save_user_settings(self): central_widget = Pireal.get_service("central") CONFIG.set_value("lastOpenFolder", central_widget.last_open_folder) CONFIG.set_value("recentFiles", central_widget.recent_databases) # Write settings CONFIG.save_settings() def closeEvent(self, event): self.save_user_settings() # Qt settings qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) # # Save window geometry if self.isMaximized(): qsettings.setValue('window_max', True) else: qsettings.setValue('window_max', False) qsettings.setValue('window_pos', self.pos()) qsettings.setValue('window_size', self.size()) central_widget = Pireal.get_service("central") db = central_widget.get_active_db() if db is not None: # Save splitters size db.save_sizes() # Databases unsaved if db.modified: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Algunos cambios no fueron guardados")) msg.setText( self.tr("Desea guardar los cambios en la base de datos?")) cancel_btn = msg.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Si"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: central_widget.save_database() if r == cancel_btn: event.ignore() # Query files unsaved_editors = central_widget.get_unsaved_queries() if unsaved_editors: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Consultas no guardadas")) text = '\n'.join([editor.name for editor in unsaved_editors]) msg.setText(self.tr("{files}\n\nQuiere guardarlas?".format( files=text))) cancel_btn = msg.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Si"), QMessageBox.YesRole) msg.exec_() if msg.clickedButton() == yes_btn: for editor in unsaved_editors: central_widget.save_query(editor) if msg.clickedButton() == cancel_btn: event.ignore()
class MainWindow(QMainWindow): """Main Window.""" def __init__(self, parent=None): """Initialize MainWindow.""" super(MainWindow, self).__init__(parent) self.ram_info, self.ram_timer = QLabel(self), QTimer(self) self.menubar, self.view = QMenu(self), WebView(self) self.ram_timer.timeout.connect(self.update_statusbar) self.ram_timer.start(60000) # Every 60 seconds self.statusBar().insertPermanentWidget(0, self.ram_info) self.setMinimumSize(640, 480) self.setMaximumSize(QDesktopWidget().screenGeometry().width() * 2, QDesktopWidget().screenGeometry().height() * 2) self.palette().setBrush(QPalette.Base, Qt.transparent) self.setPalette(self.palette()) # Transparent palette self.setAttribute(Qt.WA_OpaquePaintEvent, False) # no opaque paint self.setAttribute(Qt.WA_TranslucentBackground, True) # translucent QShortcut("Ctrl+q", self, activated=self.close) self.make_toolbar() self.make_menubar() self.update_statusbar() self.setCentralWidget(self.view) if qdarkstyle: self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) def paintEvent(self, event): """Paint transparent background,animated pattern,background text.""" painter, font = QPainter(self), self.font() painter.fillRect(event.rect(), Qt.transparent) # fill transparent rect painter.setPen(QPen(QColor(randint(9, 255), randint(9, 255), 255))) painter.rotate(30) # Rotate painter ~30 Degree font.setBold(True) # Set painter Font for text font.setPixelSize(100) painter.setFont(font) painter.drawText(99, 99, "Python Qt") # draw the background text painter.rotate(-30) # Rotate -30 the QPen back painter.setPen(Qt.NoPen) # set the pen to no pen painter.setBrush(QColor("black")) # Background Color painter.setOpacity(0.9) # Background Opacity painter.drawRoundedRect(self.rect(), 25, 25) # Back Rounded Borders for i in range(2048): # animated random dots background pattern x = randint(10, self.size().width() - 10) y = randint(10, self.size().height() - 10) painter.setPen(QPen(QColor(randint(9, 255), randint(9, 255), 255))) painter.drawPoint(x, y) QMainWindow.paintEvent(self, event) def make_toolbar(self, list_of_actions=None): """Make or Update the main Tool Bar.""" self.toolbar = QToolBar(self) self.left_spacer, self.right_spacer = QWidget(self), QWidget(self) self.left_spacer.setSizePolicy(1 | 2, 1 | 2) # Expanding, Expanding self.right_spacer.setSizePolicy(1 | 2, 1 | 2) # Expanding, Expanding self.toolbar.addAction("Menu", lambda: self.menubar.exec_(QCursor.pos())) self.toolbar.addWidget(self.left_spacer) self.toolbar.addSeparator() self.toolbar.addAction(QIcon.fromTheme("help-contents"), "Help and Docs", lambda: open_new_tab(__url__)) self.toolbar.addAction(QIcon.fromTheme("help-about"), "About Qt 5", lambda: QMessageBox.aboutQt(self)) self.toolbar.addAction(QIcon.fromTheme("help-about"), "About Python 3", lambda: open_new_tab('http://python.org/about')) self.toolbar.addAction(QIcon.fromTheme("application-exit"), "Quit", self.close) self.toolbar.addSeparator() if list_of_actions and len(list_of_actions): for action in list_of_actions: # if list_of_actions, add actions. self.toolbar.addAction(action) self.toolbar.addSeparator() self.toolbar.addWidget(self.right_spacer) self.addToolBar(self.toolbar) if sys.platform.startswith("win"): self.toolbar.hide() # windows dont have QIcon.fromTheme,so hide. return self.toolbar def make_menubar(self, list_of_actions=None): """Make or Update the main Tool Bar.""" self.menuBar().addMenu("&File").addAction("Exit", self.close) self.menubar.addMenu("&File").addAction("Exit", self.close) viewMenu = self.menuBar().addMenu("&View") viewMenu.addAction( "Toggle ToolBar", lambda: self.toolbar.setVisible(not self.toolbar.isVisible())) viewMenu.addAction( "Toggle StatusBar", lambda: self.statusBar().setVisible(not self.statusBar().isVisible())) viewMenu.addAction( "Toggle MenuBar", lambda: self.menuBar().setVisible(not self.menuBar().isVisible())) viewMenu2 = self.menubar.addMenu("&View") for action in viewMenu.actions(): viewMenu2.addAction(action) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("Full-Screen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move(QCursor.pos())) windowMenu.addSeparator() windowMenu.addAction("Increase size", lambda: self.resize( self.size().width() * 1.5, self.size().height() * 1.5)) windowMenu.addAction("Decrease size", lambda: self.resize( self.size().width() // 1.5, self.size().height() // 1.5)) windowMenu.addAction("Minimum size", lambda: self.resize(self.minimumSize())) windowMenu.addAction("Maximum size", lambda: self.resize(self.maximumSize())) windowMenu.addAction("Horizontal Wide", lambda: self.resize( self.maximumSize().width(), self.minimumSize().height())) windowMenu.addAction("Vertical Tall", lambda: self.resize( self.minimumSize().width(), self.maximumSize().height())) windowMenu.addSeparator() windowMenu.addAction("Disable Resize", lambda: self.setFixedSize(self.size())) windowMenu2 = self.menubar.addMenu("&Window") for action in windowMenu.actions(): windowMenu2.addAction(action) optionMenu = self.menuBar().addMenu("&Options") optionMenu.addAction("Set Interface Font...", lambda: self.setFont(QFontDialog.getFont(self)[0])) optionMenu.addAction("Load CSS Skin...", lambda: self.setStyleSheet(self.skin())) optionMenu.addAction("Take ScreenShoot...", lambda: self.grab().save( QFileDialog.getSaveFileName(self, "Save", os.path.expanduser("~"), "(*.png) PNG image file", "png")[0])) optionMenu2 = self.menubar.addMenu("&Options") for action in optionMenu.actions(): optionMenu2.addAction(action) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("About Qt 5", lambda: QMessageBox.aboutQt(self)) helpMenu.addAction("About Python 3", lambda: open_new_tab('https://www.python.org/about')) helpMenu.addSeparator() if sys.platform.startswith('linux'): helpMenu.addAction("View Source Code", lambda: open_new_tab(__file__)) helpMenu.addAction("View GitHub Repo", lambda: open_new_tab(__url__)) helpMenu.addAction("Report Bugs", lambda: open_new_tab(__url__ + '/issues?state=open')) helpMenu2 = self.menubar.addMenu("&Help") for action in helpMenu.actions(): helpMenu2.addAction(action) return self.menuBar() def update_statusbar(self, custom_message=None): """Make or Update the Status Bar.""" statusbar = self.statusBar() if resource: ram_use = int(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * resource.getpagesize() / 1024 / 1024) ram_byt = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') ram_all = int(ram_byt / 1024 / 1024) self.ram_info.setText("{0} / {1} Mb".format(ram_use, ram_all)) self.ram_info.setToolTip( "{0} of {1} MegaBytes of RAM Memory.".format(ram_use, ram_all)) if custom_message and len(custom_message): return statusbar.showMessage(custom_message) return statusbar.showMessage(__doc__) def skin(self, filename=None): """Open QSS from filename,if no QSS return None,if no filename ask.""" if not filename: filename = str(QFileDialog.getOpenFileName( self, __doc__ + " - Open QSS Skin", os.path.expanduser("~"), "CSS Cascading Style Sheet for Qt 5 (*.qss);;All (*.*)")[0]) if filename and os.path.isfile(filename): with open(filename, 'r', encoding="utf-8-sig") as file_to_read: text = file_to_read.read().strip() if text: return text def center(self): """Center and resize the window.""" self.showNormal() self.resize(QDesktopWidget().screenGeometry().width() // 1.25, QDesktopWidget().screenGeometry().height() // 1.25) qr = self.frameGeometry() qr.moveCenter(QDesktopWidget().availableGeometry().center()) return self.move(qr.topLeft()) def closeEvent(self, event): """Ask to Quit.""" return event.accept() if QMessageBox.question( self, "Close", "<h1>Quit ?.", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes else event.ignore()
class WidgetDemo(QWidget): def __init__(self, model, rows, columns): super(WidgetDemo, self).__init__() self.init_rows = rows self.init_columns = columns self.model = model self.initUI(model) def initUI(self, model): self.resize(800, 800) self.res_pos = [] self.focus_pos = None self.var_list = None self.signal = MySignal() #菜单栏 self.menu = QMenuBar() self.file = self.menu.addMenu('文件') self.edit = self.menu.addMenu('编辑') self.view = self.menu.addMenu('视图') self.help = self.menu.addMenu('帮助') #各菜单下的子菜单 #文件菜单下的子菜单 # self.new = self.file.addAction('新建') self.open = self.file.addAction('打开') self.save = self.file.addAction('保存') # self.save_as = self.file.addAction('另存为') #编辑菜单下的子菜单 self.cut = self.edit.addAction('剪切') self.copy = self.edit.addAction('复制') self.paste = self.edit.addAction('粘贴') self.delete = self.edit.addAction('删除') self.find = self.edit.addAction('查找') self.replace = self.edit.addAction('替换') # 快捷键 self.open.setShortcut('Ctrl+O') self.save.setShortcut('Ctrl+S') # self.new.setShortcut('Ctrl+N') self.find.setShortcut('Ctrl+F') #视图菜单下的子菜单 self.tool_view = QAction('工具栏', checkable=True) self.tool_view.setChecked(True) self.view.addAction(self.tool_view) self.statu_view = QAction('状态栏', checkable=True) self.statu_view.setChecked(True) self.view.addAction(self.statu_view) #帮助菜单下的子菜单 self.about = self.help.addAction('关于') #工具栏 self.tool_bar = QToolBar() # self.tool_bar.addAction(self.new) self.tool_bar.addAction(self.open) self.tool_bar.addAction(self.save) self.tool_bar.addAction(self.cut) self.tool_bar.addAction(self.copy) self.tool_bar.addAction(self.paste) self.tool_bar.addAction(self.find) # self.setting = QAction('变量设置') # self.setting.setEnabled(False) # self.tool_bar.addAction(self.setting) # self.tool_bar.addAction(self.replace) # #tool文本显示在下方 # self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) #findWidge self.find_widget = FindWidget() self.find_widget.hide() #表格 self.table_view = QTableView() self.table_view.setModel(self.model) #状态栏 self.status_bar = QStatusBar() self.status_bar.showMessage('状态栏') # 右键菜单栏 self.setContextMenuPolicy(Qt.CustomContextMenu) self.context_menu = QMenu() self.addRow_action = self.context_menu.addAction('增加一行') self.addRow_action.triggered.connect(self.addRow) self.delRow_action = self.context_menu.addAction('删除一行') self.delRow_action.triggered.connect( lambda: self.model.removeRow(self.table_view.currentIndex().row())) self.addColumn_action = self.context_menu.addAction('增加一列') self.addColumn_action.triggered.connect(self.addColumn) self.delColumn_action = self.context_menu.addAction('删除一列') self.delColumn_action.triggered.connect( lambda: self.model.removeColumn(self.table_view.currentIndex(). column())) self.customContextMenuRequested.connect(self.rightMenuShow) #创建布局 layout = QVBoxLayout() layout.addWidget(self.menu) layout.addWidget(self.tool_bar) layout.addWidget(self.find_widget) layout.addWidget(self.table_view) layout.addWidget(self.status_bar) self.setLayout(layout) #关联信号 self.open.triggered.connect(self.triggeredOpen) self.save.triggered.connect(self.triggeredSave) self.tool_view.triggered.connect(self.triggeredView) self.statu_view.triggered.connect(self.triggeredView) # self.new.triggered.connect(self.triggeredNew) self.find.triggered.connect(self.triggeredFind) self.find_widget.search.triggered.connect(self.dataLocation) self.find_widget.down_aciton.triggered.connect(self.downAcitonLocation) self.find_widget.up_aciton.triggered.connect(self.upAcitonLocation) self.find_widget.close_aciton.triggered.connect(self.triggeredHideFind) self.find_widget.repalce_button.clicked.connect(self.onClickReplace) self.find_widget.repalceAll_button.clicked.connect( self.onClickReplaceAll) # self.setting.triggered.connect(self.triggeredSetting) #美化 icon = QIcon() icon.addPixmap(QPixmap('./image/打开.png'), QIcon.Normal, QIcon.Off) self.open.setIcon(icon) icon.addPixmap(QPixmap('./image/保存.png'), QIcon.Normal, QIcon.Off) self.save.setIcon(icon) # icon.addPixmap(QPixmap('./image/新建.png'), QIcon.Normal, QIcon.Off) # self.new.setIcon(icon) icon.addPixmap(QPixmap('./image/剪切.png'), QIcon.Normal, QIcon.Off) self.cut.setIcon(icon) icon.addPixmap(QPixmap('./image/复制.png'), QIcon.Normal, QIcon.Off) self.copy.setIcon(icon) icon.addPixmap(QPixmap('./image/粘贴.png'), QIcon.Normal, QIcon.Off) self.paste.setIcon(icon) icon.addPixmap(QPixmap('./image/查找1.png'), QIcon.Normal, QIcon.Off) self.find.setIcon(icon) # icon.addPixmap(QPixmap('./image/设置.png'), QIcon.Normal, QIcon.Off) # self.setting.setIcon(icon) # icon.addPixmap(QPixmap('./image/替换.png'), QIcon.Normal, QIcon.Off) # self.replace.setIcon(icon) def showProgress(self, msg): self.status_bar.showMessage(msg) def loadData(self, model): print('load...') self.model = model self.table_view.setModel(self.model) qApp.processEvents() def triggeredOpen(self): self.status_bar.showMessage('打开文件', 5000) self.dialog = QFileDialog() self.dialog.setFileMode(QFileDialog.AnyFile) dir = r'D:/Learn-python-notes/projects/demo/Work/TCM_DSAS/data' self.dialog.setDirectory(dir) self.dialog.setFilter(QDir.Files) if self.dialog.exec_(): try: file_name = self.dialog.selectedFiles()[0] #这里线程实例化一定要实例化成员变量,否则线程容易销毁 self.thread = ReaderExcelThread(file_name) self.thread.standarModel_signal.connect(self.loadData) self.thread.progressRate_signal.connect(self.showProgress) self.thread.finished_signal.connect(self.thread.quit) self.thread.start() # self.setting.setEnabled(True) except Exception as e: print(e) pass def triggeredSave(self): self.status_bar.showMessage('保存文件', 5000) file_path, _ = QFileDialog.getSaveFileName( self, '保存文件', './data', 'ALL Files(*);;xlsx(*.xlsx);;xls(*.xls);;csv(*.csv)') if file_path == '': return # 文件中写入数据 try: self.write_thread = WriteExcelThread(file_path, self.model) self.write_thread.start_signal.connect(self.showProgress) self.write_thread.end_signal.connect(self.write_thread.quit) self.write_thread.start() self.status_bar.showMessage('保存完毕!') except Exception as e: print(e) #状态栏与工具栏的显示和隐藏 def triggeredView(self, state): sender = self.sender().text() if sender == '工具栏': if state: self.tool_bar.show() else: self.tool_bar.hide() else: if state: self.status_bar.show() else: self.status_bar.hide() # def triggeredNew(self): # print('New') # pass def triggeredFind(self): self.find_widget.show() #重载信号,实现ESC隐藏查找窗口 def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.find_widget.hide() #聚焦到某个cell def positionFocus(self, x, y): self.table_view.verticalScrollBar().setSliderPosition(x) self.table_view.horizontalScrollBar().setSliderPosition(y) self.table_view.openPersistentEditor(self.model.index(x, y)) self.table_view.setFocus() #得到所以匹配项的位置 def dataLocation(self): self.changeCellColor() text = self.find_widget.line_edit1.text() self.res_pos = [] flag = 0 rows = self.model.rowCount() columns = self.model.columnCount() try: for row in range(rows): for column in range(columns): if text == self.model.index(row, column).data(): self.res_pos.append((row, column)) item = self.model.item(row, column) item.setBackground(QColor(255, 255, 0)) item.setForeground(QColor(255, 0, 0)) #转到到第一个匹配值的位置,并处于可编辑状态 if not flag: flag = 1 self.positionFocus(row, column) self.focus_pos = 0 except Exception as e: print(e) #向下跳转 def downAcitonLocation(self): cnt = len(self.res_pos) if cnt == 0 or self.focus_pos == cnt - 1: return try: self.table_view.closePersistentEditor( self.model.index(self.res_pos[self.focus_pos][0], self.res_pos[self.focus_pos][1])) x, y = self.res_pos[self.focus_pos + 1] self.positionFocus(x, y) self.focus_pos += 1 except Exception as e: print(e) # 向上跳转 def upAcitonLocation(self): cnt = len(self.res_pos) if cnt == 0 or self.focus_pos == 0: return try: self.table_view.closePersistentEditor( self.model.index(self.res_pos[self.focus_pos][0], self.res_pos[self.focus_pos][1])) x, y = self.res_pos[self.focus_pos - 1] self.positionFocus(x, y) self.focus_pos -= 1 except Exception as e: print(e) def triggeredHideFind(self): self.changeCellColor() self.find_widget.hide() def changeCellColor(self): if self.res_pos is not None and len(self.res_pos): self.table_view.closePersistentEditor( self.model.index(self.res_pos[self.focus_pos][0], self.res_pos[self.focus_pos][1])) for item in self.res_pos: x, y = item item = self.model.item(x, y) item.setBackground(QColor(255, 255, 255)) item.setForeground(QColor(0, 0, 0)) def onClickReplace(self): cnt = len(self.res_pos) text = self.find_widget.line_edit2.text() if self.res_pos is None or cnt == 0: return try: x, y = self.res_pos[self.focus_pos] self.model.setItem(x, y, QStandardItem(text)) except Exception as e: print(e) def onClickReplaceAll(self): cnt = len(self.res_pos) if self.res_pos is None or cnt == 0: return try: text = self.find_widget.line_edit2.text() for x, y in self.res_pos: self.model.setItem(x, y, QStandardItem(text)) except Exception as e: print(e) # #设置变量 # def triggeredSetting(self): # self.getVar_thread = GetVarThread(self.model) # self.getVar_thread.send_signal.connect(self.initVarList) # self.getVar_thread.end_signal.connect(self.getVar_thread.quit) # self.getVar_thread.start() # # # # def initVarList(self,var_list): # dialog = VariableSettingWindow.VariableSettingWindowDemo(var_list) # dialog.signal.sender.connect(self.getVarList) # dialog.show() # # def getVarList(self,lst): # self.var_list = lst # print(lst) def addRow(self): # 当前行的下方添加一行 try: self.model.insertRows(self.table_view.currentIndex().row() + 1, 1) except Exception as e: print(e) def addColumn(self): self.model.insertColumns(self.table_view.currentIndex().column() + 1, 1) def rightMenuShow(self): try: #菜单显示的位置 self.context_menu.popup(QCursor.pos()) self.context_menu.show() except Exception as e: print(e)
class MainWindow(QMainWindow): """Main Window.""" def __init__(self, parent=None): """Initialize MainWindow.""" super(MainWindow, self).__init__(parent) self.ram_info, self.ram_timer = QLabel(self), QTimer(self) self.menubar, self.view = QMenu(self), WebView(self) self.ram_timer.timeout.connect(self.update_statusbar) self.ram_timer.start(60000) # Every 60 seconds self.statusBar().insertPermanentWidget(0, self.ram_info) self.setMinimumSize(640, 480) self.setMaximumSize(QDesktopWidget().screenGeometry().width() * 2, QDesktopWidget().screenGeometry().height() * 2) self.palette().setBrush(QPalette.Base, Qt.transparent) self.setPalette(self.palette()) # Transparent palette self.setAttribute(Qt.WA_OpaquePaintEvent, False) # no opaque paint self.setAttribute(Qt.WA_TranslucentBackground, True) # translucent QShortcut("Ctrl+q", self, activated=self.close) self.make_toolbar() self.make_menubar() self.update_statusbar() self.setCentralWidget(self.view) if qdarkstyle: self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) def paintEvent(self, event): """Paint transparent background,animated pattern,background text.""" painter, font = QPainter(self), self.font() painter.fillRect(event.rect(), Qt.transparent) # fill transparent rect painter.setPen(QPen(QColor(randint(9, 255), randint(9, 255), 255))) painter.rotate(30) # Rotate painter ~30 Degree font.setBold(True) # Set painter Font for text font.setPixelSize(100) painter.setFont(font) painter.drawText(99, 99, "Python Qt") # draw the background text painter.rotate(-30) # Rotate -30 the QPen back painter.setPen(Qt.NoPen) # set the pen to no pen painter.setBrush(QColor("black")) # Background Color painter.setOpacity(0.9) # Background Opacity painter.drawRoundedRect(self.rect(), 25, 25) # Back Rounded Borders for i in range(2048): # animated random dots background pattern x = randint(10, self.size().width() - 10) y = randint(10, self.size().height() - 10) painter.setPen(QPen(QColor(randint(9, 255), randint(9, 255), 255))) painter.drawPoint(x, y) QMainWindow.paintEvent(self, event) def make_toolbar(self, list_of_actions=None): """Make or Update the main Tool Bar.""" self.toolbar = QToolBar(self) self.left_spacer, self.right_spacer = QWidget(self), QWidget(self) self.left_spacer.setSizePolicy(1 | 2, 1 | 2) # Expanding, Expanding self.right_spacer.setSizePolicy(1 | 2, 1 | 2) # Expanding, Expanding self.toolbar.addAction("Menu", lambda: self.menubar.exec_(QCursor.pos())) self.toolbar.addWidget(self.left_spacer) self.toolbar.addSeparator() self.toolbar.addAction(QIcon.fromTheme("help-contents"), "Help and Docs", lambda: open_new_tab(__url__)) self.toolbar.addAction(QIcon.fromTheme("help-about"), "About Qt 5", lambda: QMessageBox.aboutQt(self)) self.toolbar.addAction(QIcon.fromTheme("help-about"), "About Python 3", lambda: open_new_tab('http://python.org/about')) self.toolbar.addAction(QIcon.fromTheme("application-exit"), "Quit", self.close) self.toolbar.addSeparator() if list_of_actions and len(list_of_actions): for action in list_of_actions: # if list_of_actions, add actions. self.toolbar.addAction(action) self.toolbar.addSeparator() self.toolbar.addWidget(self.right_spacer) self.addToolBar(self.toolbar) if sys.platform.startswith("win"): self.toolbar.hide() # windows dont have QIcon.fromTheme,so hide. return self.toolbar def make_menubar(self, list_of_actions=None): """Make or Update the main Tool Bar.""" self.menuBar().addMenu("&File").addAction("Exit", self.close) self.menubar.addMenu("&File").addAction("Exit", self.close) viewMenu = self.menuBar().addMenu("&View") viewMenu.addAction( "Toggle ToolBar", lambda: self.toolbar.setVisible(not self.toolbar.isVisible())) viewMenu.addAction( "Toggle StatusBar", lambda: self.statusBar().setVisible(not self.statusBar().isVisible( ))) viewMenu.addAction( "Toggle MenuBar", lambda: self.menuBar().setVisible(not self.menuBar().isVisible())) viewMenu2 = self.menubar.addMenu("&View") for action in viewMenu.actions(): viewMenu2.addAction(action) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("Full-Screen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move(QCursor.pos())) windowMenu.addSeparator() windowMenu.addAction( "Increase size", lambda: self.resize(self.size().width() * 1.5, self.size().height() * 1.5)) windowMenu.addAction( "Decrease size", lambda: self.resize(self.size().width() // 1.5, self.size().height() // 1.5)) windowMenu.addAction("Minimum size", lambda: self.resize(self.minimumSize())) windowMenu.addAction("Maximum size", lambda: self.resize(self.maximumSize())) windowMenu.addAction( "Horizontal Wide", lambda: self.resize(self.maximumSize().width(), self.minimumSize().height())) windowMenu.addAction( "Vertical Tall", lambda: self.resize(self.minimumSize().width(), self.maximumSize().height())) windowMenu.addSeparator() windowMenu.addAction("Disable Resize", lambda: self.setFixedSize(self.size())) windowMenu2 = self.menubar.addMenu("&Window") for action in windowMenu.actions(): windowMenu2.addAction(action) optionMenu = self.menuBar().addMenu("&Options") optionMenu.addAction( "Set Interface Font...", lambda: self.setFont(QFontDialog.getFont(self)[0])) optionMenu.addAction("Load CSS Skin...", lambda: self.setStyleSheet(self.skin())) optionMenu.addAction( "Take ScreenShoot...", lambda: self.grab().save( QFileDialog. getSaveFileName(self, "Save", os.path.expanduser("~"), "(*.png) PNG image file", "png")[0])) optionMenu2 = self.menubar.addMenu("&Options") for action in optionMenu.actions(): optionMenu2.addAction(action) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("About Qt 5", lambda: QMessageBox.aboutQt(self)) helpMenu.addAction( "About Python 3", lambda: open_new_tab('https://www.python.org/about')) helpMenu.addSeparator() if sys.platform.startswith('linux'): helpMenu.addAction("View Source Code", lambda: open_new_tab(__file__)) helpMenu.addAction("View GitHub Repo", lambda: open_new_tab(__url__)) helpMenu.addAction( "Report Bugs", lambda: open_new_tab(__url__ + '/issues?state=open')) helpMenu2 = self.menubar.addMenu("&Help") for action in helpMenu.actions(): helpMenu2.addAction(action) return self.menuBar() def update_statusbar(self, custom_message=None): """Make or Update the Status Bar.""" statusbar = self.statusBar() if resource: ram_use = int( resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * resource.getpagesize() / 1024 / 1024) ram_byt = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') ram_all = int(ram_byt / 1024 / 1024) self.ram_info.setText("{0} / {1} Mb".format(ram_use, ram_all)) self.ram_info.setToolTip( "{0} of {1} MegaBytes of RAM Memory.".format(ram_use, ram_all)) if custom_message and len(custom_message): return statusbar.showMessage(custom_message) return statusbar.showMessage(__doc__) def skin(self, filename=None): """Open QSS from filename,if no QSS return None,if no filename ask.""" if not filename: filename = str( QFileDialog.getOpenFileName( self, __doc__ + " - Open QSS Skin", os.path.expanduser("~"), "CSS Cascading Style Sheet for Qt 5 (*.qss);;All (*.*)") [0]) if filename and os.path.isfile(filename): with open(filename, 'r', encoding="utf-8-sig") as file_to_read: text = file_to_read.read().strip() if text: return text def center(self): """Center and resize the window.""" self.showNormal() self.resize(QDesktopWidget().screenGeometry().width() // 1.25, QDesktopWidget().screenGeometry().height() // 1.25) qr = self.frameGeometry() qr.moveCenter(QDesktopWidget().availableGeometry().center()) return self.move(qr.topLeft()) def closeEvent(self, event): """Ask to Quit.""" return event.accept() if QMessageBox.question( self, "Close", "<h1>Quit ?.", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes else event.ignore()
class IMonUi(QMainWindow): """Main Window.""" def __init__(self, parent=None): """Initializer.""" super().__init__(parent) self.setWindowTitle('imon: Image Monitor') self.setMinimumSize(1024, 768) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.setWindowIcon(QIcon('icon.ico')) # create variables self.watchdir = '' self.image_types = ('*.bmp', '*.gif', '*jpg', '*.jpeg', '*.png', '*.pbm', '*.pgm', '*.ppm', '*.xbm', '*.xpm', '*.svg', '*.BMP', '*.GIF', '*JPG', '*.JPEG', '*.PNG', '*.PBM', '*.PGM', '*.PPM', '*.XBM', '*.XPM', '*.SVG') self.wasMaximized = True # create border widgets self._createstatusbar() self._createtoolbar() # create central widget self.display = QLabel() self.display.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.display.pixmap = QPixmap('default.png') self.display.setPixmap(self.display.pixmap) self.display.setAlignment(Qt.AlignCenter) self.setCentralWidget(self.display) self.installEventFilter(self) self.status_message = '{0: <100}'.format(os.path.dirname(os.path.realpath(__file__)) + '/default.png') + ' ' +\ '{0: <50}'.format( str(self.display.pixmap.width()) + 'x' + str(self.display.pixmap.height())) + ' ' +\ '{0: <50}'.format(str(f'{os.path.getsize("default.png") / 1000000:.2f}') + 'MB') self.status.showMessage(self.status_message) self.setStatusBar(self.status) # create timer action to update image self.timer = QTimer() self.timer.timeout.connect(self.updateimage) self.timer.start(100) def updateimage(self): img = get_latest_image(self.watchdir, self.image_types) if img != '': # update display self.display.pixmap = QPixmap(img) self.display.setPixmap(self.display.pixmap) self.display.setPixmap( self.display.pixmap.scaled(self.centralWidget().size(), Qt.KeepAspectRatio)) # update status bar self.status_message = '{0: <100}'.format(img) + ' ' +\ '{0: <50}'.format(str(self.display.pixmap.width()) + 'x' + str(self.display.pixmap.height())) + ' ' +\ '{0: <50}'.format(str(f'{os.path.getsize(img) / 1000000:.2f}') + 'MB') self.status.showMessage(self.status_message) self.setStatusBar(self.status) def _createtoolbar(self): self.tools = QToolBar() self.addToolBar(self.tools) self.tools.addAction('Exit', self.close) self.tools.addAction('Select Watch Directory', self._selectdir) self.tools.addAction('Fullscreen', self._gofullscreen) def _createstatusbar(self): self.status = QStatusBar() self.setStatusBar(self.status) def _selectdir(self): self.watchdir = str( QFileDialog.getExistingDirectory(self, "Select Directory")) def _gofullscreen(self): if self.isMaximized() == True: self.wasMaximized = True else: self.wasMaximized = False self.showFullScreen() self.tools.hide() self.status.hide() def eventFilter(self, source, event): if source is self and event.type() == QEvent.Resize: self.display.setPixmap( self.display.pixmap.scaled(self.centralWidget().size(), Qt.KeepAspectRatio)) return super(IMonUi, self).eventFilter(source, event) def keyPressEvent(self, e): # TODO: when running showMaximized after being fullscreen, it behaves like showNormal if e.key() == Qt.Key_Escape: if self.isFullScreen(): if self.wasMaximized == True: self.showMaximized() self.tools.show() self.status.show() else: self.showNormal() self.tools.show() self.status.show() if e.key() == Qt.Key_F11: if self.isFullScreen(): if self.wasMaximized == True: self.showMaximized() self.tools.show() self.status.show() else: self.showNormal() self.tools.show() self.status.show() else: self._gofullscreen()
class GEditor(QMainWindow, _HalWidgetBase): percentDone = pyqtSignal(int) def __init__(self, parent=None, designer=False): if not designer: parent = None super().__init__(parent) self._show_editor = True self.load_dialog_code = 'LOAD' self.save_dialog_code = 'SAVE' self.dialog_code = 'KEYBOARD' STATUS.connect('general', self.returnFromDialog) self.isCaseSensitive = 0 self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("PyQt5 editor test example") # make editor self.editor = GcodeDisplay(self) self.setCentralWidget(self.editor) # class patch editor's function to ours # so we get the lines percent update self.editor.emit_percent = self.emit_percent self.editor.setReadOnly(True) self.editor.setModified(False) self.createActions() # Create toolbar and add action self.toolBar = QToolBar('File') self.toolBar.setObjectName('{}_toolbarfile'.format(self.objectName())) self.toolBar.addAction(self.newAction) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.saveAction) self.toolBar.addAction(self.exitAction) self.addToolBar(Qt.TopToolBarArea, self.toolBar) #self.toolBar.addSeparator() self.toolBarLexer = QToolBar('Lexer') self.toolBarLexer.setObjectName('{}_toolbarlexer'.format( self.objectName())) # add lexer actions self.toolBarLexer.addAction(self.gCodeLexerAction) self.toolBarLexer.addAction(self.pythonLexerAction) self.toolBarLexer.addSeparator() self.label = QLabel( '''<html><head/><body><p><span style=" font-size:20pt; font-weight:600;">Edit Mode</span></p></body></html>''' ) self.toolBarLexer.addWidget(self.label) self.addToolBar(Qt.TopToolBarArea, self.toolBarLexer) # Create toolbar and add action self.toolBarEdit = QToolBar('Edit') self.toolBarEdit.setObjectName('{}_toolbaredit'.format( self.objectName())) self.toolBarEdit.addAction(self.undoAction) self.toolBarEdit.addAction(self.redoAction) self.toolBarEdit.addSeparator() self.toolBarEdit.addAction(self.replaceAction) self.toolBarEdit.addAction(self.findAction) self.toolBarEdit.addAction(self.previousAction) self.toolBarEdit.addSeparator() self.toolBarEdit.addAction(self.caseAction) self.addToolBar(Qt.BottomToolBarArea, self.toolBarEdit) self.toolBarEntry = QToolBar('entry') self.toolBarEntry.setObjectName('{}_toolbarentry'.format( self.objectName())) frame = QFrame() box = QHBoxLayout() box.addWidget(self.searchText) box.addWidget(self.replaceText) box.addStretch(1) frame.setLayout(box) self.toolBarEntry.addWidget(frame) self.addToolBar(Qt.BottomToolBarArea, self.toolBarEntry) self.readOnlyMode(save=False) def createActions(self): # Create new action self.newAction = QAction(QIcon.fromTheme('document-new'), 'New', self) self.newAction.setShortcut('Ctrl+N') self.newAction.setStatusTip('New document') self.newAction.triggered.connect(self.newCall) # Create open action self.openAction = QAction(QIcon.fromTheme('document-open'), '&Open', self) self.openAction.setShortcut('Ctrl+O') self.openAction.setStatusTip('Open document') self.openAction.triggered.connect(self.openCall) # Create save action self.saveAction = QAction(QIcon.fromTheme('document-save'), '&Save', self) self.saveAction.setShortcut('Ctrl+S') self.saveAction.setStatusTip('Save document') self.saveAction.triggered.connect(self.saveCall) # Create exit action self.exitAction = QAction(QIcon.fromTheme('application-exit'), '&Exit', self) self.exitAction.setShortcut('Ctrl+Q') self.exitAction.setToolTip('Exit Edit Mode') self.exitAction.setStatusTip('Exit Edit Mode') self.exitAction.triggered.connect(self.exitCall) # Create gcode lexer action self.gCodeLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Gcode\nLexer', self) self.gCodeLexerAction.setCheckable(1) self.gCodeLexerAction.setShortcut('Ctrl+G') self.gCodeLexerAction.setStatusTip('Set Gcode highlighting') self.gCodeLexerAction.triggered.connect(self.gcodeLexerCall) # Create gcode lexer action self.pythonLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Python\nLexer', self) self.pythonLexerAction.setShortcut('Ctrl+P') self.pythonLexerAction.setStatusTip('Set Python highlighting') self.pythonLexerAction.triggered.connect(self.pythonLexerCall) self.searchText = QLineEdit(self) self.searchText.setToolTip('Search Text') self.searchText.setStatusTip('Text to search for') self.searchText.installEventFilter(self) self.replaceText = QLineEdit(self) self.replaceText.setToolTip('Replace Text') self.replaceText.setStatusTip('Replace search text with this text') self.replaceText.installEventFilter(self) # Create new action self.undoAction = QAction(QIcon.fromTheme('edit-undo'), 'Undo', self) self.undoAction.setStatusTip('Undo') self.undoAction.triggered.connect(self.undoCall) # create redo action self.redoAction = QAction(QIcon.fromTheme('edit-redo'), 'Redo', self) self.redoAction.setStatusTip('Redo') self.redoAction.triggered.connect(self.redoCall) # create replace action self.replaceAction = QAction(QIcon.fromTheme('edit-find-replace'), 'Replace', self) self.replaceAction.setStatusTip('Replace text') self.replaceAction.triggered.connect(self.replaceCall) # create find action self.findAction = QAction(QIcon.fromTheme('edit-find'), 'Find', self) self.findAction.setStatusTip('Find next occurrence of text') self.findAction.triggered.connect(self.findCall) # create next action self.previousAction = QAction(QIcon.fromTheme('go-previous'), 'Find Previous', self) self.previousAction.setStatusTip('Find previous occurrence of text') self.previousAction.triggered.connect(self.previousCall) # create case action self.caseAction = QAction(QIcon.fromTheme('edit-case'), 'Aa', self) self.caseAction.setToolTip('Toggle Match Case') self.caseAction.setStatusTip('Toggle between any case and match case') self.caseAction.setCheckable(1) self.caseAction.triggered.connect(self.caseCall) # catch focusIn event to pop keyboard dialog def eventFilter(self, obj, event): if event.type() == QEvent.FocusIn: if isinstance(obj, QLineEdit): # only if mouse selected if event.reason() == 0: self.popEntry(obj) return super().eventFilter(obj, event) # callback functions built for easy class patching ########## # want to refrain from renaming these functions as it will break # any class patch user's use # we split them like this so a user can intercept the callback # but still call the original functionality def caseCall(self): self.case() def case(self): self.isCaseSensitive -= 1 self.isCaseSensitive *= -1 def exitCall(self): self.exit() def exit(self): if self.editor.isModified(): result = self.killCheck() if result: try: self.editor.reload_last(None) except Exception as e: print(e) self.readOnlyMode() return result return True def findCall(self): self.find() def find(self): self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=True) def previousCall(self): self.previous() def previous(self): self.editor.setCursorPosition(self.editor.getSelection()[0], self.editor.getSelection()[1]) self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=False) def gcodeLexerCall(self): self.gcodeLexer() def gcodeLexer(self): self.editor.set_gcode_lexer() def nextCall(self): self.next() def next(self): self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=False) self.editor.search_Next() def newCall(self): self.new() def new(self): if self.editor.isModified(): result = self.killCheck() if result: self.editor.new_text() else: self.editor.new_text() def openCall(self): self.open() def open(self): self.getFileName() def openReturn(self, f): ACTION.OPEN_PROGRAM(f) self.editor.setModified(False) def pythonLexerCall(self): self.pythonLexer() def pythonLexer(self): self.editor.set_python_lexer() def redoCall(self): self.redo() def redo(self): self.editor.redo() def replaceCall(self): self.replace() def replace(self): self.editor.replace_text(str(self.replaceText.text())) self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=True) def saveCall(self): self.save() def save(self): self.getSaveFileName() def saveReturn(self, fname): saved = ACTION.SAVE_PROGRAM(self.editor.text(), fname) if saved is not None: self.editor.setModified(False) ACTION.OPEN_PROGRAM(saved) def undoCall(self): self.undo() def undo(self): self.editor.undo() # helper functions ############################################ def saveSettings(self): self.SETTINGS_.beginGroup("geditor-{}".format(self.objectName())) self.SETTINGS_.setValue('state', QVariant(self.saveState().data())) self.SETTINGS_.endGroup() def restoreSettings(self): # set recorded toolbar settings self.SETTINGS_.beginGroup("geditor-{}".format(self.objectName())) state = self.SETTINGS_.value('state') self.SETTINGS_.endGroup() if not state is None: try: self.restoreState(QByteArray(state)) except Exception as e: print(e) else: return True return False def editMode(self): self.editor.setReadOnly(False) result = self.restoreSettings() check = (self.toolBar.toggleViewAction().isChecked() and self.toolBarEdit.toggleViewAction().isChecked() and self.toolBarEntry.toggleViewAction().isChecked() and self.toolBarLexer.toggleViewAction().isChecked()) if not check: self.toolBar.toggleViewAction().setChecked(False) self.toolBar.toggleViewAction().trigger() if not result: self.toolBarEdit.toggleViewAction().setChecked(False) self.toolBarEdit.toggleViewAction().trigger() self.toolBarEntry.toggleViewAction().setChecked(False) self.toolBarEntry.toggleViewAction().trigger() self.toolBarLexer.toggleViewAction().setChecked(False) self.toolBarLexer.toggleViewAction().trigger() def readOnlyMode(self, save=True): if save: self.saveSettings() self.editor.setReadOnly(True) self.toolBar.toggleViewAction().setChecked(True) self.toolBar.toggleViewAction().trigger() self.toolBarEdit.toggleViewAction().setChecked(True) self.toolBarEdit.toggleViewAction().trigger() self.toolBarEntry.toggleViewAction().setChecked(True) self.toolBarEntry.toggleViewAction().trigger() self.toolBarLexer.toggleViewAction().setChecked(True) self.toolBarLexer.toggleViewAction().trigger() def getFileName(self): mess = { 'NAME': self.load_dialog_code, 'ID': '%s__' % self.objectName(), 'TITLE': 'Load Editor' } STATUS.emit('dialog-request', mess) def getSaveFileName(self): mess = { 'NAME': self.save_dialog_code, 'ID': '%s__' % self.objectName(), 'TITLE': 'Save Editor' } STATUS.emit('dialog-request', mess) def popEntry(self, obj): mess = { 'NAME': self.dialog_code, 'ID': '%s__' % self.objectName(), 'OVERLAY': False, 'OBJECT': obj, 'TITLE': 'Set Entry for {}'.format(obj.objectName().upper()), 'GEONAME': '_{}'.format(self.dialog_code) } STATUS.emit('dialog-request', mess) LOG.debug('message sent:{}'.format(mess)) # process the STATUS return message def returnFromDialog(self, w, message): if message.get('NAME') == self.load_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__' % self.objectName()) if path and code: self.openReturn(path) elif message.get('NAME') == self.save_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__' % self.objectName()) if path and code: self.saveReturn(path) elif message.get('NAME') == self.dialog_code: txt = message['RETURN'] code = bool(message.get('ID') == '%s__' % self.objectName()) if code and txt is not None: LOG.debug('message return:{}'.format(message)) obj = message.get('OBJECT') obj.setText(str(txt)) def killCheck(self): choice = QMessageBox.question( self, 'Warning!', """This file has changed since loading and has not been saved. You will lose your changes. Still want to proceed?""", QMessageBox.Yes | QMessageBox.No) if choice == QMessageBox.Yes: return True else: return False def emit_percent(self, percent): self.percentDone.emit(percent) def select_lineup(self): self.editor.select_lineup(None) def select_linedown(self): self.editor.select_linedown(None) def select_line(self, line): self.editor.highlight_line(None, line) def jump_line(self, jump): self.editor.jump_line(jump) def get_line(self): return self.editor.getCursorPosition()[0] + 1 def set_margin_width(self, width): self.editor.set_margin_width(width) def set_font(self, font): self.editor.font = font for i in range(0, 4): self.editor.lexer.setFont(font, i) def set_background_color(self, color): self.editor.set_background_color(color) def isReadOnly(self): return self.editor.isReadOnly() # designer recognized getter/setters # auto_show_mdi status # These adjust the self.editor instance def set_auto_show_mdi(self, data): self.editor.auto_show_mdi = data def get_auto_show_mdi(self): return self.editor.auto_show_mdi def reset_auto_show_mdi(self): self.editor.auto_show_mdi = True auto_show_mdi_status = pyqtProperty(bool, get_auto_show_mdi, set_auto_show_mdi, reset_auto_show_mdi) # designer recognized getter/setters # auto_show_manual status def set_auto_show_manual(self, data): self.editor.auto_show_manual = data def get_auto_show_manual(self): return self.editor.auto_show_manual def reset_auto_show_manual(self): self.editor.auto_show_manual = True auto_show_manual_status = pyqtProperty(bool, get_auto_show_manual, set_auto_show_manual, reset_auto_show_manual) # designer recognized getter/setters # show_editor status def set_show_editor(self, data): self._show_editor = data if data: self.toolBar.show() self.toolBarLexer.show() self.toolBarEntry.show() self.toolBarEdit.show() else: self.toolBar.hide() self.toolBarLexer.hide() self.toolBarEntry.hide() self.toolBarEdit.hide() def get_show_editor(self): return self._show_editor def reset_show_editor(self): self._show_editor = True self.toolBar.show() self.toolBarLexer.show() self.toolBarEntry.show() self.toolBarEdit.show() show_editor = pyqtProperty(bool, get_show_editor, set_show_editor, reset_show_editor)
class App(QMainWindow): @property def QSETTINGS(self): return QSettings(QSettings.IniFormat, QSettings.UserScope, "mdviewer", "session") def __init__(self, parent=None, filename=""): QMainWindow.__init__(self, parent) self.filename = filename or os.path.join(app_dir, u"README.md") # Set environment variables self.set_env() # Configure Preview window self.set_window_title() self.resize(self.QSETTINGS.value("size", QSize(800,400))) self.move(self.QSETTINGS.value("pos", QPoint(0,0))) # Activate WebView self.web_view = QWebView() self.setCentralWidget(self.web_view) self.scroll_pos = {} # Configure and start file watcher thread self.thread1 = WatcherThread(self.filename) self.thread1.update.connect(self.update) self.watcher = QFileSystemWatcher([self.filename]) self.watcher.fileChanged.connect(self.thread1.run) self.thread1.run() self.web_view.loadFinished.connect(self.after_update) # Get style sheet self.stylesheet = self.QSETTINGS.value("stylesheet", "default.css") # Set GUI menus and toolbars self.set_menus() self.set_search_bar() def set_env(self): path, name = os.path.split(os.path.abspath(self.filename)) ext = os.path.splitext(name)[-1].lower() os.environ["MDVIEWER_EXT"] = ext[1:] os.environ["MDVIEWER_FILE"] = name os.environ["MDVIEWER_ORIGIN"] = path def set_window_title(self): _path, name = os.path.split(os.path.abspath(self.filename)) self.setWindowTitle(u"%s – MDviewer" % (name)) def update(self, text, warn): "Update Preview." self.web_view.settings().setAttribute(QWebSettings.JavascriptEnabled, True) self.web_view.settings().setAttribute(QWebSettings.PluginsEnabled, True) self.web_view.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # Configure link behavior self.web_view.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.web_view.linkClicked.connect(lambda url: self.handle_link_clicked(url)) # Save scroll position if not self.web_view.page().currentFrame().scrollPosition() == QPoint(0,0): self.scroll_pos[self.filename] = self.web_view.page().currentFrame().scrollPosition() # Update Preview self.web_view.setHtml(text, baseUrl=QUrl.fromLocalFile(os.path.join(os.getcwd(), self.filename))) # Load JavaScript and core CSS scr = os.path.join(app_dir, "mdviewer.js") css = os.path.join(app_dir, "mdviewer.css") add_resources = """ (function() { var head = document.querySelector("head"); var css = document.createElement("link"); css.rel = "stylesheet"; css.href = "%s"; css.id = "coreCSS"; head.appendChild(css); var script = document.createElement("script"); script.type = "text/javascript"; script.src = "%s"; script.id = "coreJS"; script.setAttribute("defer", ""); head.appendChild(script); })() """ % (css, scr) self.web_view.page().currentFrame().evaluateJavaScript(add_resources) # Display processor warnings if warn: QMessageBox.warning(self, "Processor message", warn) def after_update(self): "Restore scroll position." try: pos = self.scroll_pos[self.filename] except KeyError: pass else: self.web_view.page().currentFrame().evaluateJavaScript("window.scrollTo(%s, %s)" % (pos.x(), pos.y())) def open_file(self): filename, _filter = QFileDialog.getOpenFileName(self, "Open File", os.path.dirname(self.filename)) if filename != "": self.filename = self.thread1.filename = filename self.set_env() self.set_window_title() self.thread1.run() else: pass def save_html(self): filename, _filter = QFileDialog.getSaveFileName(self, "Save File", os.path.dirname(self.filename)) if filename != "": proc = Settings.get("processor_path", "pandoc") args = Settings.get("processor_args", "") args = ("%s" % (args)).split() + [self.filename] caller = QProcess() caller.start(proc, args) caller.waitForFinished() html = str(caller.readAllStandardOutput(), "utf8") with io.open(filename, "w", encoding="utf8") as f: f.writelines(html) f.close() else: pass def find(self, text, btn=""): page = self.web_view.page() back = page.FindFlags(1) if btn is self.prev else page.FindFlags(0) case = page.FindFlags(2) if self.case.isChecked() else page.FindFlags(0) wrap = page.FindFlags(4) if self.wrap.isChecked() else page.FindFlags(0) page.findText("", page.FindFlags(8)) page.findText(text, back | wrap | case) def set_search_bar(self): self.search_bar = QToolBar() self.search_bar.setMovable(False) self.search_bar.setFloatable(False) self.search_bar.setVisible(False) self.search_bar.layout().setSpacing(1) self.addToolBar(0x8, self.search_bar) self.text = QLineEdit(self) self.text.setClearButtonEnabled(True) self.text.setPlaceholderText(u"Search") self.case = QCheckBox(u"Case sensitive", self) self.wrap = QCheckBox(u"Wrap", self) self.next = QPushButton(u"Next", self) self.next.setToolTip(u"Find next") self.next.setShortcut(QKeySequence("Return")) self.next.setDisabled(True) self.prev = QPushButton(u"Previous", self) self.prev.setToolTip(u"Find previous") self.prev.setShortcut(QKeySequence("Shift+Return")) self.prev.setDisabled(True) self.done = QPushButton(u"Done", self) self.done.setToolTip(u"Hide Search bar") self.done.setShortcut(QKeySequence("Esc")) def _enable_nav(): if self.text.text() == "": self.next.setDisabled(True) self.prev.setDisabled(True) else: self.next.setDisabled(False) self.prev.setDisabled(False) def _toggle_btn(btn=""): self.text.setFocus() self.find(self.text.text(), btn) def _hide(): if self.search_bar.isVisible(): self.search_bar.hide() self.search_bar.addWidget(self.done) self.search_bar.addSeparator() self.search_bar.addWidget(self.case) self.search_bar.addWidget(self.wrap) self.search_bar.addWidget(self.text) self.search_bar.addSeparator() self.search_bar.addWidget(self.next) self.search_bar.addWidget(self.prev) for btn in (self.prev, self.next): btn.pressed[()].connect(lambda btn = btn: _toggle_btn(btn)) self.done.pressed.connect(_hide) self.text.textChanged.connect(self.find) self.text.textChanged.connect(_enable_nav) def show_search_bar(self): self.search_bar.show() self.text.setFocus() self.text.selectAll() def print_doc(self): dialog = QPrintPreviewDialog() dialog.paintRequested.connect(self.web_view.print_) dialog.exec_() def quit(self, QCloseEvent): self.QSETTINGS.setValue("size", self.size()) self.QSETTINGS.setValue("pos", self.pos()) self.QSETTINGS.setValue("stylesheet", self.stylesheet) QtWidgets.qApp.quit() def zoom_in(self): self.web_view.setZoomFactor(self.web_view.zoomFactor()+.1) def zoom_out(self): self.web_view.setZoomFactor(self.web_view.zoomFactor()-.1) def zoom_reset(self): self.web_view.setZoomFactor(1) def scroll_down(self): self.web_view.page().currentFrame().scroll(0, +self.web_view.page().viewportSize().height()) def scroll_up(self): self.web_view.page().currentFrame().scroll(0, -self.web_view.page().viewportSize().height()) def toggle_toc(self): self.web_view.page().currentFrame().evaluateJavaScript("toggleTOC()") def handle_link_clicked(self, url): if url.isLocalFile(): if url.toLocalFile() == os.path.abspath(self.filename) and url.hasFragment(): self.web_view.page().currentFrame().scrollToAnchor(url.fragment()) return else: QDesktopServices.openUrl(url) else: QDesktopServices.openUrl(url) @staticmethod def set_stylesheet(self, stylesheet="default.css"): path = os.path.join(css_dir, stylesheet) url = QUrl.fromLocalFile(path) self.web_view.settings().setUserStyleSheetUrl(url) self.stylesheet = stylesheet def about(self): msg_about = QMessageBox(0, "About MDviewer", u"MDviewer\n\nVersion: %s" % (VERSION), parent=self) msg_about.show() def report_issue(self): url = QUrl.fromUserInput("https://github.com/saf-dmitry/mdviewer/issues") QDesktopServices.openUrl(url) def set_menus(self): menubar = self.menuBar() file_menu = menubar.addMenu("&File") for d in ( {"name": u"&Open...", "shct": "Ctrl+O", "func": self.open_file}, {"name": u"&Save HTML...", "shct": "Ctrl+S", "func": self.save_html}, {"name": u"&Find...", "shct": "Ctrl+F", "func": self.show_search_bar}, {"name": u"&Print...", "shct": "Ctrl+P", "func": self.print_doc}, {"name": u"&Quit", "shct": "Ctrl+Q", "func": self.quit} ): action = QAction(d["name"], self) action.setShortcut(QKeySequence(d["shct"])) action.triggered.connect(d["func"]) file_menu.addAction(action) view_menu = menubar.addMenu("&View") for d in ( {"name": u"Zoom &In", "shct": "Ctrl++", "func": self.zoom_in}, {"name": u"Zoom &Out", "shct": "Ctrl+-", "func": self.zoom_out}, {"name": u"&Actual Size", "shct": "Ctrl+0", "func": self.zoom_reset} ): action = QAction(d["name"], self) action.setShortcut(QKeySequence(d["shct"])) action.triggered.connect(d["func"]) view_menu.addAction(action) style_menu = menubar.addMenu("&Style") style_menu.setStyleSheet("menu-scrollable: 1") style_menu.setDisabled(True) if os.path.exists(css_dir): files = sorted(os.listdir(css_dir)) files = [f for f in files if f.endswith(".css")] if len(files) > 0: style_menu.setDisabled(False) group = QActionGroup(self, exclusive=True) for i, f in enumerate(files, start=1): name = os.path.splitext(f)[0].replace("&", "&&") action = group.addAction(QtWidgets.QAction(name, self)) action.triggered.connect( lambda x, stylesheet = f: self.set_stylesheet(self, stylesheet)) if i < 10: action.setShortcut(QKeySequence("Ctrl+%d" % i)) action.setCheckable(True) style_menu.addAction(action) if f == self.stylesheet: action.trigger() help_menu = menubar.addMenu("&Help") for d in ( {"name": u"&About...", "func": self.about}, {"name": u"&Report an Issue", "func": self.report_issue}, ): action = QAction(d["name"], self) action.triggered.connect(d["func"]) help_menu.addAction(action) # Redefine reload action reload_action = self.web_view.page().action(QWebPage.Reload) reload_action.setShortcut(QKeySequence.Refresh) reload_action.triggered.connect(self.thread1.run) self.web_view.addAction(reload_action) # Define additional shortcuts QShortcut(QKeySequence("j"), self, activated=self.scroll_down) QShortcut(QKeySequence("k"), self, activated=self.scroll_up) QShortcut(QKeySequence("t"), self, activated=self.toggle_toc) def closeEvent(self, event): self.quit(event) event.accept()
class QuickPanel(QWidget, WidgetManager): """ 一个快捷面板。类似于KDE的桌面。只不过功能会简单些。主要的方法有: addQuickAccessShortcut() 供插件添加一个系统快捷方式。 removeQuickAccessShortcut() 删除插件添加的系统快捷方式。 toggle() 如果快捷面板已经显示就隐藏它。如果处于隐藏状态则显示它。 showAndGetFocus() 显示快捷面板并且将焦点放置于常用的位置。 registerWidget() 注册部件 unregisterWidget() 反注册部件 """ def __init__(self, platform): QWidget.__init__(self, None, Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setWindowModality(Qt.ApplicationModal) self.platform = platform self.db = QuickPanelDatabase(platform.databaseFile) self.createActions() self.createControls() self.loadSettings() self.makeConnections() #Besteam系统快捷方式作为QuickPanel提供的一个服务,必须定义在这里 #虽然部件可能没有运行。QuickPanel也应该记住其它插件添加的快捷方式,以便在 #用户添加QuickAccessWidget之后,可以显示所有的系统快捷方式 self.quickAccessModel = QuickAccessModel() def createActions(self): self.actionChangeBackground = QAction(self) self.actionChangeBackground.setIcon(QIcon(":/images/change_background.png")) self.actionChangeBackground.setText(self.tr("Change &Background")) self.actionChangeBackground.setIconText(self.tr("Background")) self.actionChangeBackground.setToolTip(self.tr("Change Background")) self.actionClose = QAction(self) self.actionClose.setIcon(QIcon(":/images/close.png")) self.actionClose.setText(self.tr("&Close")) self.actionClose.setIconText(self.tr("Close")) self.actionClose.setToolTip(self.tr("Close")) self.actionChangeLayout = QAction(self) self.actionChangeLayout.setIcon(QIcon(":/images/change_layout.png")) self.actionChangeLayout.setText(self.tr("Change &Layout")) self.actionChangeLayout.setIconText(self.tr("Layout")) self.actionChangeLayout.setToolTip(self.tr("Change Layout")) self.actionChangeLayout.setCheckable(True) self.actionSelectWidgets = QAction(self) self.actionSelectWidgets.setIcon(QIcon(":/images/select_widgets.png")) self.actionSelectWidgets.setText(self.tr("&Select Widgets")) self.actionSelectWidgets.setIconText(self.tr("Widgets")) self.actionSelectWidgets.setToolTip(self.tr("Select Widgets")) self.actionResetBackground = QAction(self) self.actionResetBackground.setIcon(QIcon(":/images/reset.png")) self.actionResetBackground.setText(self.tr("&Reset Background")) self.actionResetBackground.setIconText(self.tr("Reset")) self.actionResetBackground.setToolTip(self.tr("Reset Background")) self.actionResetDefaultLayout = QAction(self) self.actionResetDefaultLayout.setIcon(QIcon(":/images/reset.png")) self.actionResetDefaultLayout.setText(self.tr("Reset &Layout")) self.actionResetDefaultLayout.setIconText(self.tr("Reset")) self.actionResetDefaultLayout.setToolTip(self.tr("Reset Layout")) def createControls(self): self.toolBarMain = QToolBar(self) self.toolBarMain.addAction(self.actionChangeBackground) self.toolBarMain.addAction(self.actionResetBackground) self.toolBarMain.addAction(self.actionChangeLayout) self.toolBarMain.addAction(self.actionClose) self.toolBarMain.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.toolBarMain.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toolBarLayout = QToolBar(self) self.toolBarLayout.addAction(self.actionSelectWidgets) self.toolBarLayout.addAction(self.actionResetDefaultLayout) self.toolBarLayout.addAction(self.actionChangeLayout) self.toolBarLayout.addAction(self.actionClose) self.toolBarLayout.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.toolBarLayout.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.canvas = Canvas(self) self.layoutEditor = LayoutEditor(self) self.lblTitle = QLabel(self) self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.setLayout(QVBoxLayout()) self.layoutTop = QHBoxLayout() self.layoutTop.addWidget(self.lblTitle) self.layoutTop.addWidget(self.toolBarMain) self.layoutTop.addWidget(self.toolBarLayout) self.toolBarLayout.hide() self.layout().addLayout(self.layoutTop) self.layout().addWidget(self.canvas) self.layout().addWidget(self.layoutEditor) self.layoutEditor.hide() def loadSettings(self): settings = self.platform.getSettings() filepath = settings.value("background", "background.png") if not os.path.exists(filepath): filepath = os.path.join(os.path.dirname(__file__), filepath) if not os.path.exists(filepath): return image = QImage(filepath) self._makeBackground(image) def makeConnections(self): self.actionClose.triggered.connect(self.close) self.actionChangeLayout.triggered.connect(self.toggleLayoutEditor) self.actionChangeBackground.triggered.connect(self.changeBackground) self.actionResetBackground.triggered.connect(self.useDefaultBackground) self.actionSelectWidgets.triggered.connect(self.selectWidgets) self.actionResetDefaultLayout.triggered.connect(self.resetDefaultLayout) QApplication.instance().focusChanged.connect(self.onWindowFocusChanged) def paintEvent(self, event): painter = QPainter(self) painter.drawImage(event.rect(), self._background_image, event.rect()) def keyPressEvent(self, event): QWidget.keyPressEvent(self, event) if not event.isAccepted() and event.key() == Qt.Key_Escape: if self.layoutEditor.isVisible(): self.leaveLayoutEditor() self.actionChangeLayout.setChecked(False) else: self.close() def onWindowFocusChanged(self, old, new): "实现类似于Qt.Popup那样点击其它窗口就立即关闭本窗口的效果。" if self.isVisible() and not self.isActiveWindow(): self.close() def showEvent(self, event): settings = self.platform.getSettings() key = settings.value("globalkey", "Alt+`") if key is not None: if os.name == "nt": #在Windows系统下,Meta键习惯叫Win键 key = key.replace("Meta", "Win") title = self.tr("提示:在任何位置按<b>{0}</b>打开快捷面板。").format(key) self.lblTitle.setText('<span style=" font-size:14pt;font-style:italic;">{0}</span>'.format(title)) else: title = self.tr("快捷面板") self.lblTitle.setText('<span style=" font-size:14pt;font-style:italic;">{0}</span>'.format(title)) #如果有时候运行全屏程序,快捷面板的位置就会发生改变 self._makeBackground(self._background_image) moveToCenter(self) self.canvas.positWidgets() QWidget.showEvent(self, event) def showAndGetFocus(self): if not self.isVisible(): self.show() if self.windowState() & Qt.WindowMinimized: self.setWindowState(self.windowState() ^ Qt.WindowMinimized) self.raise_() if os.name == "nt": ctypes.windll.user32.BringWindowToTop(int(self.winId())) ctypes.windll.user32.SwitchToThisWindow(int(self.winId()), 1) self.activateWindow() def toggle(self): if self.isVisible(): self.hide() else: self.showAndGetFocus() def addQuickAccessShortcut(self, name, icon, callback): """ 添加一个快捷方式。有一个widget专门用于显示Besteam内部各种工具的快捷方式。 name 快捷方式的名字 icon 快捷方式的图标 callback 当用户点击快捷方式的时候调用的回调函数。不会传入任何参数。 """ self.quickAccessModel.addShortcut(name, icon, callback) def removeQuickAccessShortcut(self, name): """删除一个系统快捷方式。参数name是快捷方式的名字。""" self.quickAccessModel.removeShortcut(name) def enterLayoutEditor(self): self.layoutEditor.show() self.canvas.hide() self.toolBarLayout.show() self.toolBarMain.hide() self.layoutEditor.beginEdit(self.widgets) def leaveLayoutEditor(self): self.layoutEditor.hide() self.canvas.show() self.toolBarLayout.hide() self.toolBarMain.show() changedWidgets = self.layoutEditor.saveLayout(self.widgets) for widget in changedWidgets: conf = {} conf["left"] = widget.rect.left() conf["top"] = widget.rect.top() conf["width"] = widget.rect.width() conf["height"] = widget.rect.height() conf["enabled"] = widget.enabled conf["id"] = widget.id self.db.saveWidgetConfig(conf) if widget.enabled: self._enableWidget(widget, False) else: self._disableWidget(widget, False) self.canvas.positWidgets(True) self.layoutEditor.endEdit() def toggleLayoutEditor(self, checked): if checked: self.enterLayoutEditor() else: self.leaveLayoutEditor() def changeBackground(self): filename, selectedFilter = QFileDialog.getOpenFileName(self, self.tr("Change Background"), \ QStandardPaths.writableLocation(QStandardPaths.PicturesLocation), \ self.tr("Image Files (*.png *.gif *.jpg *.jpeg *.bmp *.mng *ico)")) if not filename: return image = QImage(filename) if image.isNull(): QMessageBox.information(self, self.tr("Change Background"), \ self.tr("不能读取图像文件,请检查文件格式是否正确,或者图片是否已经损坏。")) return if image.width() < 800 or image.height() < 600: answer = QMessageBox.information(self, self.tr("Change Background"), \ self.tr("不建议设置小于800x600的图片作为背景图案。如果继续,可能会使快捷面板显示错乱。是否继续?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if answer == QMessageBox.No: return self._makeBackground(image) moveToCenter(self) self.canvas.positWidgets() self.update() with self.platform.getSettings() as settings: settings.setValue("background", filename) def useDefaultBackground(self): filename = "background.png" if not os.path.exists(filename): filename = os.path.join(os.path.dirname(__file__), filename) if os.path.exists(filename): image = QImage(filename) if not image.isNull(): self._makeBackground(image) moveToCenter(self) self.canvas.positWidgets() self.update() settings = self.platform.getSettings() settings.remove("background") def _makeBackground(self, image): desktopSize = QApplication.desktop().screenGeometry(self).size() if desktopSize.width() < image.width() or desktopSize.height() < image.height(): self._background_image = image.scaled(desktopSize, Qt.KeepAspectRatio, Qt.SmoothTransformation) else: self._background_image = image self.resize(self._background_image.size()) def runDialog(self, *args, **kwargs): """ 在QuickPanel中显示一个对话框。主要是为了避免对话框显示的时候,快捷面板会隐藏。 接受两种形式的参数,其中d是对话框: runDialog(d, d.exec_) runDialog(d.exec_, *args, **kwargs) 建议使用第二种 """ if isinstance(args[0], QDialog): return self._runDialog2(args[0], args[1]) else: callback, args = args[0], args[1:] return self._runDialog3(callback, args, kwargs) def _runDialog2(self, d, callback): return self._runDialog(d, self.canvas, callback) def _runDialog3(self, callback, args, kwargs): d = callback.__self__ f = functools.partial(callback, *args, **kwargs) return self._runDialog(d, self.canvas, f) def _runDialog(self, d, container, callback): shutter = ShutterWidget(container) newPaintEvent = functools.partial(self._dialog_paintEvent, d) oldPaintEvent = d.paintEvent d.paintEvent = newPaintEvent r = d.geometry() r.moveCenter(container.rect().center()) d.setGeometry(r) d.setWindowFlags(Qt.Widget) d.setParent(container) d.setFocus(Qt.OtherFocusReason) try: shutter.show() d.raise_() return callback() finally: d.paintEvent = oldPaintEvent shutter.close() shutter.setParent(None) def _dialog_paintEvent(self, d, event): QDialog.paintEvent(d, event) pen = QPen() pen.setWidth(2) pen.setColor(QColor(200, 200, 200)) rect = d.rect() rect = rect.adjusted(0, 0, -1, -1) painter = QPainter(d) painter.setRenderHint(QPainter.Antialiasing, True) painter.setPen(pen) painter.setOpacity(0.8) painter.setBrush(QBrush(QColor(Qt.white))) painter.drawRoundedRect(rect, 15, 15)
class MainWindow(QMainWindow): """ """ def __init__(self): super(MainWindow, self).__init__() self.menu_bar = QMenuBar() self.media_menu = QMenu("媒体(M)", self.menu_bar) self.local_action = QAction("本地文件(L)", self.media_menu) self.local_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_L)) self.local_action.triggered.connect(self.show_local_widget) self.live_search_action = QAction("直播搜索(F)", self.media_menu) self.live_search_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F)) self.live_search_action.triggered.connect(self.show_search_widget) self.tv_live_search_action = QAction("高清电视(T)", self.media_menu) self.tv_live_search_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_T)) self.tv_live_search_action.triggered.connect(self.show_tv_widget) self.radio_station_search_action = QAction("广播电台(R)", self.media_menu) self.radio_station_search_action.setShortcut( QKeySequence(Qt.CTRL + Qt.Key_R)) self.radio_station_search_action.triggered.connect( self.show_radio_station_widget) self.close_action = QAction("关闭(C)", self.media_menu) self.close_action.setShortcut( QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C)) self.close_action.triggered.connect(self.answer_close_action_triggered) self.quit_action = QAction("退出(Q)", self.media_menu) self.quit_action.setShortcut( QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Q)) self.quit_action.triggered.connect(lambda: sys.exit()) self.tool_menu = QMenu("工具(T)", self.menu_bar) self.screenshot_action = QAction("截图(J)", self.tool_menu) self.screenshot_action.setShortcut(QKeySequence(Qt.SHIFT + Qt.Key_J)) self.screenshot_action.triggered.connect( self.answer_screenshot_action_triggered) self.gif_action = QAction("动图(G)", self.tool_menu) self.gif_action.setShortcut(QKeySequence(Qt.SHIFT + Qt.Key_G)) self.gif_action.triggered.connect(self.answer_gif_action_triggered) self.screen_record_action = QAction("录屏(L)", self.tool_menu) self.screen_record_action.setShortcut(QKeySequence(Qt.SHIFT + Qt.Key_L)) self.screen_record_action.triggered.connect( self.answer_screen_record_action_triggered) self.enhance_menu = QMenu("增强(E)", self.menu_bar) self.hot_live_action = QAction("热门直播(H)", self.enhance_menu) self.hot_live_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_H)) self.attention_action = QAction("我的关注(A)", self.enhance_menu) self.attention_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_A)) self.nlp_action = QAction("智能字幕(N)", self.enhance_menu) self.nlp_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_N)) self.note_action = QAction("边看边记(W)", self.enhance_menu) self.note_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_W)) self.preferences_action = QAction("偏好设置(P)", self.enhance_menu) self.preferences_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_P)) self.preferences_action.triggered.connect(self.show_preferences_widget) self.my_menu = QMenu("我的(P)", self.menu_bar) self.connect_github_action = QAction("连接到GitHub(C)", self.my_menu) self.connect_github_action.setShortcut( QKeySequence(Qt.ALT + Qt.SHIFT + Qt.Key_C)) self.backup_data_action = QAction("备份数据(B)", self.my_menu) self.backup_data_action.setShortcut( QKeySequence(Qt.ALT + Qt.SHIFT + Qt.Key_B)) self.restore_from_backup_action = QAction("从备份中恢复(R)", self.my_menu) self.restore_from_backup_action.setShortcut( QKeySequence(Qt.ALT + Qt.SHIFT + Qt.Key_R)) self.help_menu = QMenu("帮助(H)", self.menu_bar) self.help_action = QAction("帮助文档(H)", self.help_menu) self.help_action.setShortcut(QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_H)) self.help_action.triggered.connect(self.answer_help_action_triggered) self.change_log_action = QAction("更新日志(U)", self.help_menu) self.change_log_action.setShortcut( QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_U)) self.change_log_action.triggered.connect( self.answer_change_log_action_triggered) self.check_version_action = QAction("检查版本(C)", self.help_menu) self.check_version_action.setShortcut( QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_C)) self.check_version_action.triggered.connect( self.answer_check_version_action_triggered) self.about_action = QAction("关于软件(A)", self.help_menu) self.about_action.setShortcut(QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_A)) self.about_action.triggered.connect(self.answer_about_action_triggered) self.tool_bar = QToolBar() self.tool_bar.setFloatable(False) self.tool_bar.setMovable(False) self.tool_bar.setIconSize(QSize(40, 40)) # self.tool_bar.allowedAreasChanged.connect(set_tool_bar_pos) # self.tool_bar.setStyleSheet("QToolBar{border: 1px solid #313335; spacing:5px; }") self.live_tool_action = QAction("", self.tool_bar) self.live_tool_action.setToolTip("直播搜索") self.live_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.search_widget = SearchWidget() self.live_tool_action.triggered.connect(self.show_search_widget) self.search_widget.watch_live_signal.connect( self.answer_play_action_triggered) self.tv_live_tool_action = QAction("", self.tool_bar) self.tv_live_tool_action.setToolTip("高清电视") self.tv_live_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.tv_widget = TvWidget() self.tv_live_tool_action.triggered.connect(self.show_tv_widget) self.tv_widget.watch_tv_signal.connect( self.answer_play_action_triggered) self.radio_station_tool_action = QAction("", self.tool_bar) self.radio_station_tool_action.setToolTip("广播电台") self.radio_station_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.radio_station_widget = RadioStationWidget() self.radio_station_tool_action.triggered.connect( self.show_radio_station_widget) self.radio_station_widget.listen_radio_station_signal.connect( self.answer_play_action_triggered) self.hot_live_tool_action = QAction("", self.tool_bar) self.hot_live_tool_action.setToolTip("热门直播") self.hot_live_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.attention_tool_action = QAction("", self.tool_bar) self.attention_tool_action.setToolTip("我的关注") self.attention_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.nlp_tool_action = QAction("", self.tool_bar) self.nlp_tool_action.setToolTip("智能字幕") self.nlp_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.note_tool_action = QAction("", self.tool_bar) self.note_tool_action.setToolTip("边看边记") self.note_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.preferences_tool_action = QAction("", self.tool_bar) self.preferences_tool_action.setToolTip("偏好设置") self.preferences_tool_action.setIcon( QIcon(PathHelper.get_img_path("*****@*****.**"))) self.preferences_tool_action.triggered.connect( self.show_preferences_widget) self.vlc_widget = VlcPlayerWidget() self.vlc_widget.showFullScreen_signal.connect( self.on_showFullScreen_signal) self.vlc_widget.showNormal_signal.connect(self.on_showNormal_signal) self._init_ui() self.init_cfg() def _init_ui(self): """ :return: """ # 菜单栏 self.media_menu.addAction(self.local_action) self.media_menu.addAction(self.live_search_action) self.media_menu.addAction(self.tv_live_search_action) self.media_menu.addAction(self.radio_station_search_action) self.media_menu.addSeparator() self.media_menu.addAction(self.close_action) self.media_menu.addAction(self.quit_action) self.tool_menu.addAction(self.screenshot_action) self.tool_menu.addAction(self.gif_action) self.tool_menu.addAction(self.screen_record_action) self.enhance_menu.addAction(self.hot_live_action) self.enhance_menu.addAction(self.attention_action) self.enhance_menu.addSeparator() self.enhance_menu.addAction(self.nlp_action) self.enhance_menu.addAction(self.note_action) self.enhance_menu.addSeparator() self.enhance_menu.addAction(self.preferences_action) self.my_menu.addAction(self.connect_github_action) self.my_menu.addSeparator() self.my_menu.addAction(self.backup_data_action) self.my_menu.addAction(self.restore_from_backup_action) self.help_menu.addAction(self.help_action) self.help_menu.addAction(self.change_log_action) self.help_menu.addAction(self.check_version_action) self.help_menu.addSeparator() self.help_menu.addAction(self.about_action) self.menu_bar.addMenu(self.media_menu) self.menu_bar.addMenu(self.enhance_menu) self.menu_bar.addMenu(self.tool_menu) self.menu_bar.addMenu(self.my_menu) self.menu_bar.addMenu(self.help_menu) self.setMenuBar(self.menu_bar) # 工具栏 self.tool_bar.addAction(self.live_tool_action) self.tool_bar.addAction(self.tv_live_tool_action) self.tool_bar.addAction(self.radio_station_tool_action) self.tool_bar.addAction(self.hot_live_tool_action) self.tool_bar.addAction(self.attention_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.nlp_tool_action) self.tool_bar.addAction(self.note_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.preferences_tool_action) # 显示区域 self.setCentralWidget(self.vlc_widget) self.setContextMenuPolicy(Qt.NoContextMenu) self.set_window_info() def init_cfg(self): """ :return: """ # 工具栏设置 visible = get_tool_bar_visible() if visible: self.tool_bar.show() else: self.tool_bar.hide() pos = get_tool_bar_pos() if pos == CommonEnum.ToolBarPosTop: self.addToolBar(Qt.TopToolBarArea, self.tool_bar) elif pos == CommonEnum.ToolBarPosRight: self.addToolBar(Qt.RightToolBarArea, self.tool_bar) elif pos == CommonEnum.ToolBarPosBottom: self.addToolBar(Qt.BottomToolBarArea, self.tool_bar) elif pos == CommonEnum.ToolBarPosLeft: self.addToolBar(Qt.LeftToolBarArea, self.tool_bar) else: self.addToolBar(Qt.TopToolBarArea, self.tool_bar) # 皮肤设置 skin = get_skin() set_skin(skin) # 语言设置 language = get_language() set_language(language) # 字体设置 font_dict = get_font() font = QFont() font.setFamily(font_dict["font_family"]) font.setStyleName(font_dict["font_style"]) font.setPointSize(font_dict["font_size"]) qApp.setFont(font) qApp.processEvents() def set_window_info(self): """ :return: """ desktop_widget = QDesktopWidget() screen_rect = desktop_widget.screenGeometry() self.setGeometry(screen_rect) _app_info = get_app_info() title = _app_info["name"] + " " + _app_info["version"] self.setWindowTitle(title) self.setWindowIcon(QIcon(PathHelper.get_img_path("*****@*****.**"))) self.showMaximized() def show_local_widget(self): """ :return: """ file_name, file_type = QFileDialog.getOpenFileName( self, "选择音视频文件", ".", "All Files (*)") if file_name: _type = os.path.splitext(file_name)[-1][1:] _type_list = [ "3g2", "3gp", "3gp2", "3gpp", "amv", "asf", "avi", "bik", "bin", "divx", "drc", "dv", "f4v", "flv", "gvi", "gxf", "iso", "m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov", "mp2", "mp4", "mp4v", "mpe", "mpeg", "mpeg1", "mpeg2", "mpeg4", "mpg", "mpv2", "mts", "mxf", "mxg", "nsv", "nuv", "ogg", "ogm", "ogv", "ps", "rec", "rm", "rmvb", "rpl", "thp", "tod", "ts", "tts", "txd", "vob", "vro", "webm", "wm", "wmv", "wtv", "xesc", "3ga", "669", "a52", "acc", "ac3", "adt", "adts", "aif", "aiff", "amr", "aob", "ape", "awb", "caf", "dts", "flac", "it", "kar", "m4a", "m4b", "m4p", "m5p", "mid", "mka", "mlp", "mod", "mpa", "mp1", "mp2", "mp3", "mpc", "mpga", "mus", "oga", "ogg", "oma", "opus", "qcp", "ra", "rmi", "s3m", "sid", "spx", "thd", "tta", "voc", "vqf", "w64", "wav", "wma", "wv", "xa", "xm" ] if _type in _type_list: self.answer_play_action_triggered( file_name, PlayerEnum.MrlTypeLocal.value[1]) else: _box = PromptBox(2, "音视频文件错误!", 1) width, height = get_window_center_point(_box) _box.move(width, height) _box.exec_() def show_search_widget(self): """ :return: """ width, height = get_window_center_point(self.search_widget) self.search_widget.move(width, height) self.search_widget.exec_() def show_tv_widget(self): """ :return: """ width, height = get_window_center_point(self.tv_widget) self.tv_widget.move(width, height) self.tv_widget.exec_() def show_radio_station_widget(self): """ :return: """ width, height = get_window_center_point(self.radio_station_widget) self.radio_station_widget.move(width, height) self.radio_station_widget.exec_() @staticmethod def show_preferences_widget(): """ :return: """ preferences_widget = PreferencesWidget() width, height = get_window_center_point(preferences_widget) preferences_widget.move(width, height) preferences_widget.exec_() @staticmethod def answer_help_action_triggered(): """ :return: """ desktop_services = QDesktopServices() _app_info = get_app_info() desktop_services.openUrl(QUrl(_app_info["help_url"])) @staticmethod def answer_change_log_action_triggered(): """ :return: """ desktop_services = QDesktopServices() _app_info = get_app_info() desktop_services.openUrl(QUrl(_app_info["change_log_url"])) @staticmethod def answer_check_version_action_triggered(): """ :return: """ # TODO: 获取 GitHub API 进行检查并弹窗 desktop_services = QDesktopServices() _app_info = get_app_info() desktop_services.openUrl(QUrl(_app_info["update"])) @staticmethod def answer_about_action_triggered(): """ :return: """ about_widget = AboutWidget() width, height = get_window_center_point(about_widget) about_widget.move(width, height) about_widget.exec_() def answer_play_action_triggered(self, url: str, url_type: PlayerEnum): """ :param url: :param url_type: :return: """ # if url_type == PlayerEnum.MrlTypeRS.value[1]: # self.vlc_widget = VlcPlayerWidget("--audio-visual=visual", "--effect-list=spectrometer", # "--effect-fft-window=flattop") # else: # self.vlc_widget = VlcPlayerWidget() # loading_widget = LoadingWidget() # width, height = get_window_center_point(loading_widget) # loading_widget.move(width, height) # loading_widget.show() self.vlc_widget.vlc_play(url, url_type) # while PlayerState.Load == PlayerEnum.LoadPlaying: # loading_widget.close() def answer_close_action_triggered(self): """ :return: """ self.vlc_widget.vlc_stop() @staticmethod def answer_screenshot_action_triggered(): """ :return: """ pass def answer_gif_action_triggered(self): """ :return: """ pass def answer_screen_record_action_triggered(self): """ :return: """ pass def closeEvent(self, event) -> None: """ :param event: :return: """ self.vlc_widget.vlc_release() def on_showFullScreen_signal(self): """ 媒体播放器全屏显示 :return: """ self.showFullScreen() self.menu_bar.hide() self.tool_bar.hide() def on_showNormal_signal(self): """ 媒体播放器恢复默认 :return: """ self.menu_bar.show() self.tool_bar.show() self.showNormal() self.showMaximized()
class ManageWindow(QWidget): __BASE_HEIGHT__ = 400 signal_user_res = pyqtSignal(bool) signal_table_update = pyqtSignal() def __init__(self, i18n: dict, icon_cache: MemoryCache, manager: SoftwareManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, display_limit: int, config: Configuration, context: ApplicationContext, notifications: bool, tray_icon=None): super(ManageWindow, self).__init__() self.i18n = i18n self.manager = manager self.tray_icon = tray_icon self.working = False # restrict the number of threaded actions self.pkgs = [] # packages current loaded in the table self.pkgs_available = [] # all packages loaded in memory self.pkgs_installed = [] # cached installed packages self.display_limit = display_limit self.icon_cache = icon_cache self.disk_cache = disk_cache self.download_icons = download_icons self.screen_size = screen_size self.config = config self.context = context self.notifications = notifications self.icon_app = QIcon(resource.get_path('img/logo.svg')) self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__) self.setWindowIcon(self.icon_app) self.layout = QVBoxLayout() self.setLayout(self.layout) self.toolbar_top = QToolBar() self.toolbar_top.addWidget(new_spacer()) self.label_status = QLabel() self.label_status.setText('') self.label_status.setStyleSheet("font-weight: bold") self.toolbar_top.addWidget(self.label_status) self.toolbar_search = QToolBar() self.toolbar_search.setStyleSheet("spacing: 0px;") self.toolbar_search.setContentsMargins(0, 0, 0, 0) label_pre_search = QLabel() label_pre_search.setStyleSheet( "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;" ) self.toolbar_search.addWidget(label_pre_search) self.input_search = QLineEdit() self.input_search.setMaxLength(20) self.input_search.setFrame(False) self.input_search.setPlaceholderText( self.i18n['window_manage.input_search.placeholder'] + "...") self.input_search.setToolTip( self.i18n['window_manage.input_search.tooltip']) self.input_search.setStyleSheet( "QLineEdit { background-color: white; color: gray; spacing: 0; height: 30px; font-size: 12px; width: 300px}" ) self.input_search.returnPressed.connect(self.search) self.toolbar_search.addWidget(self.input_search) label_pos_search = QLabel() label_pos_search.setPixmap(QPixmap( resource.get_path('img/search.svg'))) label_pos_search.setStyleSheet( "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;" ) self.toolbar_search.addWidget(label_pos_search) self.ref_toolbar_search = self.toolbar_top.addWidget( self.toolbar_search) self.toolbar_top.addWidget(new_spacer()) self.layout.addWidget(self.toolbar_top) self.toolbar = QToolBar() self.toolbar.setStyleSheet( 'QToolBar {spacing: 4px; margin-top: 15px; margin-bottom: 5px}') self.checkbox_updates = QCheckBox() self.checkbox_updates.setText(self.i18n['updates'].capitalize()) self.checkbox_updates.stateChanged.connect(self._handle_updates_filter) self.ref_checkbox_updates = self.toolbar.addWidget( self.checkbox_updates) self.checkbox_only_apps = QCheckBox() self.checkbox_only_apps.setText( self.i18n['manage_window.checkbox.only_apps']) self.checkbox_only_apps.setChecked(True) self.checkbox_only_apps.stateChanged.connect( self._handle_filter_only_apps) self.ref_checkbox_only_apps = self.toolbar.addWidget( self.checkbox_only_apps) self.any_type_filter = 'any' self.cache_type_filter_icons = {} self.combo_filter_type = QComboBox() self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px}') self.combo_filter_type.setEditable(True) self.combo_filter_type.lineEdit().setReadOnly(True) self.combo_filter_type.lineEdit().setAlignment(Qt.AlignCenter) self.combo_filter_type.activated.connect(self._handle_type_filter) self.combo_filter_type.addItem( load_icon(resource.get_path('img/logo.svg'), 14), self.i18n[self.any_type_filter].capitalize(), self.any_type_filter) self.ref_combo_filter_type = self.toolbar.addWidget( self.combo_filter_type) self.input_name_filter = InputFilter(self.apply_filters_async) self.input_name_filter.setMaxLength(10) self.input_name_filter.setPlaceholderText( self.i18n['manage_window.name_filter.placeholder'] + '...') self.input_name_filter.setToolTip( self.i18n['manage_window.name_filter.tooltip']) self.input_name_filter.setStyleSheet( "QLineEdit { background-color: white; color: gray;}") self.input_name_filter.setFixedWidth(130) self.ref_input_name_filter = self.toolbar.addWidget( self.input_name_filter) self.toolbar.addWidget(new_spacer()) self.bt_installed = QPushButton() self.bt_installed.setToolTip( self.i18n['manage_window.bt.installed.tooltip']) self.bt_installed.setIcon(QIcon(resource.get_path('img/disk.png'))) self.bt_installed.setText( self.i18n['manage_window.bt.installed.text'].capitalize()) self.bt_installed.clicked.connect(self._show_installed) self.bt_installed.setStyleSheet(toolbar_button_style('#A94E0A')) self.ref_bt_installed = self.toolbar.addWidget(self.bt_installed) self.bt_refresh = QPushButton() self.bt_refresh.setToolTip(i18n['manage_window.bt.refresh.tooltip']) self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg'))) self.bt_refresh.setText(self.i18n['manage_window.bt.refresh.text']) self.bt_refresh.setStyleSheet(toolbar_button_style('#2368AD')) self.bt_refresh.clicked.connect( lambda: self.refresh_apps(keep_console=False)) self.ref_bt_refresh = self.toolbar.addWidget(self.bt_refresh) self.bt_upgrade = QPushButton() self.bt_upgrade.setToolTip(i18n['manage_window.bt.upgrade.tooltip']) self.bt_upgrade.setIcon(QIcon(resource.get_path('img/app_update.svg'))) self.bt_upgrade.setText(i18n['manage_window.bt.upgrade.text']) self.bt_upgrade.setStyleSheet(toolbar_button_style('#20A435')) self.bt_upgrade.clicked.connect(self.update_selected) self.ref_bt_upgrade = self.toolbar.addWidget(self.bt_upgrade) self.layout.addWidget(self.toolbar) self.table_apps = AppsTable(self, self.icon_cache, disk_cache=self.disk_cache, download_icons=self.download_icons) self.table_apps.change_headers_policy() self.layout.addWidget(self.table_apps) toolbar_console = QToolBar() self.checkbox_console = QCheckBox() self.checkbox_console.setText( self.i18n['manage_window.checkbox.show_details']) self.checkbox_console.stateChanged.connect(self._handle_console) self.checkbox_console.setVisible(False) self.ref_checkbox_console = toolbar_console.addWidget( self.checkbox_console) toolbar_console.addWidget(new_spacer()) self.label_displayed = QLabel() toolbar_console.addWidget(self.label_displayed) self.layout.addWidget(toolbar_console) self.textarea_output = QPlainTextEdit(self) self.textarea_output.resize(self.table_apps.size()) self.textarea_output.setStyleSheet("background: black; color: white;") self.layout.addWidget(self.textarea_output) self.textarea_output.setVisible(False) self.textarea_output.setReadOnly(True) self.toolbar_substatus = QToolBar() self.toolbar_substatus.addWidget(new_spacer()) self.label_substatus = QLabel() self.toolbar_substatus.addWidget(self.label_substatus) self.toolbar_substatus.addWidget(new_spacer()) self.layout.addWidget(self.toolbar_substatus) self._change_label_substatus('') self.thread_update = self._bind_async_action( UpdateSelectedApps(self.manager, self.i18n), finished_call=self._finish_update_selected) self.thread_refresh = self._bind_async_action( RefreshApps(self.manager), finished_call=self._finish_refresh_apps, only_finished=True) self.thread_uninstall = self._bind_async_action( UninstallApp(self.manager, self.icon_cache), finished_call=self._finish_uninstall) self.thread_get_info = self._bind_async_action( GetAppInfo(self.manager), finished_call=self._finish_get_info) self.thread_get_history = self._bind_async_action( GetAppHistory(self.manager, self.i18n), finished_call=self._finish_get_history) self.thread_search = self._bind_async_action( SearchPackages(self.manager), finished_call=self._finish_search, only_finished=True) self.thread_downgrade = self._bind_async_action( DowngradeApp(self.manager, self.i18n), finished_call=self._finish_downgrade) self.thread_suggestions = self._bind_async_action( FindSuggestions(man=self.manager), finished_call=self._finish_search, only_finished=True) self.thread_run_app = self._bind_async_action( LaunchApp(self.manager), finished_call=self._finish_run_app, only_finished=False) self.thread_custom_action = self._bind_async_action( CustomAction(manager=self.manager), finished_call=self._finish_custom_action) self.thread_apply_filters = ApplyFilters() self.thread_apply_filters.signal_finished.connect( self._finish_apply_filters_async) self.thread_apply_filters.signal_table.connect( self._update_table_and_upgrades) self.signal_table_update.connect( self.thread_apply_filters.stop_waiting) self.thread_install = InstallPackage(manager=self.manager, disk_cache=self.disk_cache, icon_cache=self.icon_cache, locale_keys=self.i18n) self._bind_async_action(self.thread_install, finished_call=self._finish_install) self.thread_animate_progress = AnimateProgress() self.thread_animate_progress.signal_change.connect( self._update_progress) self.thread_verify_models = VerifyModels() self.thread_verify_models.signal_updates.connect( self._notify_model_data_change) self.toolbar_bottom = QToolBar() self.toolbar_bottom.setIconSize(QSize(16, 16)) self.toolbar_bottom.setStyleSheet('QToolBar { spacing: 3px }') self.toolbar_bottom.addWidget(new_spacer()) self.progress_bar = QProgressBar() self.progress_bar.setMaximumHeight(10 if QApplication.instance().style( ).objectName().lower() == 'windows' else 4) self.progress_bar.setTextVisible(False) self.ref_progress_bar = self.toolbar_bottom.addWidget( self.progress_bar) self.toolbar_bottom.addWidget(new_spacer()) self.combo_styles = StylesComboBox( parent=self, i18n=i18n, show_panel_after_restart=bool(tray_icon)) self.combo_styles.setStyleSheet('QComboBox {font-size: 12px;}') self.ref_combo_styles = self.toolbar_bottom.addWidget( self.combo_styles) bt_settings = IconButton( icon_path=resource.get_path('img/app_settings.svg'), action=self._show_settings_menu, background='#12ABAB', tooltip=self.i18n['manage_window.bt_settings.tooltip']) self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings) self.layout.addWidget(self.toolbar_bottom) qt_utils.centralize(self) self.filter_only_apps = True self.type_filter = self.any_type_filter self.filter_updates = False self._maximized = False self.progress_controll_enabled = True self.recent_installation = False self.dialog_about = None self.first_refresh = suggestions self.thread_warnings = ListWarnings(man=manager, locale_keys=i18n) self.thread_warnings.signal_warnings.connect(self._show_warnings) def set_tray_icon(self, tray_icon): self.tray_icon = tray_icon self.combo_styles.show_panel_after_restart = bool(tray_icon) def _update_process_progress(self, val: int): if self.progress_controll_enabled: self.thread_animate_progress.set_progress(val) def apply_filters_async(self): self.label_status.setText(self.i18n['manage_window.status.filtering'] + '...') self.ref_toolbar_search.setVisible(False) if self.ref_input_name_filter.isVisible(): self.input_name_filter.setReadOnly(True) self.thread_apply_filters.filters = self._gen_filters() self.thread_apply_filters.pkgs = self.pkgs_available self.thread_apply_filters.start() def _update_table_and_upgrades(self, pkgs_info: dict): self._update_table(pkgs_info=pkgs_info, signal=True) self.update_bt_upgrade(pkgs_info) def _finish_apply_filters_async(self, success: bool): self.label_status.setText('') self.ref_toolbar_search.setVisible(True) if self.ref_input_name_filter.isVisible(): self.input_name_filter.setReadOnly(False) def _bind_async_action(self, action: AsyncAction, finished_call, only_finished: bool = False) -> AsyncAction: action.signal_finished.connect(finished_call) if not only_finished: action.signal_confirmation.connect(self._ask_confirmation) action.signal_output.connect(self._update_action_output) action.signal_message.connect(self._show_message) action.signal_status.connect(self._change_label_status) action.signal_substatus.connect(self._change_label_substatus) action.signal_progress.connect(self._update_process_progress) self.signal_user_res.connect(action.confirm) return action def _ask_confirmation(self, msg: dict): self.thread_animate_progress.pause() diag = ConfirmationDialog(title=msg['title'], body=msg['body'], locale_keys=self.i18n, components=msg['components'], confirmation_label=msg['confirmation_label'], deny_label=msg['deny_label']) res = diag.is_confirmed() self.thread_animate_progress.animate() self.signal_user_res.emit(res) def _show_message(self, msg: dict): self.thread_animate_progress.pause() dialog.show_message(title=msg['title'], body=msg['body'], type_=msg['type']) self.thread_animate_progress.animate() def _show_warnings(self, warnings: List[str]): if warnings: dialog.show_message(title=self.i18n['warning'].capitalize(), body='<p>{}</p>'.format( '<br/><br/>'.join(warnings)), type_=MessageType.WARNING) def show(self): super(ManageWindow, self).show() if not self.thread_warnings.isFinished(): self.thread_warnings.start() def verify_warnings(self): self.thread_warnings.start() def _show_installed(self): if self.pkgs_installed: self.finish_action() self.ref_bt_upgrade.setVisible(True) self.ref_checkbox_only_apps.setVisible(True) self.input_search.setText('') self.input_name_filter.setText('') self.update_pkgs(new_pkgs=None, as_installed=True) def _show_about(self): if self.dialog_about is None: self.dialog_about = AboutDialog(self.i18n) self.dialog_about.show() def _handle_updates_filter(self, status: int): self.filter_updates = status == 2 self.apply_filters_async() def _handle_filter_only_apps(self, status: int): self.filter_only_apps = status == 2 self.apply_filters_async() def _handle_type_filter(self, idx: int): self.type_filter = self.combo_filter_type.itemData(idx) self.apply_filters_async() def _notify_model_data_change(self): self.table_apps.fill_async_data() def changeEvent(self, e: QEvent): if isinstance(e, QWindowStateChangeEvent): self._maximized = self.isMaximized() policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents self.table_apps.change_headers_policy(policy) def closeEvent(self, event): if self.tray_icon: event.ignore() self.hide() self._handle_console_option(False) def _handle_console(self, checked: bool): if checked: self.textarea_output.show() else: self.textarea_output.hide() def _handle_console_option(self, enable: bool): if enable: self.textarea_output.clear() self.ref_checkbox_console.setVisible(enable) self.checkbox_console.setChecked(False) self.textarea_output.hide() def refresh_apps(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None): self.recent_installation = False self.type_filter = None self.input_search.clear() if not keep_console: self._handle_console_option(False) self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False, clear_filters=True) self.thread_refresh.app = top_app # the app will be on top when refresh happens self.thread_refresh.pkg_types = pkg_types self.thread_refresh.start() def _finish_refresh_apps(self, res: dict, as_installed: bool = True): self.finish_action() self.ref_checkbox_only_apps.setVisible(bool(res['installed'])) self.ref_bt_upgrade.setVisible(True) self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types']) self.first_refresh = False def uninstall_app(self, app: PackageView): pwd = None requires_root = self.manager.requires_root('uninstall', app.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.uninstalling'], app.model.name)) self.thread_uninstall.app = app self.thread_uninstall.root_password = pwd self.thread_uninstall.start() def run_app(self, app: PackageView): self._begin_action( self.i18n['manage_window.status.running_app'].format( app.model.name)) self.thread_run_app.app = app self.thread_run_app.start() def _finish_uninstall(self, pkgv: PackageView): self.finish_action() if pkgv: if self._can_notify_user(): util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled'])) self.refresh_apps(pkg_types={pkgv.model.__class__}) else: if self._can_notify_user(): util.notify_user('{}: {}'.format( pkgv.model.name, self.i18n['notification.uninstall.failed'])) self.checkbox_console.setChecked(True) def _can_notify_user(self): return self.notifications and (self.isHidden() or self.isMinimized()) def _finish_downgrade(self, res: dict): self.finish_action() if res['success']: if self._can_notify_user(): util.notify_user('{} {}'.format(res['app'], self.i18n['downgraded'])) self.refresh_apps(pkg_types={res['app'].model.__class__}) if self.tray_icon: self.tray_icon.verify_updates(notify_user=False) else: if self._can_notify_user(): util.notify_user(self.i18n['notification.downgrade.failed']) self.checkbox_console.setChecked(True) def _change_label_status(self, status: str): self.label_status.setText(status) def _change_label_substatus(self, substatus: str): self.label_substatus.setText('<p>{}</p>'.format(substatus)) if not substatus: self.toolbar_substatus.hide() elif not self.toolbar_substatus.isVisible(): self.toolbar_substatus.show() def _update_table(self, pkgs_info: dict, signal: bool = False): self.pkgs = pkgs_info['pkgs_displayed'] self.table_apps.update_pkgs( self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0) if not self._maximized: self.table_apps.change_headers_policy(QHeaderView.Stretch) self.table_apps.change_headers_policy() self.resize_and_center(accept_lower_width=len(self.pkgs) > 0) self.label_displayed.setText('{} / {}'.format( len(self.pkgs), len(self.pkgs_available))) else: self.label_displayed.setText('') if signal: self.signal_table_update.emit() def update_bt_upgrade(self, pkgs_info: dict = None): show_bt_upgrade = False if not pkgs_info or pkgs_info['not_installed'] == 0: for app_v in (pkgs_info['pkgs_displayed'] if pkgs_info else self.pkgs): if app_v.update_checked: show_bt_upgrade = True break self.ref_bt_upgrade.setVisible(show_bt_upgrade) def change_update_state(self, pkgs_info: dict, trigger_filters: bool = True): self.update_bt_upgrade(pkgs_info) if pkgs_info['updates'] > 0: if pkgs_info['not_installed'] == 0: if not self.ref_checkbox_updates.isVisible(): self.ref_checkbox_updates.setVisible(True) if not self.filter_updates: self._change_checkbox(self.checkbox_updates, True, 'filter_updates', trigger_filters) if pkgs_info['napp_updates'] > 0 and self.filter_only_apps: self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger_filters) else: self._change_checkbox(self.checkbox_updates, False, 'filter_updates', trigger_filters) self.ref_checkbox_updates.setVisible(False) def _change_checkbox(self, checkbox: QCheckBox, checked: bool, attr: str = None, trigger: bool = True): if not trigger: checkbox.blockSignals(True) checkbox.setChecked(checked) if not trigger: setattr(self, attr, checked) checkbox.blockSignals(False) def _gen_filters(self, updates: int = 0, ignore_updates: bool = False) -> dict: return { 'only_apps': self.filter_only_apps, 'type': self.type_filter, 'updates': False if ignore_updates else self.filter_updates, 'name': self.input_name_filter.get_text().lower() if self.input_name_filter.get_text() else None, 'display_limit': self.display_limit if updates <= 0 else None } def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False): self.input_name_filter.setText('') pkgs_info = commons.new_pkgs_info() filters = self._gen_filters(ignore_updates) if new_pkgs is not None: old_installed = None if as_installed: old_installed = self.pkgs_installed self.pkgs_installed = [] for pkg in new_pkgs: app_model = PackageView(model=pkg) commons.update_info(app_model, pkgs_info) commons.apply_filters(app_model, filters, pkgs_info) if old_installed and types: for pkgv in old_installed: if not pkgv.model.__class__ in types: commons.update_info(pkgv, pkgs_info) commons.apply_filters(pkgv, filters, pkgs_info) else: # use installed for pkgv in self.pkgs_installed: commons.update_info(pkgv, pkgs_info) commons.apply_filters(pkgv, filters, pkgs_info) if pkgs_info['apps_count'] == 0: if self.first_refresh: self._begin_search('') self.thread_suggestions.start() return else: self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger=False) self.checkbox_only_apps.setCheckable(False) else: self.checkbox_only_apps.setCheckable(True) self._change_checkbox(self.checkbox_only_apps, True, 'filter_only_apps', trigger=False) self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False) self._apply_filters(pkgs_info, ignore_updates=ignore_updates) self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False) self.pkgs_available = pkgs_info['pkgs'] if as_installed: self.pkgs_installed = pkgs_info['pkgs'] self.pkgs = pkgs_info['pkgs_displayed'] if self.pkgs: self.ref_input_name_filter.setVisible(True) self._update_type_filters(pkgs_info['available_types']) self._update_table(pkgs_info=pkgs_info) if new_pkgs: self.thread_verify_models.apps = self.pkgs self.thread_verify_models.start() if self.pkgs_installed: self.ref_bt_installed.setVisible(not as_installed and not self.recent_installation) self.resize_and_center(accept_lower_width=self.pkgs_installed) def _apply_filters(self, pkgs_info: dict, ignore_updates: bool): pkgs_info['pkgs_displayed'] = [] filters = self._gen_filters(updates=pkgs_info['updates'], ignore_updates=ignore_updates) for pkgv in pkgs_info['pkgs']: commons.apply_filters(pkgv, filters, pkgs_info) def _update_type_filters(self, available_types: dict = None): if available_types is None: self.ref_combo_filter_type.setVisible( self.combo_filter_type.count() > 1) else: self.type_filter = self.any_type_filter if available_types and len(available_types) > 1: if self.combo_filter_type.count() > 1: for _ in range(self.combo_filter_type.count() - 1): self.combo_filter_type.removeItem(1) for app_type, icon_path in available_types.items(): icon = self.cache_type_filter_icons.get(app_type) if not icon: icon = load_icon(icon_path, 14) self.cache_type_filter_icons[app_type] = icon self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type) self.ref_combo_filter_type.setVisible(True) else: self.ref_combo_filter_type.setVisible(False) def resize_and_center(self, accept_lower_width: bool = True): if self.pkgs: new_width = reduce(operator.add, [ self.table_apps.columnWidth(i) for i in range(self.table_apps.columnCount()) ]) if self.ref_bt_upgrade.isVisible( ) or self.ref_bt_settings.isVisible(): new_width *= 1.07 else: new_width = self.toolbar_top.width() if accept_lower_width or new_width > self.width(): self.resize(new_width, self.height()) if self.ref_bt_upgrade.isVisible( ) and self.bt_upgrade.visibleRegion().isEmpty(): self.adjustSize() qt_utils.centralize(self) def update_selected(self): if self.pkgs: requires_root = False to_update = [] for app_v in self.pkgs: if app_v.update_checked: to_update.append(app_v) if self.manager.requires_root('update', app_v.model): requires_root = True if to_update and dialog.ask_confirmation( title=self.i18n['manage_window.upgrade_all.popup.title'], body=self.i18n['manage_window.upgrade_all.popup.body'], locale_keys=self.i18n, widgets=[ UpdateToggleButton( None, self, self.i18n, clickable=False) ]): pwd = None if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self.progress_controll_enabled = len(to_update) == 1 self._begin_action(self.i18n['manage_window.status.upgrading']) self.thread_update.apps_to_update = to_update self.thread_update.root_password = pwd self.thread_update.start() def _finish_update_selected(self, res: dict): self.finish_action() if res['success']: if self._can_notify_user(): util.notify_user('{} {}'.format( res['updated'], self.i18n['notification.update_selected.success'])) self.refresh_apps(pkg_types=res['types']) if self.tray_icon: self.tray_icon.verify_updates() else: if self._can_notify_user(): util.notify_user( self.i18n['notification.update_selected.failed']) self.ref_bt_upgrade.setVisible(True) self.checkbox_console.setChecked(True) def _update_action_output(self, output: str): self.textarea_output.appendPlainText(output) def _begin_action(self, action_label: str, keep_search: bool = False, keep_bt_installed: bool = True, clear_filters: bool = False): self.ref_input_name_filter.setVisible(False) self.ref_combo_filter_type.setVisible(False) self.ref_bt_settings.setVisible(False) self.thread_animate_progress.stop = False self.thread_animate_progress.start() self.ref_progress_bar.setVisible(True) self.ref_combo_styles.setVisible(False) self.label_status.setText(action_label + "...") self.ref_bt_upgrade.setVisible(False) self.ref_bt_refresh.setVisible(False) self.checkbox_only_apps.setEnabled(False) self.table_apps.setEnabled(False) self.checkbox_updates.setEnabled(False) if not keep_bt_installed: self.ref_bt_installed.setVisible(False) elif self.ref_bt_installed.isVisible(): self.ref_bt_installed.setEnabled(False) if keep_search: self.ref_toolbar_search.setVisible(True) else: self.ref_toolbar_search.setVisible(False) if clear_filters: self._update_type_filters({}) else: self.combo_filter_type.setEnabled(False) def finish_action(self): self.ref_combo_styles.setVisible(True) self.thread_animate_progress.stop = True self.thread_animate_progress.wait() self.ref_progress_bar.setVisible(False) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(False) self._change_label_substatus('') self.ref_bt_settings.setVisible(True) self.ref_bt_refresh.setVisible(True) self.checkbox_only_apps.setEnabled(True) self.table_apps.setEnabled(True) self.input_search.setEnabled(True) self.label_status.setText('') self.label_substatus.setText('') self.ref_toolbar_search.setVisible(True) self.ref_toolbar_search.setEnabled(True) self.combo_filter_type.setEnabled(True) self.checkbox_updates.setEnabled(True) self.progress_controll_enabled = True if self.pkgs: self.ref_input_name_filter.setVisible(True) self.update_bt_upgrade() self._update_type_filters() if self.ref_bt_installed.isVisible(): self.ref_bt_installed.setEnabled(True) def downgrade(self, pkgv: PackageView): pwd = None requires_root = self.manager.requires_root('downgrade', pkgv.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.downgrading'], pkgv.model.name)) self.thread_downgrade.app = pkgv self.thread_downgrade.root_password = pwd self.thread_downgrade.start() def get_app_info(self, pkg: dict): self._handle_console_option(False) self._begin_action(self.i18n['manage_window.status.info']) self.thread_get_info.app = pkg self.thread_get_info.start() def get_app_history(self, app: dict): self._handle_console_option(False) self._begin_action(self.i18n['manage_window.status.history']) self.thread_get_history.app = app self.thread_get_history.start() def _finish_get_info(self, app_info: dict): self.finish_action() dialog_info = InfoDialog(app=app_info, icon_cache=self.icon_cache, locale_keys=self.i18n, screen_size=self.screen_size) dialog_info.exec_() def _finish_get_history(self, res: dict): self.finish_action() if res.get('error'): self._handle_console_option(True) self.textarea_output.appendPlainText(res['error']) self.checkbox_console.setChecked(True) else: dialog_history = HistoryDialog(res['history'], self.icon_cache, self.i18n) dialog_history.exec_() def _begin_search(self, word): self._handle_console_option(False) self.ref_checkbox_only_apps.setVisible(False) self.ref_checkbox_updates.setVisible(False) self.filter_updates = False self._begin_action('{} {}'.format( self.i18n['manage_window.status.searching'], word if word else ''), clear_filters=True) def search(self): word = self.input_search.text().strip() if word: self._begin_search(word) self.thread_search.word = word self.thread_search.start() def _finish_search(self, res: dict): self.finish_action() if not res['error']: self.ref_bt_upgrade.setVisible(False) self.update_pkgs(res['pkgs_found'], as_installed=False, ignore_updates=True) else: dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING) def install(self, pkg: PackageView): pwd = None requires_root = self.manager.requires_root('install', pkg.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.installing'], pkg.model.name)) self.thread_install.pkg = pkg self.thread_install.root_password = pwd self.thread_install.start() def _finish_install(self, res: dict): self.input_search.setText('') self.finish_action() console_output = self.textarea_output.toPlainText() if console_output: log_path = '/tmp/bauh/logs/install/{}/{}'.format( res['pkg'].model.get_type(), res['pkg'].model.name) try: Path(log_path).mkdir(parents=True, exist_ok=True) log_file = log_path + '/{}.log'.format(int(time.time())) with open(log_file, 'w+') as f: f.write(console_output) self.textarea_output.appendPlainText( self.i18n['console.install_logs.path'].format( '"{}"'.format(log_file))) except: self.textarea_output.appendPlainText( "[warning] Could not write install log file to '{}'". format(log_path)) if res['success']: self.recent_installation = True if self._can_notify_user(): util.notify_user(msg='{} ({}) {}'.format( res['pkg'].model.name, res['pkg'].model.get_type(), self.i18n['installed'])) self._finish_refresh_apps({ 'installed': [res['pkg'].model], 'total': 1, 'types': None }) self.ref_bt_installed.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) else: if self._can_notify_user(): util.notify_user('{}: {}'.format( res['pkg'].model.name, self.i18n['notification.install.failed'])) self.checkbox_console.setChecked(True) def _update_progress(self, value: int): self.progress_bar.setValue(value) def _finish_run_app(self, success: bool): self.finish_action() def execute_custom_action(self, pkg: PackageView, action: PackageAction): pwd = None if not is_root() and action.requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format(self.i18n[action.i18n_status_key], pkg.model.name)) self.thread_custom_action.pkg = pkg self.thread_custom_action.root_password = pwd self.thread_custom_action.custom_action = action self.thread_custom_action.start() def _finish_custom_action(self, res: dict): self.finish_action() if res['success']: self.refresh_apps(pkg_types={res['pkg'].model.__class__}) else: self.checkbox_console.setChecked(True) def show_gems_selector(self): gem_panel = GemSelectorPanel(window=self, manager=self.manager, i18n=self.i18n, config=self.config, show_panel_after_restart=bool( self.tray_icon)) gem_panel.show() def _show_settings_menu(self): menu_row = QMenu() if isinstance(self.manager, GenericSoftwareManager): action_gems = QAction(self.i18n['manage_window.settings.gems']) action_gems.setIcon(self.icon_app) action_gems.triggered.connect(self.show_gems_selector) menu_row.addAction(action_gems) action_about = QAction(self.i18n['manage_window.settings.about']) action_about.setIcon(QIcon(resource.get_path('img/about.svg'))) action_about.triggered.connect(self._show_about) menu_row.addAction(action_about) menu_row.adjustSize() menu_row.popup(QCursor.pos()) menu_row.exec_()
class MyTextEditDialog(QMainWindow): def __init__(self, parent): super(MyTextEditDialog, self).__init__(parent) layout = QVBoxLayout() self.editor = TextEdit() # Setup the QTextEdit editor configuration self.editor.setAutoFormatting(QTextEdit.AutoAll) self.editor.selectionChanged.connect(self.update_format) # Initialize default font size. font = QFont('Times', 12) self.editor.setFont(font) # We need to repeat the size to init the current format. self.editor.setFontPointSize(12) # self.path holds the path of the currently open file. # If none, we haven't got a file open yet (or creating new). self.path = None layout.addWidget(self.editor) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) self.status = QStatusBar() self.setStatusBar(self.status) # Uncomment to disable native menubar on Mac # self.menuBar().setNativeMenuBar(False) self.file_toolbar = QToolBar("File") self.file_toolbar.setIconSize(QSize(14, 14)) self.addToolBar(self.file_toolbar) file_menu = self.menuBar().addMenu("&File") open_file_action = QAction( QIcon(os.path.join('images', 'blue-folder-open-document.png')), "Open file...", self) open_file_action.setStatusTip("Open file") open_file_action.triggered.connect(self.file_open) file_menu.addAction(open_file_action) self.file_toolbar.addAction(open_file_action) save_file_action = QAction(QIcon(os.path.join('images', 'disk.png')), "Save", self) save_file_action.setStatusTip("Save current page") save_file_action.triggered.connect(self.file_save) file_menu.addAction(save_file_action) self.file_toolbar.addAction(save_file_action) saveas_file_action = QAction( QIcon(os.path.join('images', 'disk--pencil.png')), "Save As...", self) saveas_file_action.setStatusTip("Save current page to specified file") saveas_file_action.triggered.connect(self.file_saveas) file_menu.addAction(saveas_file_action) self.file_toolbar.addAction(saveas_file_action) print_action = QAction(QIcon(os.path.join('images', 'printer.png')), "Print...", self) print_action.setStatusTip("Print current page") print_action.triggered.connect(self.file_print) file_menu.addAction(print_action) self.file_toolbar.addAction(print_action) self.edit_toolbar = QToolBar("Edit") self.edit_toolbar.setIconSize(QSize(16, 16)) self.addToolBar(self.edit_toolbar) edit_menu = self.menuBar().addMenu("&Edit") undo_action = QAction( QIcon(os.path.join('images', 'arrow-curve-180-left.png')), "Undo", self) undo_action.setStatusTip("Undo last change") undo_action.triggered.connect(self.editor.undo) edit_menu.addAction(undo_action) redo_action = QAction(QIcon(os.path.join('images', 'arrow-curve.png')), "Redo", self) redo_action.setStatusTip("Redo last change") redo_action.triggered.connect(self.editor.redo) self.edit_toolbar.addAction(redo_action) edit_menu.addAction(redo_action) edit_menu.addSeparator() cut_action = QAction(QIcon(os.path.join('images', 'scissors.png')), "Cut", self) cut_action.setStatusTip("Cut selected text") cut_action.setShortcut(QKeySequence.Cut) cut_action.triggered.connect(self.editor.cut) self.edit_toolbar.addAction(cut_action) edit_menu.addAction(cut_action) copy_action = QAction( QIcon(os.path.join('images', 'document-copy.png')), "Copy", self) copy_action.setStatusTip("Copy selected text") cut_action.setShortcut(QKeySequence.Copy) copy_action.triggered.connect(self.editor.copy) self.edit_toolbar.addAction(copy_action) edit_menu.addAction(copy_action) paste_action = QAction( QIcon(os.path.join('images', 'clipboard-paste-document-text.png')), "Paste", self) paste_action.setStatusTip("Paste from clipboard") cut_action.setShortcut(QKeySequence.Paste) paste_action.triggered.connect(self.editor.paste) self.edit_toolbar.addAction(paste_action) edit_menu.addAction(paste_action) select_action = QAction( QIcon(os.path.join('images', 'selection-input.png')), "Select all", self) select_action.setStatusTip("Select all text") cut_action.setShortcut(QKeySequence.SelectAll) select_action.triggered.connect(self.editor.selectAll) edit_menu.addAction(select_action) edit_menu.addSeparator() wrap_action = QAction( QIcon(os.path.join('images', 'arrow-continue.png')), "Wrap text to window", self) wrap_action.setStatusTip("Toggle wrap text to window") wrap_action.setCheckable(True) wrap_action.setChecked(True) wrap_action.triggered.connect(self.edit_toggle_wrap) edit_menu.addAction(wrap_action) self.format_toolbar = QToolBar("Format") self.format_toolbar.setIconSize(QSize(16, 16)) self.addToolBar(self.format_toolbar) self.format_menu = self.menuBar().addMenu("&Format") # We need references to these actions/settings to update as selection changes, so attach to self. # self.bt_ = QPushButton() # self.fonts.currentFontChanged.connect(self.editor.setCurrentFont) # self.format_toolbar.addWidget(self.fonts) self.fonts = QFontComboBox() self.fonts.currentFontChanged.connect(self.editor.setCurrentFont) self.format_toolbar.addWidget(self.fonts) self.fontsize = QComboBox() self.fontsize.addItems([str(s) for s in FONT_SIZES]) # Connect to the signal producing the text of the current selection. Convert the string to float # and set as the pointsize. We could also use the index + retrieve from FONT_SIZES. self.fontsize.currentIndexChanged[str].connect( lambda s: self.editor.setFontPointSize(float(s))) self.format_toolbar.addWidget(self.fontsize) self.bold_action = QAction( QIcon(os.path.join('images', 'edit-bold.png')), "Bold", self) self.bold_action.setStatusTip("Bold") self.bold_action.setShortcut(QKeySequence.Bold) self.bold_action.setCheckable(True) self.bold_action.toggled.connect(lambda x: self.editor.setFontWeight( QFont.Bold if x else QFont.Normal)) self.format_toolbar.addAction(self.bold_action) self.format_menu.addAction(self.bold_action) self.italic_action = QAction( QIcon(os.path.join('images', 'edit-italic.png')), "Italic", self) self.italic_action.setStatusTip("Italic") self.italic_action.setShortcut(QKeySequence.Italic) self.italic_action.setCheckable(True) self.italic_action.toggled.connect(self.editor.setFontItalic) self.format_toolbar.addAction(self.italic_action) self.format_menu.addAction(self.italic_action) self.underline_action = QAction( QIcon(os.path.join('images', 'edit-underline.png')), "Underline", self) self.underline_action.setStatusTip("Underline") self.underline_action.setShortcut(QKeySequence.Underline) self.underline_action.setCheckable(True) self.underline_action.toggled.connect(self.editor.setFontUnderline) self.format_toolbar.addAction(self.underline_action) self.format_menu.addAction(self.underline_action) self.format_menu.addSeparator() self.alignl_action = QAction( QIcon(os.path.join('images', 'edit-alignment.png')), "Align left", self) self.alignl_action.setStatusTip("Align text left") self.alignl_action.setCheckable(True) self.alignl_action.triggered.connect( lambda: self.editor.setAlignment(Qt.AlignLeft)) self.format_toolbar.addAction(self.alignl_action) self.format_menu.addAction(self.alignl_action) self.alignc_action = QAction( QIcon(os.path.join('images', 'edit-alignment-center.png')), "Align center", self) self.alignc_action.setStatusTip("Align text center") self.alignc_action.setCheckable(True) self.alignc_action.triggered.connect( lambda: self.editor.setAlignment(Qt.AlignCenter)) self.format_toolbar.addAction(self.alignc_action) self.format_menu.addAction(self.alignc_action) self.alignr_action = QAction( QIcon(os.path.join('images', 'edit-alignment-right.png')), "Align right", self) self.alignr_action.setStatusTip("Align text right") self.alignr_action.setCheckable(True) self.alignr_action.triggered.connect( lambda: self.editor.setAlignment(Qt.AlignRight)) self.format_toolbar.addAction(self.alignr_action) self.format_menu.addAction(self.alignr_action) self.alignj_action = QAction( QIcon(os.path.join('images', 'edit-alignment-justify.png')), "Justify", self) self.alignj_action.setStatusTip("Justify text") self.alignj_action.setCheckable(True) self.alignj_action.triggered.connect( lambda: self.editor.setAlignment(Qt.AlignJustify)) self.format_toolbar.addAction(self.alignj_action) self.format_menu.addAction(self.alignj_action) format_group = QActionGroup(self) format_group.setExclusive(True) format_group.addAction(self.alignl_action) format_group.addAction(self.alignc_action) format_group.addAction(self.alignr_action) format_group.addAction(self.alignj_action) self.format_menu.addSeparator() # A list of all format-related widgets/actions, so we can disable/enable signals when updating. self._format_actions = [ self.fonts, self.fontsize, self.bold_action, self.italic_action, self.underline_action, # We don't need to disable signals for alignment, as they are paragraph-wide. ] # Initialize. self.update_format() self.update_title() self.hideAllButTextEdit(False) self.show() def block_signals(self, objects, b): for o in objects: o.blockSignals(b) def update_format(self): """ Update the font format toolbar/actions when a new text selection is made. This is neccessary to keep toolbars/etc. in sync with the current edit state. :return: """ # Disable signals for all format widgets, so changing values here does not trigger further formatting. self.block_signals(self._format_actions, True) self.fonts.setCurrentFont(self.editor.currentFont()) # Nasty, but we get the font-size as a float but want it was an int self.fontsize.setCurrentText(str(int(self.editor.fontPointSize()))) self.italic_action.setChecked(self.editor.fontItalic()) self.underline_action.setChecked(self.editor.fontUnderline()) self.bold_action.setChecked(self.editor.fontWeight() == QFont.Bold) self.alignl_action.setChecked(self.editor.alignment() == Qt.AlignLeft) self.alignc_action.setChecked( self.editor.alignment() == Qt.AlignCenter) self.alignr_action.setChecked(self.editor.alignment() == Qt.AlignRight) self.alignj_action.setChecked( self.editor.alignment() == Qt.AlignJustify) self.block_signals(self._format_actions, False) def dialog_critical(self, s): dlg = QMessageBox(self) dlg.setText(s) dlg.setIcon(QMessageBox.Critical) dlg.show() def file_open(self): path, _ = QFileDialog.getOpenFileName( self, "Open file", "", "HTML documents (*.html);Text documents (*.txt);All files (*.*)") try: with open(path, 'rU') as f: text = f.read() except Exception as e: self.dialog_critical(str(e)) else: self.path = path # Qt will automatically try and guess the format as txt/html self.editor.setText(text) self.update_title() def file_save(self): if self.path is None: # If we do not have a path, we need to use Save As. return self.file_saveas() text = self.editor.toHtml() if splitext( self.path) in HTML_EXTENSIONS else self.editor.toPlainText() try: with open(self.path, 'w') as f: f.write(text) except Exception as e: self.dialog_critical(str(e)) def file_saveas(self): path, _ = QFileDialog.getSaveFileName( self, "Save file", "", "HTML documents (*.html);Text documents (*.txt);All files (*.*)") if not path: # If dialog is cancelled, will return '' return text = self.editor.toHtml() if splitext( path) in HTML_EXTENSIONS else self.editor.toPlainText() try: with open(path, 'w') as f: f.write(text) except Exception as e: self.dialog_critical(str(e)) else: self.path = path self.update_title() def file_print(self): dlg = QPrintDialog() if dlg.exec_(): self.editor.print_(dlg.printer()) def update_title(self): self.setWindowTitle( "%s - Megasolid Idiom" % (os.path.basename(self.path) if self.path else "Untitled")) def edit_toggle_wrap(self): self.editor.setLineWrapMode(1 if self.editor.lineWrapMode() == 0 else 0) def hideAllButTextEdit(self, bShow): if (not bShow): # hide all but text self.format_toolbar.hide() self.file_toolbar.hide() self.edit_toolbar.hide() self.menuWidget().hide() self.status.hide() pass else: # show all self.format_toolbar.show() self.file_toolbar.show() self.edit_toolbar.show() self.menuWidget().show() self.status.show() pass
class MainWindow(QMainWindow): """ """ def __init__(self): super(MainWindow, self).__init__() self.menu_bar = QMenuBar() self.file_menu = QMenu("文件(&F)", self.menu_bar) self.open_action = QAction("打开(O)", self.file_menu) self.open_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) self.search_action = QAction("搜索(F)", self.file_menu) self.search_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F)) self.search_action.triggered.connect(self.show_search_widget) self.close_action = QAction("关闭(C)", self.file_menu) self.close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_C)) self.close_action.triggered.connect(self.answer_close_action_triggered) self.quit_action = QAction("退出(Q)", self.file_menu) self.quit_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) self.quit_action.triggered.connect(lambda: sys.exit()) self.play_menu = QMenu("播放(&L)", self.menu_bar) self.play_action = QAction("播放(P)", self.play_menu) self.stop_action = QAction("停止(S)", self.play_menu) self.pause_action = QAction("暂停(P)", self.play_menu) self.resume_action = QAction("恢复(R)", self.play_menu) self.rate_action = QAction("倍速(R)", self.play_menu) self.jump_action = QAction("跳转(J)", self.play_menu) self.mute_action = QAction("静音(M)", self.play_menu) self.volume_up_action = QAction("增大音量(U)", self.play_menu) self.volume_down_action = QAction("减小音量(D)", self.play_menu) self.enhance_menu = QMenu("增强(&E)", self.menu_bar) self.skin_menu = QMenu("换肤", self.enhance_menu) self.dark_skin_action = QAction("暗黑模式", self.skin_menu) self.dark_skin_action.setCheckable(True) self.dark_skin_action.triggered.connect( self.answer_dark_skin_action_triggered) self.white_skin_action = QAction("纯白模式", self.skin_menu) self.white_skin_action.setCheckable(True) self.white_skin_action.triggered.connect( self.answer_white_skin_action_triggered) self.blue_skin_action = QAction("浅蓝模式", self.skin_menu) self.blue_skin_action.setCheckable(True) self.blue_skin_action.triggered.connect( self.answer_blue_skin_action_triggered) self.skin_action_group = QActionGroup(self) self.skin_action_group.addAction(self.dark_skin_action) self.skin_action_group.addAction(self.white_skin_action) self.skin_action_group.addAction(self.blue_skin_action) self.language_menu = QMenu("语言", self.enhance_menu) self.zh_CN_action = QAction("简体中文", self.language_menu) self.zh_CN_action.setCheckable(True) self.zh_CN_action.triggered.connect(self.answer_zh_CN_action_triggered) self.en_action = QAction("English", self.language_menu) self.en_action.setCheckable(True) self.en_action.triggered.connect(self.answer_en_action_triggered) self.language_action_group = QActionGroup(self) self.language_action_group.addAction(self.zh_CN_action) self.language_action_group.addAction(self.en_action) self.font_action = QAction("字体(F)", self.enhance_menu) self.font_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) self.font_action.triggered.connect(self.answer_font_action_triggered) self.hide_action = QAction("隐藏(V)", self.enhance_menu) self.hide_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_V)) self.hide_action.setCheckable(True) self.hide_action.triggered.connect(self.answer_hide_action_triggered) self.tool_menu = QMenu("工具(&T)", self.menu_bar) self.screenshot_action = QAction("截图(J)", self.tool_menu) self.screenshot_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J)) self.screenshot_action.triggered.connect( self.answer_screenshot_action_triggered) self.gif_action = QAction("动图(G)", self.tool_menu) self.gif_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_G)) self.gif_action.triggered.connect(self.answer_gif_action_triggered) self.screen_record_action = QAction("录屏(L)", self.tool_menu) self.screen_record_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_L)) self.screen_record_action.triggered.connect( self.answer_screen_record_action_triggered) self.help_menu = QMenu("帮助(&H)", self.menu_bar) self.help_action = QAction("帮助文档(H)", self.help_menu) self.help_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_H)) self.help_action.triggered.connect(self.answer_help_action_triggered) self.change_log_action = QAction("更新日志(U)", self.help_menu) self.change_log_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_U)) self.change_log_action.triggered.connect( self.answer_change_log_action_triggered) self.check_version_action = QAction("检查版本(C)", self.help_menu) self.check_version_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_C)) self.check_version_action.triggered.connect( self.answer_check_version_action_triggered) self.about_action = QAction("关于软件(A)", self.help_menu) self.about_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_A)) self.about_action.triggered.connect(self.answer_about_action_triggered) self.tool_bar = QToolBar() self.tool_bar.setFloatable(False) self.tool_bar.setMovable(True) self.tool_bar.setIconSize(QSize(32, 32)) # self.tool_bar.setStyleSheet("QToolBar{border: 1px solid #313335; spacing:5px; }") self.addToolBar(Qt.TopToolBarArea, self.tool_bar) self.search_tool_action = QAction("", self.tool_bar) self.search_tool_action.setToolTip("信息搜索") self.search_tool_action.setIcon( QIcon("./resources/img/[email protected]")) self.search_widget = SearchWidget() self.search_tool_action.triggered.connect(self.show_search_widget) self.search_widget.watch_live_signal.connect( self.answer_watch_live_signal) self.attention_tool_action = QAction("", self.tool_bar) self.attention_tool_action.setToolTip("历史关注") self.attention_tool_action.setIcon( QIcon("./resources/img/[email protected]")) # self.attention_tool_action.triggered.connect(self.show_search_widget) self.pure_tool_action = QAction("", self.tool_bar) self.pure_tool_action.setToolTip("纯净模式") self.pure_tool_action.setIcon( QIcon("./resources/img/[email protected]")) # self.pure_tool_action.triggered.connect(self.show_search_widget) self.nlp_tool_action = QAction("", self.tool_bar) self.nlp_tool_action.setToolTip("智能字幕") self.nlp_tool_action.setIcon(QIcon("./resources/img/[email protected]")) # self.nlp_tool_action.triggered.connect(self.show_search_widget) self.note_tool_action = QAction("", self.tool_bar) self.note_tool_action.setToolTip("边看边记") self.note_tool_action.setIcon( QIcon("./resources/img/[email protected]")) # self.note_tool_action.triggered.connect(self.show_search_widget) self.live_widget = LiveWidget() self.init_ui() self._init_cfg() def init_ui(self): """ :return: """ # 菜单栏 self.file_menu.addAction(self.open_action) self.file_menu.addAction(self.search_action) self.file_menu.addSeparator() self.file_menu.addAction(self.close_action) self.file_menu.addAction(self.quit_action) self.play_menu.addAction(self.play_action) self.play_menu.addAction(self.stop_action) self.play_menu.addAction(self.pause_action) self.play_menu.addAction(self.resume_action) self.play_menu.addSeparator() self.play_menu.addAction(self.rate_action) self.play_menu.addAction(self.jump_action) self.play_menu.addSeparator() self.play_menu.addAction(self.mute_action) self.play_menu.addAction(self.volume_up_action) self.play_menu.addAction(self.volume_down_action) self.enhance_menu.addMenu(self.skin_menu) self.skin_menu.addAction(self.dark_skin_action) self.skin_menu.addAction(self.white_skin_action) self.skin_menu.addAction(self.blue_skin_action) self.enhance_menu.addMenu(self.language_menu) self.language_menu.addAction(self.zh_CN_action) self.language_menu.addAction(self.en_action) self.enhance_menu.addAction(self.font_action) self.enhance_menu.addSeparator() self.enhance_menu.addAction(self.hide_action) self.tool_menu.addAction(self.screenshot_action) self.tool_menu.addAction(self.gif_action) self.tool_menu.addAction(self.screen_record_action) self.tool_menu.addSeparator() self.help_menu.addAction(self.help_action) self.help_menu.addAction(self.change_log_action) self.help_menu.addAction(self.check_version_action) self.help_menu.addSeparator() self.help_menu.addAction(self.about_action) self.menu_bar.addMenu(self.file_menu) self.menu_bar.addMenu(self.play_menu) self.menu_bar.addMenu(self.enhance_menu) self.menu_bar.addMenu(self.tool_menu) self.menu_bar.addMenu(self.help_menu) self.setMenuBar(self.menu_bar) # 工具栏 self.tool_bar.addAction(self.search_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.attention_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.pure_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.nlp_tool_action) self.tool_bar.addSeparator() self.tool_bar.addAction(self.note_tool_action) self.tool_bar.addSeparator() # 显示区域 self.setCentralWidget(self.live_widget) self.set_window_info() def _init_cfg(self): """ :return: """ # 工具栏可见性 visible = retrieve_content("preferences", "tool_bar_visible") if visible == "True": self.hide_action.setChecked(False) self.hide_action.triggered.emit(False) else: self.hide_action.setChecked(True) self.hide_action.triggered.emit(True) # 工具栏位置 position = retrieve_content("preferences", "tool_bar_position") # 皮肤设置 skin = retrieve_content("preferences", "skin") if skin == "dark": self.dark_skin_action.setChecked(True) self.dark_skin_action.triggered.emit() elif skin == "white": self.white_skin_action.setChecked(True) self.white_skin_action.triggered.emit() else: self.blue_skin_action.setChecked(True) self.blue_skin_action.triggered.emit() # 字体设置 font_family = retrieve_content("preferences", "font-family") font_style = retrieve_content("preferences", "font-style") font_size = retrieve_content("preferences", "font-size") font = QFont() font.setFamily(font_family) font.setStyleName(font_style) font.setPointSize(int(font_size)) qApp.setFont(font) qApp.processEvents() def set_window_info(self): """ :return: """ desktop_widget = QDesktopWidget() screen_rect = desktop_widget.screenGeometry() self.setGeometry(screen_rect) title = retrieve_content("software_info", "software_name") + " " + retrieve_content( "software_info", "software_version") self.setWindowTitle(title) self.setWindowIcon(QIcon('./resources/img/[email protected]')) self.showMaximized() def show_search_widget(self): """ :return: """ width, height = get_window_center_point(self.search_widget) self.search_widget.move(width, height) self.search_widget.exec_() @staticmethod def answer_help_action_triggered(): """ :return: """ desktop_services = QDesktopServices() desktop_services.openUrl( QUrl( "https://github.com/parzulpan/real-live/blob/master/resources/Help.md" )) @staticmethod def answer_change_log_action_triggered(): """ :return: """ desktop_services = QDesktopServices() desktop_services.openUrl( QUrl( "https://github.com/parzulpan/real-live/blob/master/resources/ChangeLog.md" )) @staticmethod def answer_check_version_action_triggered(): """ :return: """ # TODO: 获取GitHub API 进行检查并弹窗 desktop_services = QDesktopServices() desktop_services.openUrl( QUrl("https://github.com/parzulpan/real-live/releases")) @staticmethod def answer_about_action_triggered(): """ :return: """ about_widget = AboutWidget() width, height = get_window_center_point(about_widget) about_widget.move(width, height) about_widget.exec_() def answer_watch_live_signal(self, url): """ :param url: :return: """ # self.live_widget.vlc_widget.get_player() self.live_widget.vlc_widget.play_url(url) self.live_widget.set_player_widget(True) def answer_close_action_triggered(self): """ :return: """ self.live_widget.set_player_widget(False) # self.live_widget.vlc_widget.get_player() # self.live_widget.vlc_widget.release_player() self.live_widget.vlc_widget.stop() def answer_hide_action_triggered(self, checked): """ :param checked: :return: """ if checked: self.tool_bar.hide() update_contents("preferences", "tool_bar_visible", "False") else: self.tool_bar.show() update_contents("preferences", "tool_bar_visible", "True") def answer_tool_bar_top_level_changed(self, area): """ :return: """ print("top_level_changed") print(area) if self.tool_bar.allowedAreas() == Qt.LeftToolBarArea: print("LeftToolBarArea") elif self.tool_bar.allowedAreas() == Qt.RightToolBarArea: print("RightToolBarArea") elif self.tool_bar.allowedAreas() == Qt.BottomToolBarArea: print("BottomToolBarArea") elif self.tool_bar.allowedAreas() == Qt.TopToolBarArea: print("TopToolBarArea") else: print("else") @staticmethod def answer_screenshot_action_triggered(): """ :return: """ # dll = windll.LoadLibrary('./bin/OEScreenshot.dll') # dll = WinDLL('./bin/OEScreenshot.dll') # dll = cdll.LoadLibrary('./bin/OEScreenshot.dll') dll = CDLL('./bin/screenshot/OEScreenshot.dll') print(dll) def answer_gif_action_triggered(self): """ :return: """ pass def answer_screen_record_action_triggered(self): """ :return: """ pass @staticmethod def answer_dark_skin_action_triggered(checked): """ :param checked: :return: """ with open("./resources/qss/dark.qss", "r", encoding="utf-8") as f: qss = f.read() qApp.setStyleSheet(qss) update_contents("preferences", "skin", "dark") @staticmethod def answer_white_skin_action_triggered(checked): """ :param checked: :return: """ with open("./resources/qss/white.qss", "r", encoding="utf-8") as f: qss = f.read() qApp.setStyleSheet(qss) update_contents("preferences", "skin", "white") @staticmethod def answer_blue_skin_action_triggered(checked): """ :param checked: :return: """ with open("./resources/qss/blue.qss", "r", encoding="utf-8") as f: qss = f.read() qApp.setStyleSheet(qss) update_contents("preferences", "skin", "blue") def answer_font_action_triggered(self, checked): """ :param checked: :return: """ font = qApp.font() font, changed = QFontDialog().getFont(font, self, caption="字体设置") if changed: qApp.setFont(font) qApp.processEvents() update_contents("preferences", "font-family", font.family()) update_contents("preferences", "font-style", font.styleName()) update_contents("preferences", "font-size", str(font.pointSize())) def answer_zh_CN_action_triggered(self, checked): """ :param checked: :return: """ pass def answer_en_action_triggered(self, checked): """ :param checked: :return: """ pass def closeEvent(self, event) -> None: """ :param event: :return: """ print("closeEvent") self.live_widget.vlc_widget.release_player()
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 = pyqtSignal() filesAndProjectsLoaded = pyqtSignal() __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 def __init__(self, start_server=False): QMainWindow.__init__(self) 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 # 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) # 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() # TODO: # 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", "slot": self.show_message }, { "target": "main_container", "signal_name": "currentEditorChanged", "slot": self.change_window_title }, { "target": "main_container", "signal_name": "openPreferences", "slot": self.show_preferences }, { "target": "main_container", "signal_name": "currentEditorChanged", "slot": self._change_item_in_project }) self.register_signals('ide', connections) # connections = ( # {'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}, # ) # Central Widget MUST always exists self.central = IDE.get_service('central_container') 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: # These two are the same service, I think that's ok menu_bar.load_menu(self) 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) IDE.__instance = self def _change_item_in_project(self, filename): project_explorer = IDE.get_service("projects_explorer") if project_explorer is not None: project_explorer.set_current_item(filename) @classmethod def get_service(cls, service_name): """Return the instance of a registered service.""" service = cls.__IDESERVICES.get(service_name, None) if service is None: logger.debug("Service '{}' unregistered".format(service_name)) return service 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.__created: cls.__instance.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.__created: cls.__instance._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): # FIXME: sl = getattr(target, signal_name, None) if sl is not None: sl.connect(slot) connection['connected'] = True # print("Falta conectar {} a {}".format(signal_name, # slot.__name__)) # self.connect(target, SIGNAL(signal_name), 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['PyQt_PyObject'].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 @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) pref.setModal(True) # main_container = IDE.get_service("main_container") # if main_container: # main_container.show_dialog(pref) # else: pref.show() def load_session_files_projects(self, files, projects, current_file, recent_files=None): """Load the files and projects from previous session.""" # Load projects projects_explorer = IDE.get_service('projects_explorer') projects_explorer.load_session_projects(projects) # Load files main_container = IDE.get_service('main_container') for path, cursor_pos in files: line, col = cursor_pos main_container.open_file(path, line, col) if current_file: main_container.open_file(current_file) self.filesAndProjectsLoaded.emit() # 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.""" # display_name - project - NINJA-IDE if self._session is None: self.setWindowTitle('%s - NINJA-IDE' % 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: QMainWindow.wheelEvent(self, event) @classmethod def ninja_settings(cls): qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) return qsettings @classmethod def data_settings(cls): qsettings = QSettings(resources.DATA_SETTINGS_PATH, QSettings.IniFormat) return qsettings # @classmethod # def ninja_settings(cls, qobject=None): # qsettings = nsettings.NSettings(resources.SETTINGS_PATH, qobject, # prefix="ns") # if cls.__created: # qsettings.valueChanged['PyQt_PyObject', # 'QString', # 'PyQt_PyObject'].connect( # cls.__instance._settings_value_changed) # return qsettings # @classmethod # def data_settings(cls): # qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH, # prefix="ds") # if cls.__created: # qsettings.valueChanged['PyQt_PyObject', # 'QString', # 'PyQt_PyObject'].connect( # cls.__instance._settings_value_changed) # return qsettings # def _settings_value_changed(self, qobject, key, value): # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_") # signal_name = "%s" % key.replace("/", "_") # print(qobject, key) # callback = getattr(self, signal_name, None) # if hasattr(callback, "__call__"): # callback() # print(signal_name, value) # self.emit(SIGNAL(signal_name), value) # print("Falta emitir {}".format(signal_name)) def save_settings(self): """ Save the settings before the application is closed with QSettings. Info saved: files and projects opened, windows state(size and position). """ data_settings = IDE.data_settings() ninja_settings = IDE.ninja_settings() # Get opened files opened_files = self.filesystem.get_files() files_info = [] for path in opened_files: editable = self.__neditables.get(opened_files[path]) files_info.append((path, editable.editor.cursor_position)) data_settings.setValue('last_session/opened_files', files_info) # Current opened file main_container = self.get_service("main_container") neditor = main_container.get_current_editor() current_file = '' if neditor is not None: current_file = neditor.file_path data_settings.setValue('last_session/current_file', current_file) # Save toolbar visibility ninja_settings.setValue('window/hide_toolbar', not self.toolbar.isVisible()) # Get opened projects projects_obj = self.filesystem.get_projects() # rojects = [projects_obj[project].path for project in projects_obj] projects = [projects_obj[project].path for project in projects_obj] data_settings.setValue('last_session/projects', projects) # Save window state if self.isMaximized(): ninja_settings.setValue("window/maximized", True) else: ninja_settings.setValue("window/maximized", False) ninja_settings.setValue("window/size", self.size()) ninja_settings.setValue("window/pos", self.pos()) data_settings.setValue('lastSession/openedFiles', files_info) # projects_obj = self.filesystem.get_projects() # projects = [projects_obj[proj].path for proj in projects_obj] # data_settings.setValue('lastSession/projects', projects) # 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: # 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.cursor_position, # 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", QSizeF(800, 600))) self.move(qsettings.value("window/pos", QPointF(100, 100))) 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) if editable 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(nfile=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.""" print(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") if 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 MultiLayerSelect: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value("locale/userLocale")[0:2] locale_path = os.path.join(self.plugin_dir, "i18n", "MultiLayerSelect_{}.qm".format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Init settings self.settings = QSettings() self.settings.beginGroup("plugins/multilayerselect") def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # pylint: disable=invalid-name return QCoreApplication.translate("MultiLayerSelect", message) def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" # pylint: disable=invalid-name # Create settings dialog self.settings_dialog = SettingsDialog(self.settings, self.iface.mainWindow()) self.expression_dialog = None try: QgsProject.instance().selectionColorChanged.connect( self.on_color_changed) QgsProject.instance().selectionColorChanged.connect( self.settings_dialog.on_project_color_changed) except AttributeError: # QGIS < 3.10 self.settings_dialog.colorChanged.connect(self.on_color_changed) QgsProject.instance().readProject.connect( self.settings_dialog.on_project_color_changed) self.settings_dialog.settingsChanged.connect(self.on_settings_changed) self.toolbar = QToolBar("Multilayer Select", self.iface.mainWindow()) self.toolbar.setObjectName("MultiSelectToolbar") self.about_action = QAction( QIcon(":/plugins/multilayerselect/icons/about.svg"), self.tr("About"), parent=self.iface.mainWindow(), ) self.about_action.triggered.connect(self.show_about) self.settings_action = QAction( QIcon(":/images/themes/default/console/iconSettingsConsole.svg"), self.tr("Settings"), parent=self.iface.mainWindow(), ) self.settings_action.setObjectName("actionMultiLayerSelectSettings") self.settings_action.setToolTip( self.tr("<b>Multilayer Select Settings</b>")) self.settings_action.triggered.connect(self.show_settings) self.plugin_menu = self.iface.pluginMenu().addMenu( QIcon(":/plugins/multilayerselect/icons/icon.svg"), "Multilayer Select") self.plugin_menu.addAction(self.about_action) self.plugin_menu.addAction(self.settings_action) self.selection_tool_button = QToolButton(self.toolbar) self.selection_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self.selection_tool_button.setObjectName("selectionToolButton") self.advanced_selection_tool_button = QToolButton(self.toolbar) self.advanced_selection_tool_button.setPopupMode( QToolButton.MenuButtonPopup) self.advanced_selection_tool_button.setObjectName( "advancedSelectionToolButton") self.select_rect_tool = MultiSelectionAreaTool(self.iface.mapCanvas()) self.select_polygon_tool = MultiSelectionPolygonTool( self.iface.mapCanvas()) self.select_freehand_tool = MultiSelectionFreehandTool( self.iface.mapCanvas()) self.select_radius_tool = MultiSelectionRadiusTool( self.iface.mapCanvas()) self.actions_settings = [ SelectAction( text=self.tr("Select Features"), tooltip=self.tr( "<b>Select Features by area or single click</b>"), icon=":/plugins/multilayerselect/icons/selectRectangle.svg", objectname="actionMultiSelectByRectangle", tool=self.select_rect_tool, ), SelectAction( text=self.tr("Select Features by Polygon"), icon=":/plugins/multilayerselect/icons/selectPolygon.svg", objectname="actionMultiSelectByPolygon", tool=self.select_polygon_tool, ), SelectAction( text=self.tr("Select Features by Freehand"), icon=":/plugins/multilayerselect/icons/selectFreehand.svg", objectname="actionMultiSelectByFreehand", tool=self.select_freehand_tool, ), SelectAction( text=self.tr("Select Features by Radius"), icon=":/plugins/multilayerselect/icons/selectRadius.svg", objectname="actionMultiSelectByRadius", tool=self.select_radius_tool, ), ] def on_select_tool(tool, action): self.selection_tool_button.setDefaultAction(action) if self.embedded_selection_tool_button: self.embedded_selection_tool_button.setDefaultAction(action) self.iface.mapCanvas().setMapTool(tool) self.select_actions = [] for select_action in self.actions_settings: action = QAction(select_action.text) action.setToolTip(select_action.tooltip) action.setObjectName(select_action.objectname) action.setCheckable(True) select_action.tool.setAction(action) action.triggered.connect( partial(on_select_tool, select_action.tool, action)) self.selection_tool_button.addAction(action) if not self.selection_tool_button.defaultAction(): self.selection_tool_button.setDefaultAction(action) self.select_actions.append(action) self.toolbar.addWidget(self.selection_tool_button) self.select_all_action = QAction( self.tr("Select all features from all layers"), ) self.select_all_action.setToolTip("<b>{}</b>".format( self.select_all_action.text())) self.select_all_action.setObjectName("actionMultiSelectAll") self.select_all_action.triggered.connect(self.select_all) self.advanced_selection_tool_button.addAction(self.select_all_action) self.advanced_selection_tool_button.setDefaultAction( self.select_all_action) self.invert_all_action = QAction( self.tr("Invert selection for all layers"), ) self.invert_all_action.setToolTip("<b>{}</b>".format( self.invert_all_action.text())) self.invert_all_action.setObjectName("actionMultiSelectInvert") self.invert_all_action.triggered.connect(self.invert_all) self.advanced_selection_tool_button.addAction(self.invert_all_action) self.select_by_expr_action = QAction( QIcon(":/images/themes/default/mIconExpressionSelect.svg"), self.tr("Select Features by Expression..."), ) self.select_by_expr_action.setToolTip("<b>{}</b>".format( self.select_by_expr_action.text())) self.select_by_expr_action.setObjectName("actionMultiSelectExpr") self.select_by_expr_action.triggered.connect(self.select_by_expression) self.advanced_selection_tool_button.addAction( self.select_by_expr_action) self.toolbar.addWidget(self.advanced_selection_tool_button) self.deselect_all_action = QAction( self.tr("Deselect features from all layers")) self.deselect_all_action.setToolTip("<b>{}</b>".format( self.deselect_all_action.text())) self.deselect_all_action.setObjectName("actionDeselectAll") self.deselect_all_action.triggered.connect(self.deselect_all) self.toolbar.addAction(self.deselect_all_action) self.toolbar.addAction(self.settings_action) self.iface.mainWindow().addToolBar(self.toolbar) # Embedded actions self.embedded_selection_tool_button_action = None self.embedded_selection_tool_button = None self.embedded_advanced_tool_button_action = None self.embedded_advanced_tool_button = None self.on_color_changed() self.on_settings_changed() def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" # Delete Settings dialog self.settings_dialog.deleteLater() # Remove menu from plugins menu self.iface.pluginMenu().removeAction(self.plugin_menu.menuAction()) self.select_freehand_tool.deleteLater() self.select_polygon_tool.deleteLater() self.select_radius_tool.deleteLater() self.select_rect_tool.deleteLater() self.iface.mainWindow().removeToolBar(self.toolbar) self.toolbar.deleteLater() self.replace_default_action(False) try: QgsProject.instance().selectionColorChanged.disconnect( self.on_color_changed) QgsProject.instance().selectionColorChanged.disconnect( self.settings_dialog.on_project_color_changed) except AttributeError: # QGIS < 3.10 pass def show_about(self): """ Show the about dialog """ # Used to display plugin icon in the about message box bogus = QWidget(self.iface.mainWindow()) bogus.setWindowIcon(QIcon(":/plugins/multilayerselect/icons/icon.svg")) cfg = configparser.ConfigParser() cfg.read(os.path.join(os.path.dirname(__file__), "metadata.txt")) version = cfg.get("general", "version") homepage = cfg.get("general", "homepage") tracker = cfg.get("general", "tracker") repository = cfg.get("general", "repository") QMessageBox.about( bogus, self.tr("About Multilayer Select"), "<b>Version</b> {3}<br><br>" "<b>{4}</b> : <a href={0}>GitHub</a><br>" "<b>{5}</b> : <a href={1}>GitHub</a><br>" "<b>{6}</b> : <a href={2}>GitHub Pages</a>".format( repository, tracker, homepage, version, self.tr("Source code"), self.tr("Report issues"), self.tr("Documentation"), ), ) bogus.deleteLater() def show_settings(self): """ Show the settings dialog """ geometry = self.settings_dialog.geometry() # The first time the dialog is shown (y=0), explicitely set its geometry # which allow to restore the geometry on subsequent calls if geometry.y() == 0: self.settings_dialog.show() self.settings_dialog.raise_() self.settings_dialog.setGeometry(self.settings_dialog.geometry()) return self.settings_dialog.show() self.settings_dialog.raise_() def on_color_changed(self): """ Called when the selection color has changed. Replace every icon """ color = self.iface.mapCanvas().selectionColor() color = QColor.fromHsv(color.hue(), color.saturation() * 0.9, color.value() * 0.95, color.alpha()) for i in range(len(self.select_actions)): path = self.actions_settings[i].icon icon = create_icon(path, color) self.select_actions[i].setIcon(icon) icon = create_icon(":/plugins/multilayerselect/icons/deselectAll.svg", color) self.deselect_all_action.setIcon(icon) icon = select_all_icon(color) self.select_all_action.setIcon(icon) icon = invert_selection_icon(color) self.invert_all_action.setIcon(icon) icon = expression_select_icon(color) self.select_by_expr_action.setIcon(icon) def on_settings_changed(self): """ Called when any setting has changed """ if self.settings.value("show_settings", True, bool): self.toolbar.addAction(self.settings_action) else: self.toolbar.removeAction(self.settings_action) self.replace_default_action( self.settings.value("replace_actions", False, bool)) def deselect_all(self): """ Deselect every feature """ for layer in QgsProject.instance().mapLayers().values(): if isinstance(layer, QgsVectorLayer): layer.removeSelection() update_status_message() def select_all(self): """ Select all the features from every vector layer """ for layer in vector_layers(): layer.selectAll() self.advanced_selection_tool_button.setDefaultAction( self.select_all_action) if self.embedded_advanced_tool_button: self.embedded_advanced_tool_button.setDefaultAction( self.select_all_action) update_status_message() def invert_all(self): """ Invert the selection of every vector layer """ for layer in vector_layers(): layer.invertSelection() self.advanced_selection_tool_button.setDefaultAction( self.invert_all_action) if self.embedded_advanced_tool_button: self.embedded_advanced_tool_button.setDefaultAction( self.invert_all_action) update_status_message() def select_by_expression(self): """ Create and open the Expression builder dialog""" if self.expression_dialog: self.expression_dialog.deleteLater() self.expression_dialog = MultiLayerSelectionExpressionBuilder() self.expression_dialog.show() self.advanced_selection_tool_button.setDefaultAction( self.select_by_expr_action) if self.embedded_advanced_tool_button: self.embedded_advanced_tool_button.setDefaultAction( self.select_by_expr_action) update_status_message() def replace_default_action(self, value): """Replace the default QGIS selection action with the multilayer ones Args: value (bool): If true, replace the actions, else put the multi actions inside their own toolbar """ toolbar = self.iface.attributesToolBar() main_window = self.iface.mainWindow() main_window.findChild(QAction, "ActionSelect").setVisible(not value) main_window.findChild(QAction, "ActionSelection").setVisible(not value) main_window.findChild(QAction, "mActionDeselectAll").setVisible(not value) actiontable = main_window.findChild(QAction, "mActionOpenTable") actionform = main_window.findChild(QAction, "mActionSelectByForm") # Remove the multi layer tool buttons from the QGIS attribute toolbar toolbar.removeAction(self.embedded_selection_tool_button_action) toolbar.removeAction(self.embedded_advanced_tool_button_action) if value: # Create the QToolButtons that will be added to the default toolbar self.embedded_selection_tool_button = QToolButton() self.embedded_selection_tool_button.setPopupMode( QToolButton.MenuButtonPopup) # Add selection tools action to the button (Rect, Polygon, Radius, Freehand) self.embedded_selection_tool_button.addActions(self.select_actions) self.embedded_selection_tool_button.setDefaultAction( self.select_actions[0]) self.embedded_advanced_tool_button = QToolButton() self.embedded_advanced_tool_button.setPopupMode( QToolButton.MenuButtonPopup) # Add Invert, Select All, Select from value and Select from expressions self.embedded_advanced_tool_button.addAction( self.select_all_action) self.embedded_advanced_tool_button.setDefaultAction( self.select_all_action) self.embedded_advanced_tool_button.addAction( self.invert_all_action) self.embedded_advanced_tool_button.addAction( self.select_by_expr_action) self.embedded_advanced_tool_button.addAction(actionform) self.embedded_selection_tool_button_action = toolbar.insertWidget( actiontable, self.embedded_selection_tool_button) self.embedded_advanced_tool_button_action = toolbar.insertWidget( actiontable, self.embedded_advanced_tool_button) # Add the deselect all action toolbar.insertAction(actiontable, self.deselect_all_action) # If the settigns is enabled add the show settings action if self.settings.value("show_settings", True, bool): toolbar.insertAction(actiontable, self.settings_action) else: toolbar.removeAction(self.settings_action) self.toolbar.hide() else: # Remove the multi actions from the default toolbar, and show # the custom toolbar self.embedded_selection_tool_button = None self.embedded_advanced_tool_button = None toolbar.removeAction(self.deselect_all_action) toolbar.removeAction(self.settings_action) self.toolbar.show()
class InfoDialog(QDialog): def __init__(self, app: dict, icon_cache: MemoryCache, i18n: dict, screen_size: QSize()): super(InfoDialog, self).__init__() self.setWindowTitle(str(app['__app__'])) self.screen_size = screen_size self.i18n = i18n layout = QVBoxLayout() self.setLayout(layout) self.toolbar_field = QToolBar() self.bt_back = QPushButton(i18n['back'].capitalize()) self.bt_back.clicked.connect(self.back_to_info) self.toolbar_field.addWidget(self.bt_back) self.layout().addWidget(self.toolbar_field) self.toolbar_field.hide() # shows complete field string self.text_field = QPlainTextEdit() self.layout().addWidget(self.text_field) self.text_field.hide() self.gbox_info = QGroupBox() self.gbox_info.setMaximumHeight(self.screen_size.height() - self.screen_size.height() * 0.1) self.gbox_info_layout = QGridLayout() self.gbox_info.setLayout(self.gbox_info_layout) layout.addWidget(self.gbox_info) # THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED # # icon_data = icon_cache.get(app['__app__'].model.icon_url) # # if icon_data and icon_data.get('icon'): # self.setWindowIcon(icon_data.get('icon')) self.setWindowIcon(QIcon(app['__app__'].model.get_type_icon_path())) for idx, attr in enumerate(sorted(app.keys())): if attr not in IGNORED_ATTRS and app[attr]: i18n_key = app['__app__'].model.get_type().lower( ) + '.info.' + attr.lower() if isinstance(app[attr], list): val = ' '.join([str(e).strip() for e in app[attr] if e]) show_val = '\n'.join( ['* ' + str(e).strip() for e in app[attr] if e]) else: val = str(app[attr]).strip() show_val = val i18n_val = i18n.get('{}.{}'.format(i18n_key, val.lower())) if i18n_val: val = i18n_val show_val = val text = QLineEdit() text.setToolTip(show_val) text.setText(val) text.setCursorPosition(0) text.setStyleSheet("width: 400px") text.setReadOnly(True) label = QLabel( i18n.get(i18n_key, i18n.get(attr.lower(), attr)).capitalize()) label.setStyleSheet("font-weight: bold") self.gbox_info_layout.addWidget(label, idx, 0) self.gbox_info_layout.addWidget(text, idx, 1) self._gen_show_button(idx, show_val) self.adjustSize() def _gen_show_button(self, idx: int, val): def show_full_field(): self.gbox_info.hide() self.toolbar_field.show() self.text_field.show() self.text_field.setPlainText(val) bt_full_field = QPushButton(self.i18n['show'].capitalize()) bt_full_field.clicked.connect(show_full_field) self.gbox_info_layout.addWidget(bt_full_field, idx, 2) def back_to_info(self): self.text_field.setPlainText("") self.text_field.hide() self.toolbar_field.hide() self.gbox_info.show()
class ImageViewer(QMainWindow): def __init__(self, db): super(ImageViewer, self).__init__() self.db = db self.currentIndex = -1 self.files = [] self._init_widgets() def _init_widgets(self): #self.toolbar = AutoHideToolBar() self.toolbar = QToolBar() self.addToolBar(self.toolbar) self.toolbar.hide() self.toolbar.addAction('Z 1:1').triggered.connect(self.doNormalZoom) self.toolbar.addAction('Z Fit').triggered.connect(self.doFitAllZoom) self.toolbar.addAction('Z FitExp').triggered.connect(self.doFitCutZoom) self.toolbar.addAction('Z x1.5').triggered.connect(self.zoom) self.toolbar.addAction('Z /1.5').triggered.connect(self.unzoom) self.toolbar.addAction('Copy tags').triggered.connect(self.copyPreviousTags) self.fullscreenAction = self.toolbar.addAction('Fullscreen') self.fullscreenAction.setCheckable(True) self.fullscreenAction.toggled.connect(self.setFullscreen) act = self.toolbar.addAction('Prev') act.setShortcut(QKeySequence(Qt.Key_Backspace)) act.triggered.connect(self.showPreviousFile) act = self.toolbar.addAction('Next') act.setShortcut(QKeySequence(Qt.Key_Space)) act.triggered.connect(self.showNextFile) #~ act.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.tageditor = TagEditor(self.db) self.docktagger = AutoHideDock() self.docktagger.setWidget(self.tageditor) self.addDockWidget(Qt.LeftDockWidgetArea, self.docktagger) self.docktagger.hide() self.scrollview = ImageViewerCenter() self.scrollview.installEventFilter(self) ### ! self.setCentralWidget(self.scrollview) self.scrollview.topZoneEntered.connect(self.toolbar.show) self.scrollview.topZoneLeft.connect(self.toolbar.hide) self.scrollview.leftZoneEntered.connect(self.docktagger.show) self.scrollview.leftZoneLeft.connect(self.docktagger.hide) #~ self.setWindowState(self.windowState() | Qt.WindowMaximized) ''' self.qtagwl = QListWidget() self.qtagwl.setParent(self) self.qtagwl.hide() #self.qtagwl.setFixedSize(self.qtagwl.minimumSizeHint()) self.qtagwl.setFrameShape(QFrame.NoFrame) self.qtagwl.setStyleSheet('QListWidget{background-color: rgba(255,255,255,200);}\n *{background-color:rgba(0,255,255,255);}') self.qtagwl.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.qthtimer = QTimer() self.connect(self.qthtimer, SIGNAL('timeout()'), self.qtaghide) ''' def eventFilter(self, sview, ev): if ev.type() == QEvent.KeyPress: if ev.key() == Qt.Key_Escape: self.fullscreenAction.setChecked(False) return True elif ev.key() in [Qt.Key_PageUp, Qt.Key_Backspace]: # qactions self.showPreviousFile() return True elif ev.key() in [Qt.Key_PageDown, Qt.Key_Space]: self.showNextFile() return True return super(ImageViewer, self).eventFilter(sview, ev) @Slot() def doNormalZoom(self): self.scrollview.setZoomFactor(1.) @Slot() def doFitAllZoom(self): self.scrollview.setZoomMode(ZOOM_FITALL) @Slot() def doFitCutZoom(self): self.scrollview.setZoomMode(ZOOM_FITCUT) @Slot() def zoom(self): self.scrollview.multiplyZoomFactor(1.5) @Slot() def unzoom(self): self.scrollview.multiplyZoomFactor(1/1.5) def spawn(self, files, currentFile): self.files = files self.currentIndex = files.index(currentFile) self.setFile(currentFile) if self.isHidden(): #~ self.setWindowState(self.windowState() | Qt.WindowMaximized) #~ self.show() self.fullscreenAction.setChecked(False) self.fullscreenAction.setChecked(True) #~ self.showMaximized() else: self.show() def setFile(self, file): self.tageditor.setFile(file) self.scrollview.setFile(file) @Slot() def copyPreviousTags(self): tags = self.db.find_tags_by_file(self.files[self.currentIndex - 1]) self.db.tag_file(self.files[self.currentIndex], tags) self.tageditor.setFile(self.files[self.currentIndex]) def setFullscreen(self, full): if full: self.showFullScreen() else: self.showNormal() @Slot() def showPreviousFile(self): if self.currentIndex > 0: self.currentIndex -= 1 self.setFile(self.files[self.currentIndex]) @Slot() def showNextFile(self): if self.currentIndex < len(self.files) - 1: self.currentIndex += 1 self.setFile(self.files[self.currentIndex])
class ImageViewer(QMainWindow): def __init__(self, db, *args, **kwargs): super(ImageViewer, self).__init__(*args, **kwargs) self.db = db self.currentIndex = -1 self.files = [] self._init_widgets() def _init_widgets(self): #self.toolbar = AutoHideToolBar() self.toolbar = QToolBar() self.addToolBar(self.toolbar) self.toolbar.hide() act = self.toolbar.addAction(QIcon.fromTheme('go-previous'), 'Previous') act.setShortcut(QKeySequence(Qt.Key_Backspace)) act.triggered.connect(self.showPreviousFile) act = self.toolbar.addAction(QIcon.fromTheme('go-next'), 'Next') act.setShortcut(QKeySequence(Qt.Key_Space)) act.triggered.connect(self.showNextFile) self.toolbar.addSeparator() self.toolbar.addAction(QIcon.fromTheme('zoom-original'), 'Z 1:1').triggered.connect(self.doNormalZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-fit-best'), 'Z Fit').triggered.connect(self.doFitAllZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-fit-best'), 'Z FitExp').triggered.connect(self.doFitCutZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-in'), 'Z x1.5').triggered.connect(self.zoom) self.toolbar.addAction(QIcon.fromTheme('zoom-out'), 'Z /1.5').triggered.connect(self.unzoom) self.fullscreenAction = self.toolbar.addAction(QIcon.fromTheme('view-fullscreen'), 'Fullscreen') self.fullscreenAction.setCheckable(True) self.fullscreenAction.toggled.connect(self.setFullscreen) self.toolbar.addSeparator() self.toolbar.addAction('Copy tags').triggered.connect(self.copyPreviousTags) self.tageditor = TagEditor() self.tageditor.setDb(self.db) self.docktagger = AutoHideDock() self.docktagger.setWidget(self.tageditor) self.addDockWidget(Qt.LeftDockWidgetArea, self.docktagger) self.docktagger.hide() self.scrollview = ImageViewerCenter() self.scrollview.installEventFilter(self) ### ! self.setCentralWidget(self.scrollview) self.scrollview.topZoneEntered.connect(self.toolbar.show) self.scrollview.topZoneLeft.connect(self.toolbar.hide) self.scrollview.leftZoneEntered.connect(self.docktagger.show) self.scrollview.leftZoneLeft.connect(self.docktagger.hide) def eventFilter(self, sview, ev): if ev.type() == QEvent.KeyPress: if ev.key() == Qt.Key_Escape: self.fullscreenAction.setChecked(False) return True elif ev.key() in [Qt.Key_PageUp, Qt.Key_Backspace]: # qactions self.showPreviousFile() return True elif ev.key() in [Qt.Key_PageDown, Qt.Key_Space]: self.showNextFile() return True return super(ImageViewer, self).eventFilter(sview, ev) @Slot() def doNormalZoom(self): self.scrollview.setZoomFactor(1.) @Slot() def doFitAllZoom(self): self.scrollview.setZoomMode(ZOOM_FITALL) @Slot() def doFitCutZoom(self): self.scrollview.setZoomMode(ZOOM_FITCUT) @Slot() def zoom(self): self.scrollview.multiplyZoomFactor(1.5) @Slot() def unzoom(self): self.scrollview.multiplyZoomFactor(1 / 1.5) def spawn(self, files, currentFile): self.files = files self.currentIndex = files.index(currentFile) self.setFile(currentFile) if self.isHidden(): #~ self.setWindowState(self.windowState() | Qt.WindowMaximized) #~ self.show() self.fullscreenAction.setChecked(False) self.fullscreenAction.setChecked(True) #~ self.showMaximized() else: self.show() def setFile(self, file): self.tageditor.setFile(file) self.scrollview.setFile(file) self.setWindowTitle(file) @Slot() def copyPreviousTags(self): tags = self.db.find_tags_by_file(self.files[self.currentIndex - 1]) with self.db: self.db.tag_file(self.files[self.currentIndex], tags) self.tageditor.setFile(self.files[self.currentIndex]) def setFullscreen(self, full): if full: self.showFullScreen() else: self.showNormal() @Slot() def showPreviousFile(self): if self.currentIndex > 0: self.currentIndex -= 1 self.setFile(self.files[self.currentIndex]) @Slot() def showNextFile(self): if self.currentIndex < len(self.files) - 1: self.currentIndex += 1 self.setFile(self.files[self.currentIndex])
class LineTrace(BaseGraph): def __init__(self, data: DataBuffer = None, parent=None): """ TODO: Fix documentation, i might have changed things and forgot to update documentation (axis_data) Inherits: BaseGraph() Used to display 2D type of graphs. Enables user to to transformations to 2D type of data (x, y) :param data: DataBuffer(): If DataBuffer is being opened, this is a reference to that data buffer :param axis_data [Optional]: If what is being open is a line trace created from Heatmap window, then there is no actual data buffer rather just x and y axis data """ print("Instantiating 2d window . . .") super().__init__() # Axis data exists when what is being opened is a line trace created by Heatmap window. In that case no # reference to data buffer is made. self.parent = parent self.data_buffer = data self.x_values = self.data_buffer.data["x"] self.y_values = self.data_buffer.data["y"] self.active_data_set = self.y_values[0] self.displayed_data_set = self.active_data_set self.title = self.data_buffer.name # indicates in which modes the window is currently working. self.modes = {"fit": False} # used in fit mode of this window, holds data about created fit curves to enable hiding and displaying them at # any point self.fit_curves = {} for index, data in enumerate(self.y_values): self.fit_curves[index] = {} self.init_ui() """ # ########################################### # ############ USER INTERFACE ############### # ########################################### """ def init_ui(self): """ Method that builds user interface :return: """ print("Creating 2d window . . .") # set dimensions, title and icon of the window self.setGeometry(50, 50, 640, 400) self.setWindowTitle("Line trace window") self.setWindowIcon(QIcon("../img/lineGraph.png")) # Elements for ploting data self.plt = pg.GraphicsView() self.central_item = pg.GraphicsLayout() self.main_subplot = pg.PlotItem(x=self.x_values, y=self.active_data_set, pen=(60, 60, 60), title=self.title) self.fit_plot = pg.PlotItem(pen=(60, 60, 60)) self.fit_plot.sigRangeChanged.connect(self.update_region_area) for plot_item in [self.main_subplot, self.fit_plot]: plot_item.layout.removeItem(plot_item.getAxis("top")) if "extra_axis" in self.data_buffer.data: extra_view_box = pg.ViewBox() extra_axis = pg.AxisItem("top") extra_axis.setPen((60, 60, 60)) axis_data = self.data_buffer.axis_values["extra_axis"] label_style = {'font-size': '9pt'} extra_axis.setLabel(axis_data["name"], axis_data["unit"], **label_style) plot_item.layout.addItem(extra_axis, 0, 1) extra_axis.linkToView(extra_view_box) extra_view_box.setXLink(plot_item.vb) if plot_item == self.main_subplot: extra_view_box_main = extra_view_box else: extra_view_box_fit = extra_view_box self.setCentralWidget(self.plt) # set the color of the background and set the central widget. All other elements are added to this central # widget, which makes them resizable and scalable with the window size. self.plt.setBackground("w") self.plt.setCentralWidget(self.central_item) print("Creating axis elements . . .") legend = {"left": "y", "bottom": "x", "top": "extra_axis"} self.central_item.addItem(self.main_subplot, colspan=2) self.central_item.nextRow() self.central_item.addItem(self.fit_plot) self.fit_plot.hide() self.plot_elements = { "main_subplot": self.main_subplot, "fit_plot": self.fit_plot } if "extra_axis" in self.data_buffer.data: self.plot_elements["extra_view_box_main"] = extra_view_box_main self.plot_elements["extra_view_box_fit"] = extra_view_box_fit # connect the range changed signal of main subplot to a method that updates the range of the extra axis. self.main_subplot.sigRangeChanged.connect(self.update_extra_axis_main) self.fit_plot.sigRangeChanged.connect(self.update_extra_axis_fit) print("Configuring axis data . . .") self.update_axis_labels() self.init_toolbar() self.show() def init_toolbar(self): """ Create toolbar of the line trace window :return: NoneType """ print("Creating toolbar . . .") self.tools = self.addToolBar("Tools") self.tools.actionTriggered[QAction].connect(self.perform_action) print("Filling selection combobox . . .") self.data_selection_combobox = QComboBox() for index, data in enumerate(self.data_buffer.data["y"]): display_member = "param{}".format(index) value_member = data self.data_selection_combobox.addItem(display_member, value_member) self.data_selection_combobox.currentIndexChanged.connect( lambda: self.change_active_set(index=self.data_selection_combobox. currentIndex())) self.tools.addWidget(self.data_selection_combobox) print("Adding actions . . .") self.toggle_fit_mode = QAction(QIcon("img/fit_curve_icon.png"), "Fit_mode", self) self.tools.addAction(self.toggle_fit_mode) self.toggle_fit_mode.setCheckable(True) self.window_toolbar = QToolBar("Window toolbar") self.window_toolbar.actionTriggered[QAction].connect( self.perform_action) self.addToolBar(self.window_toolbar) self.customize_font_btn = QAction(QIcon("img/editFontIcon.png"), "Font", self) self.customize_font_btn.setToolTip( "Open a widget that allows user to customise font and axis values") self.window_toolbar.addAction(self.customize_font_btn) self.exit_action_Btn = QAction(QIcon("img/closeIcon.png"), "Exit", self) self.window_toolbar.addAction(self.exit_action_Btn) self.init_fit_toolbar() def init_fit_toolbar(self): """ Additional toolbar that is displayed when window is operating in "fit" mode :return: NoneType """ print("Initialising fit toolbar . . .") self.fit_toolbar = QToolBar("Fitting options") self.fit_toolbar.actionTriggered[QAction].connect(self.perform_action) self.addToolBar(Qt.RightToolBarArea, self.fit_toolbar) self.fit_toolbar.hide() self.default_graph = QAction(QIcon("img/noneIcon.png"), "Default_graph", self) self.default_graph.setCheckable(True) self.default_graph.setChecked(True) self.fit_toolbar.addAction(self.default_graph) self.linear = QAction(QIcon("img/linear_fit_icon.png"), "Linear_fit", self) self.linear.setCheckable(True) self.fit_toolbar.addAction(self.linear) self.gauss = QAction(QIcon("img/gaussianIcon.png"), "Gaussian_fit", self) self.gauss.setCheckable(True) self.fit_toolbar.addAction(self.gauss) self.sinus = QAction(QIcon("img/sinusIcon.png"), "Sinus_fit", self) self.sinus.setCheckable(True) self.fit_toolbar.addAction(self.sinus) def define_plot_parameters(self): pass """ # ########################################### # ################# HELPERS ################# # ########################################### """ def get_selection_area(self): """ This method gets data points on x axis that are inside of the area selected by LinearRegionItem (self.region_select). It then also selects coresponding data points on y axis and returns two numpy arrays containing x and y data points. :return: np.array, np.array: data points on x and y axis that are inside the selected area """ min_x, max_x = self.region_select.getRegion() x = [] y = [] for i, value in enumerate(self.x_values): if value >= min_x and value <= max_x: x.append(value) y.append(self.active_data_set[i]) x = np.array(x) y = np.array(y) return x, y def change_active_set(self, index): """ TODO: Write documentation :param index: :return: """ data = self.y_values[index] self.active_data_set = data self.change_displayed_data_set(data, index) def change_displayed_data_set(self, data_set, index): """ TODO: Write documentation :param data_set: :param index: :return: """ self.displayed_data_set = data_set self.plot_elements["main_subplot"].clear() self.plot_elements["main_subplot"].plot(self.x_values, self.active_data_set, pen=(60, 60, 60)) self.plot_elements["fit_plot"].clear() self.plot_elements["fit_plot"].plot(self.x_values, self.active_data_set) self.update_axis_labels(index) if self.modes["fit"]: self.modes["fit"] = False self.fit_mode_action() """ # ########################################### # ############### ACTIONS ################### # ########################################### """ def fit_mode_action(self): """ TODO: This might have a memory leak, i keep creating new LinerRegionItems. Probably should reuse the old one Called when user clicks on an action that activates fit mode of the window. Shows / hides additional toolbar used to fit different kinds of curves to the displayed data. :return: NoneType """ if self.modes["fit"]: self.modes["fit"] = False self.fit_toolbar.hide() self.main_subplot.removeItem(self.region_select) del self.region_select self.fit_plot.hide() self.fit_plot.clear() else: self.modes["fit"] = True self.fit_toolbar.show() self.region_select = pg.LinearRegionItem([0, 1]) self.region_select.sigRegionChanged.connect( self.update_selected_region) self.region_select.setZValue(10) self.region_select.setRegion([self.x_values[0], self.x_values[-1]]) self.main_subplot.addItem(self.region_select, ignoreBounds=True) self.set_initial_fit_graph_state() self.fit_plot.show() # add only selected data to graph def set_initial_fit_graph_state(self): """ Default state of the fit mode displays only mirrored source of the data. Other fits need to be manually turned on for them to be displayed. :return: NoneType """ self.fit_plot.clear() plot_item = pg.PlotDataItem(self.x_values, self.active_data_set) self.fit_plot.addItem(plot_item) self.fit_curves["default"] = plot_item # Set button states as they should be self.default_graph.setChecked(True) self.gauss.setChecked(False) def font_action(self): """ Opens a new widget that allows user to modify font sizes on axis of all graphs in this window :return: NoneType """ self.eaw = Edit2DAxisWidget(self) self.eaw.submitted.connect(self.edit_axis_data) self.eaw.show() # ############################### # ############ FITS ############# # ############################### def default_graph_action(self): """ Mirror the source data of the window to the graph for fits. Turning off this action removes the curve from graph for displaying fit curves. :return: NoneType """ if self.default_graph.isChecked(): x = self.x_values y = self.active_data_set plot_item = pg.PlotDataItem(x, y) self.fit_plot.addItem(plot_item) self.fit_curves["default"] = plot_item else: self.fit_plot.removeItem(self.fit_curves["default"]) def gaussian_fit_action(self): """ As suggested by the name of the function, it tries to apply gaussian fit the the selected data. Data is then added and displayed in the graph for fir curves. Turning off the action that prompts this function removes curve from the graph :return: """ def gauss(x, a, x0, sigma): """ :param x: :param a: :param x0: :param sigma: :return: """ return a * exp(-(x - x0)**2 / (2 * sigma**2)) if self.gauss.isChecked(): try: x, y = self.get_selection_area() mean = sum(x * y) / sum( y ) # https://en.wikipedia.org/wiki/Weighted_arithmetic_mean sigma = np.sqrt(sum(y * (x - mean)**2) / sum(y)) p0 = array([max(y), mean, sigma]) popt, pcov = curve_fit(gauss, x, y, p0=p0) plot_item = pg.PlotDataItem(x, gauss(x, *popt)) plot_item.setPen(255, 0, 0) self.fit_plot.addItem(plot_item) self.fit_curves["gauss"] = plot_item except Exception as e: print(str(e)) show_error_message("Could not find fit", str(e)) else: self.fit_plot.removeItem(self.fit_curves["gauss"]) del self.fit_curves["gauss"] def sinus_fit_action(self): """ As suggested by the name of the function, it tries to apply sinus fit the the selected data. Data is then added and displayed in the graph for fir curves. Turning off the action that prompts this function removes curve from the graph :return: """ def sin(x, freq, amplitude, phase, offset): """ :param x: :param freq: :param amplitude: :param phase: :param offset: :return: """ return np.sin(x * freq + phase) * amplitude + offset if self.sinus.isChecked(): x, y = self.get_selection_area() guess_freq = 1 guess_amplitude = 3 * np.std(y) / (2**0.5) guess_phase = 0 guess_offset = np.mean(y) p0 = array( [guess_freq, guess_amplitude, guess_phase, guess_offset]) fit = curve_fit(sin, x, y, p0=p0) fit_data = sin(x, *fit[0]) plot_item = pg.PlotDataItem(x, fit_data) plot_item.setPen(0, 0, 255) self.fit_plot.addItem(plot_item) self.fit_curves["sinus"] = plot_item else: self.fit_plot.removeItem(self.fit_curves["sinus"]) del self.fit_curves["sinus"] def linear_fit_action(self): """ :return: """ def linear(a, b, k): """ :param a: :param b: :param k: :return: """ return a * k + b if self.linear.isChecked(): x, y = self.get_selection_area() initial_guess = np.polyfit(x, y, 1) fit = curve_fit(linear, x, y, initial_guess) fit_data = linear(x, *fit[0]) plot_item = pg.PlotDataItem(x, fit_data) plot_item.setPen(0, 255, 0) self.fit_plot.addItem(plot_item) self.fit_curves["linear"] = plot_item else: self.fit_plot.removeItem(self.fit_curves["linear"]) del self.fit_curves["linear"] """ # ########################################### # ################# Helpers ################# # ########################################### """ def update_axis_labels(self, y_index=0): legend = {"left": "y", "bottom": "x", "top": "extra_axis"} for plot_item in [self.main_subplot, self.fit_plot]: for axis in ['left', 'bottom']: pi = plot_item ax = pi.getAxis(axis) ax.setPen((60, 60, 60)) axis_data = self.data_buffer.axis_values[legend[axis]] label_style = {'font-size': '10pt'} if axis == "left": ax.setLabel(axis_data[y_index]["name"], axis_data[y_index]["unit"], **label_style) else: ax.setLabel(axis_data["name"], axis_data["unit"], **label_style) """ # ########################################### # ################## EVENTS ################# # ########################################### """ def update_selected_region(self): """ Event that upon changing bounds of region select element changes limits of the fit graph in a way that it only displays the selected portion of the data. :return: NoneType """ min_x, max_x = self.region_select.getRegion() self.fit_plot.setXRange(min_x, max_x, padding=0) return def update_region_area(self, element, view_range): """ Method that updates region select when fit graph axis changes range. :param element: pyqtgraph element that sent the signal :param view_range: range on x and y axis :return: NoneType """ min_x, max_x = view_range[0] self.region_select.setRegion([min_x, max_x]) return def edit_axis_data(self, data): """ Method that applies the changes to axis (font, labels, ...) :param data: data passed trough the signal. It contains user input (values to apply to font/label/...) :return: NoneType """ for element, sides in data.items(): for side, options in sides.items(): if side != "top": axis = self.plot_elements[element].getAxis(side) axis.setLabel(options["name"], options["unit"], **options["label_style"]) if options["ticks"]["font"] != "": font = QFont() font.setPixelSize(int(options["ticks"]["font"])) axis.tickFont = font axis.setStyle(tickTextOffset=int( int(options["ticks"]["font"]) / 2)) return def update_extra_axis_fit(self): """ :return: """ self.update_extra_axis(self.fit_plot.vb, self.plot_elements["extra_view_box_fit"]) def update_extra_axis_main(self): """ :return: """ self.update_extra_axis(self.main_subplot.vb, self.plot_elements["extra_view_box_main"]) def update_extra_axis(self, view_box, extra_view_box): """ :return: """ point1 = self.parent.line_segment_roi["ROI"].getSceneHandlePositions(0) _, scene_coords = point1 start_coords = self.parent.line_segment_roi["ROI"].mapSceneToParent( scene_coords) point2 = self.parent.line_segment_roi["ROI"].getSceneHandlePositions(1) _, scene_coords = point2 end_coords = self.parent.line_segment_roi["ROI"].mapSceneToParent( scene_coords) x_diff = end_coords.x() - start_coords.x() y_diff = end_coords.y() - start_coords.y() angle = atan2(y_diff, x_diff) angle_tan = tan(angle) start_value_x_axis = view_box.state["viewRange"][0][0] end_value_x_axis = view_box.state["viewRange"][0][1] # padding = view_box.suggestPadding(0) if (start_coords.y() > start_value_x_axis) and (end_coords.y() < end_value_x_axis): delta_x_start = start_coords.y() - start_value_x_axis delta_y_start = delta_x_start / angle_tan delta_x_end = end_value_x_axis - end_coords.y() delta_y_end = delta_x_end / angle_tan start_value_extra_axis = start_coords.x() - delta_y_start end_value_extra_axis = end_coords.x() + delta_y_end # Option 2: starting point has been dragged outside of the visible range on line trace graph elif (start_coords.y() < start_value_x_axis) and (end_coords.y() < end_value_x_axis): delta_x_start = start_value_x_axis - start_coords.y() delta_y_start = delta_x_start / angle_tan delta_x_end = end_value_x_axis - end_coords.y() delta_y_end = delta_x_end / angle_tan start_value_extra_axis = start_coords.x() + delta_y_start end_value_extra_axis = end_coords.x() + delta_y_end # Option 3: ending point has been draged outside of the visible range of line trace graph elif (start_coords.y() > start_value_x_axis) and (end_coords.y() > end_value_x_axis): delta_x_start = start_coords.y() - start_value_x_axis delta_y_start = delta_x_start / angle_tan delta_x_end = end_coords.y() - end_value_x_axis delta_y_end = delta_x_end / angle_tan start_value_extra_axis = start_coords.x() - delta_y_start end_value_extra_axis = end_coords.x() - delta_y_end # Option 4: graph has been zoomed in and neither starting or ending point are visible on the line trace # graph else: delta_x_start = start_value_x_axis - start_coords.y() delta_y_start = delta_x_start / angle_tan delta_x_end = end_coords.y() - end_value_x_axis delta_y_end = delta_x_end / angle_tan start_value_extra_axis = start_coords.x() + delta_y_start end_value_extra_axis = end_coords.x() - delta_y_end extra_view_box.setXRange(start_value_extra_axis, end_value_extra_axis, padding=0)