def _view_toolbar(self, toolbar: QToolBar, viewToolbarAction: QAction): if toolbar.isVisible(): viewToolbarAction.setText(self.tr("Show Toolbar")) toolbar.close() else: viewToolbarAction.setText(self.tr("Hide Toolbar")) toolbar.show()
class PreparePanel(QWidget, TaskManager): signal_status = pyqtSignal(object, object) signal_password_response = pyqtSignal(str, bool) def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize, i18n: I18n, manage_window: QWidget): super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint) self.i18n = i18n self.context = context self.manage_window = manage_window self.setWindowTitle('{} ({})'.format( __app_name__, self.i18n['prepare_panel.title.start'].lower())) self.setMinimumWidth(screen_size.width() * 0.5) self.setMinimumHeight(screen_size.height() * 0.35) self.setMaximumHeight(screen_size.height() * 0.95) self.setLayout(QVBoxLayout()) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.manager = manager self.tasks = {} self.output = {} self.ntasks = 0 self.ftasks = 0 self.self_close = False self.prepare_thread = Prepare(self.context, manager, self.i18n) self.prepare_thread.signal_register.connect(self.register_task) self.prepare_thread.signal_update.connect(self.update_progress) self.prepare_thread.signal_finished.connect(self.finish_task) self.prepare_thread.signal_started.connect(self.start) self.prepare_thread.signal_ask_password.connect(self.ask_root_password) self.prepare_thread.signal_output.connect(self.update_output) self.signal_password_response.connect( self.prepare_thread.set_password_reply) self.check_thread = CheckFinished() self.signal_status.connect(self.check_thread.update) self.check_thread.signal_finished.connect(self.finish) self.skip_thread = EnableSkip() self.skip_thread.signal_timeout.connect(self._enable_skip_button) self.progress_thread = AnimateProgress() self.progress_thread.signal_change.connect(self._change_progress) self.label_top = QLabel() self.label_top.setCursor(QCursor(Qt.WaitCursor)) self.label_top.setText("{}...".format( self.i18n['prepare_panel.title.start'].capitalize())) self.label_top.setAlignment(Qt.AlignHCenter) self.label_top.setStyleSheet( "QLabel { font-size: 14px; font-weight: bold; }") self.layout().addWidget(self.label_top) self.layout().addWidget(QLabel()) self.table = QTableWidget() self.table.setCursor(QCursor(Qt.WaitCursor)) self.table.setStyleSheet( "QTableWidget { background-color: transparent; }") self.table.setFocusPolicy(Qt.NoFocus) self.table.setShowGrid(False) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setVisible(False) self.table.horizontalHeader().setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.table.setColumnCount(4) self.table.setHorizontalHeaderLabels(['' for _ in range(4)]) self.layout().addWidget(self.table) self.textarea_output = QPlainTextEdit(self) self.textarea_output.resize(self.table.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.textarea_output.setMaximumHeight(100) self.current_output_task = None self.bottom_widget = QWidget() self.bottom_widget.setLayout(QHBoxLayout()) self.bottom_widget.layout().addStretch() bt_hide_output = QPushButton(self.i18n['prepare.bt_hide_details']) bt_hide_output.setStyleSheet( 'QPushButton { text-decoration: underline; border: 0px; background: none } ' ) bt_hide_output.clicked.connect(self.hide_output) bt_hide_output.setCursor(QCursor(Qt.PointingHandCursor)) self.bottom_widget.layout().addWidget(bt_hide_output) self.bottom_widget.layout().addStretch() self.layout().addWidget(self.bottom_widget) self.bottom_widget.setVisible(False) self.bt_bar = QToolBar() self.bt_close = QPushButton(self.i18n['close'].capitalize()) self.bt_close.setCursor(QCursor(Qt.PointingHandCursor)) self.bt_close.clicked.connect(self.close) self.bt_close.setVisible(False) self.ref_bt_close = self.bt_bar.addWidget(self.bt_close) self.bt_bar.addWidget(new_spacer()) self.progress_bar = QProgressBar() self.progress_bar.setStyleSheet(styles.PROGRESS_BAR) self.progress_bar.setMaximumHeight(10 if QApplication.instance().style( ).objectName().lower() == 'windows' else 4) self.progress_bar.setTextVisible(False) self.progress_bar.setVisible(False) self.progress_bar.setCursor(QCursor(Qt.WaitCursor)) self.ref_progress_bar = self.bt_bar.addWidget(self.progress_bar) self.bt_bar.addWidget(new_spacer()) self.bt_skip = QPushButton( self.i18n['prepare_panel.bt_skip.label'].capitalize()) self.bt_skip.clicked.connect(self.finish) self.bt_skip.setEnabled(False) self.bt_skip.setCursor(QCursor(Qt.WaitCursor)) self.bt_bar.addWidget(self.bt_skip) self.layout().addWidget(self.bt_bar) def hide_output(self): self.current_output_task = None self.textarea_output.setVisible(False) self.textarea_output.clear() self.bottom_widget.setVisible(False) self._resize_columns() self.setFocus(Qt.NoFocusReason) if not self.bt_bar.isVisible(): self.bt_bar.setVisible(True) def ask_root_password(self): root_pwd, ok = root.ask_root_password(self.context, self.i18n) self.signal_password_response.emit(root_pwd, ok) def _enable_skip_button(self): self.bt_skip.setEnabled(True) self.bt_skip.setCursor(QCursor(Qt.PointingHandCursor)) def _change_progress(self, value: int): self.progress_bar.setValue(value) def get_table_width(self) -> int: return reduce(operator.add, [ self.table.columnWidth(i) for i in range(self.table.columnCount()) ]) def _resize_columns(self): header_horizontal = self.table.horizontalHeader() for i in range(self.table.columnCount()): header_horizontal.setSectionResizeMode( i, QHeaderView.ResizeToContents) self.resize(self.get_table_width() * 1.05, self.sizeHint().height()) def show(self): super(PreparePanel, self).show() self.prepare_thread.start() centralize(self) def start(self): self.ref_bt_close.setVisible(True) self.check_thread.start() self.skip_thread.start() self.ref_progress_bar.setVisible(True) self.progress_thread.start() def closeEvent(self, QCloseEvent): if not self.self_close: QCoreApplication.exit() def register_task(self, id_: str, label: str, icon_path: str): self.ntasks += 1 self.table.setRowCount(self.ntasks) task_row = self.ntasks - 1 icon_widget = QWidget() icon_widget.setLayout(QHBoxLayout()) icon_widget.layout().setContentsMargins(10, 0, 10, 0) bt_icon = QToolButton() bt_icon.setCursor(QCursor(Qt.WaitCursor)) bt_icon.setEnabled(False) bt_icon.setToolTip(self.i18n['prepare.bt_icon.no_output']) bt_icon.setFixedSize(QSize(24, 24)) bt_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) if icon_path: bt_icon.setIcon(QIcon(icon_path)) def _show_output(): lines = self.output[id_] if lines: self.current_output_task = id_ self.textarea_output.clear() self.textarea_output.setVisible(True) for l in lines: self.textarea_output.appendPlainText(l) self.bottom_widget.setVisible(True) self.setFocus(Qt.NoFocusReason) if self.bt_bar.isVisible(): self.bt_bar.setVisible(False) bt_icon.clicked.connect(_show_output) icon_widget.layout().addWidget(bt_icon) self.table.setCellWidget(task_row, 0, icon_widget) lb_status = QLabel(label) lb_status.setCursor(Qt.WaitCursor) lb_status.setMinimumWidth(50) lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) lb_status.setStyleSheet("QLabel { font-weight: bold; }") self.table.setCellWidget(task_row, 1, lb_status) lb_sub = QLabel() lb_status.setCursor(Qt.WaitCursor) lb_sub.setContentsMargins(10, 0, 10, 0) lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) lb_sub.setMinimumWidth(50) self.table.setCellWidget(task_row, 2, lb_sub) lb_progress = QLabel('{0:.2f}'.format(0) + '%') lb_progress.setCursor(Qt.WaitCursor) lb_progress.setContentsMargins(10, 0, 10, 0) lb_progress.setStyleSheet("QLabel { font-weight: bold; }") lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.table.setCellWidget(task_row, 3, lb_progress) self.tasks[id_] = { 'bt_icon': bt_icon, 'lb_status': lb_status, 'lb_prog': lb_progress, 'progress': 0, 'lb_sub': lb_sub, 'finished': False } self.signal_status.emit(self.ntasks, self.ftasks) def update_progress(self, task_id: str, progress: float, substatus: str): task = self.tasks[task_id] if progress != task['progress']: task['progress'] = progress task['lb_prog'].setText('{0:.2f}'.format(progress) + '%') if substatus: task['lb_sub'].setText('( {} )'.format(substatus)) else: task['lb_sub'].setText('') self._resize_columns() def update_output(self, task_id: str, output: str): full_output = self.output.get(task_id) if full_output is None: full_output = [] self.output[task_id] = full_output task = self.tasks[task_id] task['bt_icon'].setEnabled(True) task['bt_icon'].setCursor(QCursor(Qt.PointingHandCursor)) task['bt_icon'].setToolTip(self.i18n['prepare.bt_icon.output']) full_output.append(output) if self.current_output_task == task_id: self.textarea_output.appendPlainText(output) def finish_task(self, task_id: str): task = self.tasks[task_id] task['lb_sub'].setText('') for key in ('lb_prog', 'lb_status'): task[key].setStyleSheet( 'QLabel { color: %s; text-decoration: line-through; }' % GREEN) task['finished'] = True self._resize_columns() self.ftasks += 1 self.signal_status.emit(self.ntasks, self.ftasks) if self.ntasks == self.ftasks: self.label_top.setText(self.i18n['ready'].capitalize()) def finish(self): if self.isVisible(): self.manage_window.begin_refresh_packages() self.manage_window.show() self.self_close = True self.close()
class IDE(QMainWindow): """This class is like the Sauron's Ring: One ring to rule them all, One ring to find them, One ring to bring them all and in the darkness bind them. This Class knows all the containers, and its know by all the containers, but the containers don't need to know between each other, in this way we can keep a better api without the need to tie the behaviour between the widgets, and let them just consume the 'actions' they need.""" ############################################################################### # SIGNALS # # goingDown() ############################################################################### __IDESERVICES = {} __IDECONNECTIONS = {} __IDESHORTCUTS = {} __IDEBARCATEGORIES = {} __IDEMENUS = {} __IDETOOLBAR = {} # CONNECTIONS structure: # ({'target': service_name, 'signal_name': string, 'slot': function_obj},) # On modify add: {connected: True} __instance = None __created = False MessageStatusChanged = pyqtSignal(str) goingDown = pyqtSignal() # # ns_preferences_editor_font = pyqtSignal() # ns_preferences_editor_showTabsAndSpaces = pyqtSignal() # ns_preferences_editor_showIndentationGuide = pyqtSignal() # ns_preferences_editor_indent = pyqtSignal() # ns_preferences_editor_marginLine = pyqtSignal()#podría tener un argumento # ns_preferences_editor_showLineNumbers = pyqtSignal() # ns_preferences_editor_showMigrationTips = pyqtSignal() # ns_preferences_editor_checkStyle = pyqtSignal() # ns_preferences_editor_errors = pyqtSignal() # ds_lastSession_projects = pyqtSignal() # ds_lastSession_openedFiles = pyqtSignal() # ds_lastSession_currentFile = pyqtSignal() # ds_lastSession_recentFiles = pyqtSignal() # ns_preferences_editor_bookmarks = pyqtSignal() # ns_preferences_editor_breakpoints = pyqtSignal() # ns_window_maximized = pyqtSignal() # ns_preferences_general_loadFiles = pyqtSignal() # ns_preferences_general_activatePlugins = pyqtSignal() # ns_preferences_general_notifyUpdates = pyqtSignal() # ns_preferences_general_showStartPage = pyqtSignal(bool) # ns_preferences_general_confirmExit = pyqtSignal(bool) # ns_preferences_general_workspace = pyqtSignal() ns_preferences_general_supportedExtensions = pyqtSignal("QStringList") #ns_preferences_general_notification_position = pyqtSignal(int) #... ns_preferences_general_loadFiles = pyqtSignal(bool)# dato: 'True' ns_preferences_general_activatePlugins = pyqtSignal(bool)# dato: 'True' ns_preferences_general_notifyUpdates = pyqtSignal(bool)# dato: 'True' ns_preferences_general_showStartPage = pyqtSignal(bool)# dato: 'True' ns_preferences_general_confirmExit = pyqtSignal(bool)# dato: 'True' ns_preferences_general_workspace = pyqtSignal(str)# dato: '' #ns_preferences_general_supportedExtensions = pyqtSignal(list)# dato: '['.py', '.pyw', '.html', '.jpg','.png', '.ui', '.css', '.json', '.js', '.ini']' ns_preferences_general_notification_position = pyqtSignal(int)# dato: '0' ns_preferences_general_notification_color = pyqtSignal(str)# dato: '#000' ns_pythonPath = pyqtSignal(str)# dato: 'D:\Python34\python.exe' ns_executionOptions = pyqtSignal(str)# dato: '' ns_Show_Code_Nav = pyqtSignal(str)# dato: 'Ctrl+3' ns_Follow_mode = pyqtSignal(str)# dato: 'Ctrl+F10' ns_Change_Tab = pyqtSignal(str)# dato: 'Ctrl+PgDown' ns_Change_Tab_Reverse = pyqtSignal(str)# dato: 'Ctrl+PgUp' ns_Close_file = pyqtSignal(str)# dato: 'Ctrl+W' ns_Close_Split = pyqtSignal(str)# dato: 'Shift+F9' ns_Comment = pyqtSignal(str)# dato: 'Ctrl+G' ns_Complete_Declarations = pyqtSignal(str)# dato: 'Alt+Return' ns_copy = pyqtSignal(str)# dato: 'Ctrl+C' ns_History_Copy = pyqtSignal(str)# dato: 'Ctrl+Alt+C' ns_New_project = pyqtSignal(str)# dato: 'Ctrl+Shift+N' ns_New_file = pyqtSignal(str)# dato: 'Ctrl+N' ns_cut = pyqtSignal(str)# dato: 'Ctrl+X' ns_Debug = pyqtSignal(str)# dato: 'F7' ns_Duplicate = pyqtSignal(str)# dato: 'Ctrl+R' ns_Run_file = pyqtSignal(str)# dato: 'Ctrl+F6' ns_Run_project = pyqtSignal(str)# dato: 'F6' ns_expand_file_combo = pyqtSignal(str)# dato: 'Ctrl+Tab' ns_expand_symbol_combo = pyqtSignal(str)# dato: 'Ctrl+2' ns_Find = pyqtSignal(str)# dato: 'Ctrl+F' ns_Find_replace = pyqtSignal(str)# dato: 'Ctrl+H' ns_Find_in_files = pyqtSignal(str)# dato: 'Ctrl+L' ns_Find_next = pyqtSignal(str)# dato: 'Ctrl+F3' ns_Find_previous = pyqtSignal(str)# dato: 'Shift+F3' ns_Find_with_word = pyqtSignal(str)# dato: 'Ctrl+Shift+F' ns_Full_screen = pyqtSignal(str)# dato: 'Ctrl+F11' ns_Go_to_definition = pyqtSignal(str)# dato: 'Ctrl+Return' ns_Hide_all = pyqtSignal(str)# dato: 'F11' ns_Hide_editor = pyqtSignal(str)# dato: 'F3' ns_Hide_explorer = pyqtSignal(str)# dato: 'F2' ns_Hide_misc = pyqtSignal(str)# dato: 'F4' ns_Highlight_Word = pyqtSignal(str)# dato: 'Ctrl+Down' ns_Import = pyqtSignal(str)# dato: 'Ctrl+I' ns_Indent_less = pyqtSignal(str)# dato: 'Shift+Tab' ns_Indent_more = pyqtSignal(str)# dato: 'Tab' ns_Add_Bookmark_or_Breakpoint = pyqtSignal(str)# dato: 'Ctrl+B' ns_Title_comment = pyqtSignal(str)# dato: '' ns_Horizontal_line = pyqtSignal(str)# dato: '' ns_Move_down = pyqtSignal(str)# dato: 'Alt+Down' ns_Move_up = pyqtSignal(str)# dato: 'Alt+Up' ns_Move_Tab_to_left = pyqtSignal(str)# dato: 'Ctrl+Shift+9' ns_Move_Tab_to_right = pyqtSignal(str)# dato: 'Ctrl+Shift+0' ns_Navigate_back = pyqtSignal(str)# dato: 'Alt+Left' ns_Navigate_forward = pyqtSignal(str)# dato: 'Alt+Right' ns_Open_file = pyqtSignal(str)# dato: 'Ctrl+O' ns_Open_project = pyqtSignal(str)# dato: 'Ctrl+Shift+O' ns_Open_recent_closed = pyqtSignal(str)# dato: 'Ctrl+Shift+T' ns_paste = pyqtSignal(str)# dato: 'Ctrl+V' ns_History_Paste = pyqtSignal(str)# dato: 'Ctrl+Alt+V' ns_Print_file = pyqtSignal(str)# dato: 'Ctrl+P' ns_Redo = pyqtSignal(str)# dato: 'Ctrl+Y' ns_Reload_file = pyqtSignal(str)# dato: 'F5' ns_Remove_line = pyqtSignal(str)# dato: 'Ctrl+E' ns_Save_file = pyqtSignal(str)# dato: 'Ctrl+S' ns_Save_project = pyqtSignal(str)# dato: 'Ctrl+Shift+S' ns_Code_locator = pyqtSignal(str)# dato: 'Ctrl+K' ns_Show_Paste_History = pyqtSignal(str)# dato: 'Ctrl+4' ns_File_Opener = pyqtSignal(str)# dato: 'Ctrl+Alt+O' ns_Help = pyqtSignal(str)# dato: 'F1' ns_Show_Selector = pyqtSignal(str)# dato: 'Ctrl+`' ns_Split_assistance = pyqtSignal(str)# dato: 'F10' ns_change_tab_visibility = pyqtSignal(str)# dato: 'Shift+F1' ns_Split_horizontal = pyqtSignal(str)# dato: 'F9' ns_Split_vertical = pyqtSignal(str)# dato: 'Ctrl+F9' ns_Stop_execution = pyqtSignal(str)# dato: 'Ctrl+Shift+F6' ns_Uncomment = pyqtSignal(str)# dato: 'Ctrl+Shift+G' ns_undo = pyqtSignal(str)# dato: 'Ctrl+Z' ns_preferences_interface_showProjectExplorer = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showSymbolsList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showWebInspector = pyqtSignal(bool)# dato: 'False' ns_preferences_interface_showErrorsList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_showMigrationList = pyqtSignal(bool)# dato: 'True' ns_preferences_interface_language = pyqtSignal(str)# dato: 'English' ns_preferences_editor_font = pyqtSignal(QFont)# dato: '<PyQt5.QtGui.QFont object at 0x089D32F0>' ns_preferences_editor_minimapMaxOpacity = pyqtSignal(float)# dato: '0.8' ns_preferences_editor_minimapMinOpacity = pyqtSignal(float)# dato: '0.1' ns_preferences_editor_minimapSizeProportion = pyqtSignal(float)# dato: '0.17' ns_preferences_editor_minimapShow = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_scheme = pyqtSignal(str)# dato: 'default' ns_preferences_editor_useTabs = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_marginLine = pyqtSignal(int)# dato: '80' ns_preferences_editor_showMarginLine = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_indent = pyqtSignal(int)# dato: '4' ns_preferences_editor_platformEndOfLine = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_errorsUnderlineBackground = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_errors = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_errorsInLine = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkStyle = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_showMigrationTips = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkStyleInline = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_centerOnScroll = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_removeTrailingSpaces = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_allowWordWrap = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showTabsAndSpaces = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showIndentationGuide = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_checkForDocstrings = pyqtSignal(bool)# dato: 'False' ns_preferences_editor_showLineNumbers = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_parentheses = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_brackets = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_keys = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_simpleQuotes = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_doubleQuotes = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_codeCompletion = pyqtSignal(bool)# dato: 'True' ns_preferences_editor_completeDeclarations = pyqtSignal(bool)# dato: 'True' ns_preferences_theme_skin = pyqtSignal(str)# dato: 'Default' ds_lastSession_projects = pyqtSignal(list)# dato: '[]' ds_lastSession_openedFiles = pyqtSignal(list)# dato: '[]' ds_lastSession_currentFile = pyqtSignal(str)# dato: '' ds_lastSession_recentFiles = pyqtSignal(list)# dato: '[]' ns_preferences_editor_bookmarks = pyqtSignal(dict)# dato: '{}' ns_preferences_editor_breakpoints = pyqtSignal(dict)# dato: '{}' ns_window_maximized = pyqtSignal(bool)# dato: 'True' ns_window_central_baseSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x03\x84\x00\x00\x00\xc8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x01\x01'' ns_window_central_insideSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01B\x00\x00\x00\xa8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x02\x01'' ns_window_central_lateralVisible = pyqtSignal(bool)# dato: 'True' ns_window_hide_toolbar = pyqtSignal(bool)# dato: 'False' ns_tools_dock_visible = pyqtSignal(bool)# dato: 'True' #... ds_recentProjects = pyqtSignal(dict) ns_window_size = pyqtSignal(QSize) ns_window_pos = pyqtSignal(QPoint) def __init__(self, start_server=False): super(IDE, self).__init__() self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') self.setMinimumSize(750, 500) QToolTip.setFont(QFont(settings.FONT.family(), 10)) #Load the size and the position of the main window self.load_window_geometry() self.__project_to_open = 0 IDE.__instance = self wid = QWidget()#adjustSize wid.setContentsMargins(0, 0, 0, 0) box = QHBoxLayout(wid) box.setContentsMargins(0, 0, 0, 0) # l1 = QLabel("Info Here") # l1.setObjectName("Info") # l1.setStyleSheet("background-color: rgb(88, 255, 85);") # box.addWidget(l1) space = QSpacerItem(10,10, QSizePolicy.Expanding)#, QSizePolicy.Maximum) box.addSpacerItem(space) l2 = QLabel("Tab Size: "+str(settings.INDENT))#int(qsettings.value('preferences/editor/indent', 4, type=int)))) l2.setObjectName("Det1") font = l2.font() font.setPointSize(8) l2.setFont(font) box.addWidget(l2) box.addSpacing(50) l3 = QLabel("Python") l3.setObjectName("Det2") font.setPointSize(9) l3.setFont(font) box.addWidget(l3) box.addSpacing(30) status = self.statusBar() status.setMaximumHeight(20) status.addPermanentWidget(wid) # wid.show() # self.__wid = wid status.reformat() status.showMessage("Info Here") status.setStyleSheet("background-color: rgb(85, 85, 85);") #Editables self.__neditables = {} #Filesystem self.filesystem = nfilesystem.NVirtualFileSystem() #Sessions handler self._session = None #Opacity self.opacity = settings.MAX_OPACITY #ToolBar self.toolbar = QToolBar(self) if settings.IS_MAC_OS: self.toolbar.setIconSize(QSize(36, 36)) else: self.toolbar.setIconSize(QSize(24, 24)) self.toolbar.setToolTip(translations.TR_IDE_TOOLBAR_TOOLTIP) self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) # Set toggleViewAction text and tooltip self.toggleView = self.toolbar.toggleViewAction() self.toggleView.setText(translations.TR_TOOLBAR_VISIBILITY) self.toggleView.setToolTip(translations.TR_TOOLBAR_VISIBILITY) self.addToolBar(settings.TOOLBAR_AREA, self.toolbar) if settings.HIDE_TOOLBAR: self.toolbar.hide() #Notificator self.notification = notification.Notification(self) self.statusBar().messageChanged[str].connect(self.MessageStatusChanged.emit) #Plugin Manager # CHECK ACTIVATE PLUGINS SETTING #services = { #'editor': plugin_services.MainService(), #'toolbar': plugin_services.ToolbarService(self.toolbar), ##'menuApp': plugin_services.MenuAppService(self.pluginsMenu), #'menuApp': plugin_services.MenuAppService(None), #'explorer': plugin_services.ExplorerService(), #'misc': plugin_services.MiscContainerService(self.misc)} #serviceLocator = plugin_manager.ServiceLocator(services) serviceLocator = plugin_manager.ServiceLocator(None) self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS, serviceLocator) self.plugin_manager.discover() #load all plugins! self.plugin_manager.load_all() #Tray Icon self.trayIcon = updates.TrayIconUpdates(self) self.trayIcon.closeTrayIcon.connect(self._close_tray_icon) self.trayIcon.show() key = Qt.Key_1 for i in range(10): if settings.IS_MAC_OS: short = ui_tools.TabShortcuts( QKeySequence(Qt.CTRL + Qt.ALT + key), self, i) else: short = ui_tools.TabShortcuts( QKeySequence(Qt.ALT + key), self, i) key += 1 short.activated.connect(self._change_tab_index) short = ui_tools.TabShortcuts(QKeySequence(Qt.ALT + Qt.Key_0), self, 10) short.activated.connect(self._change_tab_index) # Register menu categories IDE.register_bar_category(translations.TR_MENU_FILE, 100) IDE.register_bar_category(translations.TR_MENU_EDIT, 110) IDE.register_bar_category(translations.TR_MENU_VIEW, 120) IDE.register_bar_category(translations.TR_MENU_SOURCE, 130) IDE.register_bar_category(translations.TR_MENU_PROJECT, 140) IDE.register_bar_category(translations.TR_MENU_EXTENSIONS, 150) IDE.register_bar_category(translations.TR_MENU_ABOUT, 160) # Register General Menu Items ui_tools.install_shortcuts(self, actions.ACTIONS_GENERAL, self) self.register_service('ide', self) self.register_service('toolbar', self.toolbar) self.register_service('filesystem', self.filesystem) #Register signals connections connections = ( {'target': 'main_container', 'signal_name': 'fileSaved',#(QString) 'slot': self.show_message}, {'target': 'main_container', 'signal_name': 'currentEditorChanged',#(QString) 'slot': self.change_window_title}, {'target': 'main_container', 'signal_name': 'openPreferences',#() 'slot': self.show_preferences}, {'target': 'main_container', 'signal_name': 'allTabsClosed',#() 'slot': self._last_tab_closed}, {'target': 'explorer_container', 'signal_name': 'changeWindowTitle',#(QString) 'slot': self.change_window_title}, {'target': 'explorer_container', 'signal_name': 'projectClosed',#(QString) 'slot': self.close_project}, ) self.register_signals('ide', connections) # Central Widget MUST always exists self.central = IDE.get_service('central_container') print("self.central:", self.central) self.setCentralWidget(self.central) # Install Services for service_name in self.__IDESERVICES: self.install_service(service_name) IDE.__created = True # Place Status Bar main_container = IDE.get_service('main_container') status_bar = IDE.get_service('status_bar') main_container.add_status_bar(status_bar) # Load Menu Bar menu_bar = IDE.get_service('menu_bar') if menu_bar: menu_bar.load_menu(self) #These two are the same service, I think that's ok menu_bar.load_toolbar(self) #Start server if needed self.s_listener = None if start_server: self.s_listener = QLocalServer() self.s_listener.listen("ninja_ide") self.s_listener.newConnection.connect(self._process_connection) @classmethod def hasCreated(clss): return clss.__created @classmethod def getInstance(clss): return clss.__instance @classmethod def get_service(cls, service_name): """Return the instance of a registered service.""" return cls.__IDESERVICES.get(service_name, None) def get_menuitems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDEMENUS def get_bar_categories(self): """Get the registered Categories for the Application menus.""" return IDE.__IDEBARCATEGORIES def get_toolbaritems(self): """Return a dictionary with the registered menu items.""" return IDE.__IDETOOLBAR @classmethod def register_service(cls, service_name, obj): """Register a service providing the service name and the instance.""" cls.__IDESERVICES[service_name] = obj if cls.hasCreated(): cls.getInstance().install_service(service_name) def install_service(self, service_name): """Activate the registered service.""" obj = IDE.__IDESERVICES.get(service_name, None) func = getattr(obj, 'install', None) if isinstance(func, collections.Callable): func() self._connect_signals() def place_me_on(self, name, obj, region="central", top=False): """Place a widget in some of the areas in the IDE. @name: id to access to that widget later if needed. @obj: the instance of the widget to be placed. @region: the area where to put the widget [central, lateral] @top: place the widget as the first item in the split.""" self.central.add_to_region(name, obj, region, top) @classmethod def register_signals(cls, service_name, connections): """Register all the signals that a particular service wants to be attached of. @service_name: id of the service @connections: list of dictionaries for the connection with: - 'target': 'the_other_service_name', - 'signal_name': 'name of the signal in the other service', - 'slot': function object in this service""" cls.__IDECONNECTIONS[service_name] = connections if cls.hasCreated(): cls.getInstance()._connect_signals() def _connect_signals(self): """Connect the signals between the different services.""" for service_name in IDE.__IDECONNECTIONS: connections = IDE.__IDECONNECTIONS[service_name] for connection in connections: if connection.get('connected', False): continue target = IDE.__IDESERVICES.get( connection['target'], None) slot = connection['slot'] signal_name = connection['signal_name'] if target and isinstance(slot, collections.Callable): getattr(target, signal_name).connect(slot) connection['connected'] = True @classmethod def register_shortcut(cls, shortcut_name, shortcut, action=None): """Register a shortcut and action.""" cls.__IDESHORTCUTS[shortcut_name] = (shortcut, action) @classmethod def register_menuitem(cls, menu_action, section, weight): """Register a QAction or QMenu in the IDE to be loaded later in the menubar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @menu_action: QAction or QMenu @section: String (name) @weight: int""" cls.__IDEMENUS[menu_action] = (section, weight) @classmethod def register_toolbar(cls, action, section, weight): """Register a QAction in the IDE to be loaded later in the toolbar using the section(string) to define where is going to be contained, and the weight define the order where is going to be placed. @action: QAction @section: String (name) @weight: int""" cls.__IDETOOLBAR[action] = (section, weight) @classmethod def register_bar_category(cls, category_name, weight): """Register a Menu Category to be created with the proper weight. @category_name: string @weight: int""" cls.__IDEBARCATEGORIES[category_name] = weight @classmethod def update_shortcut(cls, shortcut_name): """Update all the shortcuts of the application.""" short = resources.get_shortcut shortcut, action = cls.__IDESHORTCUTS.get(shortcut_name) if shortcut: shortcut.setKey(short(shortcut_name)) if action: action.setShortcut(short(shortcut_name)) def get_or_create_nfile(self, filename): """For convenience access to files from ide""" return self.filesystem.get_file(nfile_path=filename) def get_or_create_editable(self, filename="", nfile=None): if nfile is None: nfile = self.filesystem.get_file(nfile_path=filename) editable = self.__neditables.get(nfile) if editable is None: editable = neditable.NEditable(nfile) editable.fileClosing.connect(self._unload_neditable) self.__neditables[nfile] = editable return editable def _unload_neditable(self, editable): self.__neditables.pop(editable.nfile) editable.nfile.deleteLater() editable.editor.deleteLater() editable.deleteLater() @property def opened_files(self): return tuple(self.__neditables.keys()) def get_project_for_file(self, filename): project = None if filename: project = self.filesystem.get_project_for_file(filename) return project def create_project(self, path): nproj = nproject.NProject(path) self.filesystem.open_project(nproj) return nproj def close_project(self, project_path): self.filesystem.close_project(project_path) def get_projects(self): return self.filesystem.get_projects() def get_current_project(self): current_project = None projects = self.filesystem.get_projects() for project in projects: if projects[project].is_current: current_project = projects[project] break return current_project def showMessageStatus(self, msg): QTimer.singleShot(1, Qt.PreciseTimer, lambda: self.statusBar().showMessage(msg)) # self.statusBar().showMessage(msg) @classmethod def select_current(cls, widget): """Show the widget with a 4px lightblue border line.""" widget.setProperty("highlight", True) widget.style().unpolish(widget) widget.style().polish(widget) @classmethod def unselect_current(cls, widget): """Remove the 4px lightblue border line from the widget.""" widget.setProperty("highlight", False) widget.style().unpolish(widget) widget.style().polish(widget) def _close_tray_icon(self): """Close the System Tray Icon.""" self.trayIcon.hide() self.trayIcon.deleteLater() def _change_tab_index(self): """Change the tabs of the current TabWidget using alt+numbers.""" widget = QApplication.focusWidget() shortcut_index = getattr(widget, 'shortcut_index', None) if shortcut_index: obj = self.sender() shortcut_index(obj.index) def _process_connection(self): """Read the ipc input from another instance of ninja.""" connection = self.s_listener.nextPendingConnection() connection.waitForReadyRead() data = connection.readAll() connection.close() if data: files, projects = str(data).split(ipc.project_delimiter, 1) files = [(x.split(':')[0], int(x.split(':')[1])) for x in files.split(ipc.file_delimiter)] projects = projects.split(ipc.project_delimiter) self.load_session_files_projects(files, [], projects, None) def fullscreen_mode(self): """Change to fullscreen mode.""" if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def change_toolbar_visibility(self): """Switch the toolbar visibility""" if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def load_external_plugins(self, paths): """Load external plugins, the ones added to ninja throw the cmd.""" for path in paths: self.plugin_manager.add_plugin_dir(path) #load all plugins! self.plugin_manager.discover() self.plugin_manager.load_all() def _last_tab_closed(self): """ Called when the last tasb is closed """ self.explorer.cleanup_tabs() def show_preferences(self): """Open the Preferences Dialog.""" pref = preferences.Preferences(self) main_container = IDE.get_service("main_container") print("\n\npreferences!!") if main_container: main_container.show_dialog(pref) print("\n\nmain_container---") else: pref.show() print("\n\nNONE---") def load_session_files_projects(self, files, projects, current_file, recent_files=None): """Load the files and projects from previous session.""" main_container = IDE.get_service('main_container') projects_explorer = IDE.get_service('projects_explorer') if main_container and files: for fileData in files: if file_manager.file_exists(fileData[0]): mtime = os.stat(fileData[0]).st_mtime ignore_checkers = (mtime == fileData[2]) line, col = fileData[1][0], fileData[1][1] main_container.open_file(fileData[0], line, col, ignore_checkers=ignore_checkers) #if current_file: #main_container.open_file(current_file) if projects_explorer and projects: projects_explorer.load_session_projects(projects) #if recent_files is not None: #menu_file = IDE.get_service('menu_file') #menu_file.update_recent_files(recent_files) #def _set_editors_project_data(self): #self.__project_to_open -= 1 #if self.__project_to_open == 0: #self.disconnect(self.explorer, SIGNAL("projectOpened(QString)"), #self._set_editors_project_data) #self.mainContainer.update_editor_project() #def open_file(self, filename): #if filename: #self.mainContainer.open_file(filename) #def open_project(self, project): #if project: #self.actions.open_project(project) def __get_session(self): return self._session def __set_session(self, sessionName): self._session = sessionName if self._session is not None: self.setWindowTitle(translations.TR_SESSION_IDE_HEADER % {'session': self._session}) else: self.setWindowTitle( 'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') Session = property(__get_session, __set_session) def change_window_title(self, title): """Change the title of the Application.""" if self._session is None: self.setWindowTitle('NINJA-IDE - %s' % title) else: self.setWindowTitle((translations.TR_SESSION_IDE_HEADER % {'session': self._session}) + ' - %s' % title) def wheelEvent(self, event): """Change the opacity of the application.""" if event.modifiers() == Qt.ShiftModifier: if event.delta() == 120 and self.opacity < settings.MAX_OPACITY: self.opacity += 0.1 elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY: self.opacity -= 0.1 self.setWindowOpacity(self.opacity) event.ignore() else: super(IDE, self).wheelEvent(event) @classmethod def ninja_settings(cls): qsettings = nsettings.NSettings(resources.SETTINGS_PATH, prefix="ns") if cls.hasCreated(): qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed) return qsettings @classmethod def data_settings(cls): qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH, prefix="ds") if cls.hasCreated(): qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed) return qsettings def _settings_value_changed(self, key, value): # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_") # self.emit(SIGNAL(signal_name), value) key = key.replace("/", "_").replace("-", "_") try: getattr(self, key).emit(value) except TypeError as reason: print("\n:::", key, value, type(value)) print("\n\nerrors:-:", reason) getattr(self, key).emit() except AttributeError: print("\n:::", key, value, type(value)) # if not value: # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError: # getattr(self, key.replace("/", "_")).emit() # return # try: # getattr(self, key.replace("/", "_")).emit(value) # except TypeError as e: # print("\n\nerrors", e) # getattr(self, key.replace("/", "_")).emit() ##getattr(self, key.replace("/", "_").replace("-", "_")).emit(value) def save_settings(self): """Save the settings before the application is closed with QSettings. Info saved: Tabs and projects opened, windows state(size and position). """ qsettings = IDE.ninja_settings() data_qsettings = IDE.data_settings() main_container = self.get_service("main_container") editor_widget = None if main_container: editor_widget = main_container.get_current_editor() current_file = '' if editor_widget is not None: current_file = editor_widget.file_path if qsettings.value('preferences/general/loadFiles', True, type=bool): openedFiles = self.filesystem.get_files() projects_obj = self.filesystem.get_projects() projects = [projects_obj[proj].path for proj in projects_obj] data_qsettings.setValue('lastSession/projects', projects) files_info = [] for path in openedFiles: if not openedFiles[path]._exists(): print("\n\ncontinue", path) continue editable = self.__neditables.get(openedFiles[path]) if editable is not None and editable.is_dirty: stat_value = 0 else: stat_value = os.stat(path).st_mtime files_info.append([path, editable.editor.getCursorPosition(), stat_value]) data_qsettings.setValue('lastSession/openedFiles', files_info) if current_file is not None: data_qsettings.setValue('lastSession/currentFile', current_file) data_qsettings.setValue('lastSession/recentFiles', settings.LAST_OPENED_FILES) qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS) qsettings.setValue('preferences/editor/breakpoints', settings.BREAKPOINTS) # Session if self._session is not None: val = QMessageBox.question( self, translations.TR_SESSION_ACTIVE_IDE_CLOSING_TITLE, (translations.TR_SESSION_ACTIVE_IDE_CLOSING_BODY % {'session': self.Session}), QMessageBox.Yes, QMessageBox.No) if val == QMessageBox.Yes: session_manager.SessionsManager.save_session_data( self.Session, self) #qsettings.setValue('preferences/general/toolbarArea', #self.toolBarArea(self.toolbar)) #Save if the windows state is maximixed if(self.isMaximized()): qsettings.setValue("window/maximized", True) else: qsettings.setValue("window/maximized", False) #Save the size and position of the mainwindow qsettings.setValue("window/size", self.size()) qsettings.setValue("window/pos", self.pos()) self.central.save_configuration() #Save the toolbar visibility qsettings.setValue("window/hide_toolbar", not self.toolbar.isVisible()) #else: #qsettings.setValue("window/hide_toolbar", False) #Save Misc state #qsettings.setValue("window/show_region1", self.misc.isVisible()) #Save Profiles #if self.profile is not None: #self.actions.save_profile(self.profile) #else: #qsettings.setValue('ide/profiles', settings.PROFILES) def activate_profile(self): """Show the Session Manager dialog.""" profilesLoader = session_manager.SessionsManager(self) profilesLoader.show() def deactivate_profile(self): """Close the Session Session.""" self.Session = None def load_window_geometry(self): """Load from QSettings the window size of Ninja IDE""" qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) if qsettings.value("window/maximized", True, type=bool): self.setWindowState(Qt.WindowMaximized) else: self.resize(qsettings.value( "window/size", QSize(800, 600), type='QSize')) self.move(qsettings.value( "window/pos", QPoint(100, 100), type='QPoint')) def _get_unsaved_files(self): """Return an array with the path of the unsaved files.""" unsaved = [] files = self.opened_files for f in files: editable = self.__neditables.get(f) print("\n\neditable::", editable, getattr(editable, "editor", "-")) if editable is not None and editable.editor is not None and editable.editor.is_modified: unsaved.append(f) return unsaved def _save_unsaved_files(self, files): """Save the files from the paths in the array.""" for f in files: editable = self.get_or_create_editable(f) editable.ignore_checkers = True editable.save_content() def closeEvent(self, event): """Saves some global settings before closing.""" if self.s_listener: self.s_listener.close() main_container = self.get_service("main_container") unsaved_files = self._get_unsaved_files() if (settings.CONFIRM_EXIT and unsaved_files): txt = '\n'.join([nfile.file_name for nfile in unsaved_files]) val = QMessageBox.question( self, translations.TR_IDE_CONFIRM_EXIT_TITLE, (translations.TR_IDE_CONFIRM_EXIT_BODY % {'files': txt}), QMessageBox.Yes | QMessageBox.No, QMessageBox.Cancel) if val == QMessageBox.Yes: #Saves all open files self._save_unsaved_files(unsaved_files) if val == QMessageBox.Cancel: event.ignore() return self.save_settings() self.goingDown.emit() #close python documentation server (if running) main_container.close_python_doc() #Shutdown PluginManager self.plugin_manager.shutdown() #completion_daemon.shutdown_daemon() super(IDE, self).closeEvent(event) def notify_plugin_errors(self): #TODO: Check if the Plugin Error dialog can be improved errors = self.plugin_manager.errors if errors: plugin_error_dialog = traceback_widget.PluginErrorDialog() for err_tuple in errors: plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1]) #show the dialog plugin_error_dialog.exec_() def show_message(self, message, duration=3000): """Show status message.""" self.notification.set_message(message, duration) self.notification.show() def show_plugins_store(self): """Open the Plugins Manager to install/uninstall plugins.""" store = plugins_store.PluginsStore(self) main_container = IDE.get_service("main_container") print("\nshow_plugins_store") if main_container: print("\nshow_plugins_store::main_container") main_container.show_dialog(store) else: store.show() def show_languages(self): """Open the Language Manager to install/uninstall languages.""" manager = language_manager.LanguagesManagerWidget(self) manager.show() def show_schemes(self): """Open the Schemes Manager to install/uninstall schemes.""" manager = schemes_manager.SchemesManagerWidget(self) manager.show() def show_about_qt(self): """Show About Qt Dialog.""" QMessageBox.aboutQt(self, translations.TR_ABOUT_QT) def show_about_ninja(self): """Show About NINJA-IDE Dialog.""" about = about_ninja.AboutNinja(self) about.show() def show_python_detection(self): """Show Python detection dialog for windows.""" #TODO: Notify the user when no python version could be found suggested = settings.detect_python_path() if suggested: dialog = python_detect_dialog.PythonDetectDialog(suggested, self) dialog.show()
class IDE(QMainWindow): """This class is like the Sauron's Ring: One ring to rule them all, One ring to find them, One ring to bring them all and in the darkness bind them. This Class knows all the containers, and its know by all the containers, but the containers don't need to know between each other, in this way we can keep a better api without the need to tie the behaviour between the widgets, and let them just consume the 'actions' they need.""" ############################################################################### # SIGNALS ############################################################################### goingDown = 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 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 MainWindow(QMainWindow): """MainWindow class for Paint4Brains. This class contains the implementation of a series of functions required for the Main Windows of the Paint4Brains GUI. Wraps MainWidget to allow a Menu. To operate, the constructor class calls the MainWidget class which contains the bulk of the gui and enables the use of menus. Because of this most of this class is dedicated to defining menu entries and actions to be added to these entries Another thing it does is load the nib files from a string containing the path. Args: file (str): Path leading to the location of the brain data file. label_file (str): Path to the location of the labeled data file. """ def __init__(self, file, label_file=None): super(MainWindow, self).__init__() if file is None: file = self.load_initial() self.brain = BrainData(file, label_file) self.main_widget = MainWidget(self.brain, self) self.setCentralWidget(self.main_widget) self.setWindowTitle("Paint4Brains") # Making a menu menu_bar = self.menuBar() self.file = menu_bar.addMenu("File") self.edit = menu_bar.addMenu("Edit") self.view_menu = menu_bar.addMenu("View") self.tools = menu_bar.addMenu("Tools") self.help = menu_bar.addMenu("Help") # Actions in file bar (This enables shortcuts too) loadAction = QAction('Load Label', self) loadAction.setShortcut('Ctrl+L') loadAction.setStatusTip('Load Labels') loadAction.triggered.connect(self.load) self.file.addAction(loadAction) saveAction = QAction('Save', self) saveAction.setStatusTip('Save Labels') saveAction.triggered.connect(self.save) self.file.addAction(saveAction) saveAsAction = QAction('Save As', self) saveAsAction.setShortcut('Ctrl+S') saveAsAction.setStatusTip('Save labels to selected file') saveAsAction.triggered.connect(self.save_as) self.file.addAction(saveAsAction) # Predefined actions that usually appear when you right click. Recycling one that resets the view here. viewBoxActionsList = self.main_widget.win.view.menu.actions() oldButtonsAction = QAction('Single View Mode', self) oldButtonsAction.setStatusTip('Sets layout to single window mode') oldButtonsAction.triggered.connect( self.main_widget.revert_to_old_buttons) viewToolbarAction = QAction("Editing Toolbar", self) viewToolbarAction.setStatusTip("View Editing Toolbar") viewToolbarAction.triggered.connect(self.view_edit_tools) for i in [0]: # range(1, len(viewBoxActionsList)): ViewActions = viewBoxActionsList[i] self.view_menu.addAction(ViewActions) self.view_menu.addSeparator() self.view_menu.addAction(oldButtonsAction) self.view_menu.addSeparator() self.view_menu.addAction(viewToolbarAction) viewVisualizationAction = QAction("Visualization Toolbar", self) viewVisualizationAction.setStatusTip("View Visualization Toolbar") viewVisualizationAction.triggered.connect( self.view_visualization_tools) self.view_menu.addAction(viewVisualizationAction) self.view_menu.addSeparator() resetViewAction = viewBoxActionsList[0] resetViewAction.setText("Recenter View") resetViewAction.setShortcut('Ctrl+V') self.view_menu.addAction(resetViewAction) seeAllAction = QAction('All Labels', self) seeAllAction.setShortcut('Ctrl+A') seeAllAction.setStatusTip('Edit Next Segmented label') seeAllAction.triggered.connect(self.main_widget.win.view_back_labels) self.view_menu.addAction(seeAllAction) nextLabelAction = QAction('Next Label', self) nextLabelAction.setShortcut('Ctrl+N') nextLabelAction.setStatusTip('Edit Next Segmented label') nextLabelAction.triggered.connect(self.main_widget.win.next_label) self.edit.addAction(nextLabelAction) prevLabelAction = QAction('Previous Label', self) prevLabelAction.setShortcut('Ctrl+M') prevLabelAction.setStatusTip('Edit Previous Segmented label') prevLabelAction.triggered.connect(self.main_widget.win.previous_label) self.edit.addAction(prevLabelAction) selectLabelAction = QAction('Select Label', self) selectLabelAction.setStatusTip('Select Label to be edited') selectLabelAction.triggered.connect(self.main_widget.win.select_label) self.edit.addAction(selectLabelAction) self.edit.addSeparator() nodrawAction = QAction('Drawing Mode', self) nodrawAction.setShortcut('Ctrl+D') nodrawAction.setStatusTip('Activate/Deactivate drawing mode') nodrawAction.triggered.connect(self.main_widget.win.disable_drawing) self.edit.addAction(nodrawAction) self.edit.addSeparator() undoAction = QAction('Undo', self) undoAction.setShortcut('Ctrl+Z') undoAction.setStatusTip('Undo previous edit') undoAction.triggered.connect(self.main_widget.win.undo_previous_edit) self.edit.addAction(undoAction) undoAction = QAction('Redo', self) undoAction.setShortcut('Ctrl+Shift+Z') undoAction.setStatusTip('Revert to previous edit') undoAction.triggered.connect(self.main_widget.win.redo_previous_edit) self.edit.addAction(undoAction) histogramAction = QAction('Adjust Brain Intensity', self) histogramAction.setShortcut('Ctrl+H') histogramAction.setStatusTip('View Intensity Histogram') histogramAction.triggered.connect(self.view_histogram) histogramAction.triggered.connect(self.main_widget.normalize_intensity) self.tools.addAction(histogramAction) extractAction = QAction('Extract Brain', self) extractAction.setShortcut('Ctrl+E') extractAction.setStatusTip('Extract Brain') extractAction.triggered.connect(self.main_widget.extract) self.tools.addAction(extractAction) unextractAction = QAction('See Full Brain', self) unextractAction.setShortcut('Ctrl+U') unextractAction.setStatusTip('See Full Brain') unextractAction.triggered.connect(self.main_widget.full_brain) self.tools.addAction(unextractAction) segmentAction = QAction('Segment Brain', self) segmentAction.setShortcut('Ctrl+W') segmentAction.setStatusTip('Segment Brain') segmentAction.triggered.connect(self.segment) self.tools.addAction(segmentAction) # Editing tools as a toolbar current_directory = os.path.dirname(os.path.realpath(__file__)) pen = QAction(QIcon(current_directory + "/images/pen.png"), "Pencil: Draw Pixels", self) pen.triggered.connect(self.main_widget.win.edit_button1) rubber = QAction(QIcon(current_directory + "/images/eraser.png"), "Eraser: Remove Drawn Pixels", self) rubber.triggered.connect(self.main_widget.win.edit_button2) cross = QAction(QIcon(current_directory + "/images/cross.png"), "Brush: Draw Multiple Pixels", self) cross.triggered.connect(self.main_widget.win.edit_button3) bonus = QAction(QIcon(current_directory + "/images/star.png"), "Custom: Make Your Own Brush", self) bonus.triggered.connect(self.main_widget.win.bonus_brush) left = QAction(QIcon(current_directory + "/images/left.png"), "Previous Label: Go To Previously Selected Label", self) left.triggered.connect(self.main_widget.win.previous_label) label = QAction(QIcon(current_directory + "/images/label.png"), "Select Label: Select Individual Label", self) label.triggered.connect(self.main_widget.win.select_label) right = QAction(QIcon(current_directory + "/images/right.png"), "Next Label: Go To The Next Label", self) right.triggered.connect(self.main_widget.win.next_label) self.edit_toolbar = self.addToolBar("Editing Tools") self.edit_toolbar.setIconSize(QSize(40, 40)) self.edit_toolbar.addSeparator() self.edit_toolbar.addAction(pen) self.edit_toolbar.addAction(cross) self.edit_toolbar.addAction(rubber) self.edit_toolbar.addAction(bonus) self.edit_toolbar.addSeparator() self.edit_toolbar.addSeparator() self.edit_toolbar.addAction(left) self.edit_toolbar.addAction(label) self.edit_toolbar.addAction(right) self.edit_toolbar.addSeparator() self.edit_toolbar.addSeparator() self.edit_toolbar.addWidget(self.main_widget.win.dropbox) self.edit_toolbar.setVisible(False) self.optional_sliders = QToolBar() self.addToolBar(Qt.RightToolBarArea, self.optional_sliders) self.optional_sliders.addWidget(OptionalSliders(self.main_widget.win)) self.optional_sliders.setVisible(False) # Making the Histogram Tab invisible as long as Intensity Histogram has not yet been clicked self.hist_widget = HistogramWidget(self.main_widget.win) self.hist_widget.setVisible(False) def load_initial(self): """Original brain loader Loads the "base" brain. The pre-segmentation scan has to be uploaded before the gui is initialised! This can be done either through the command line beforehand (this can be set in pycharm too) or through a window that appears on start (gets annoying). If you try to open it with nothing it complains and gives you an error message. Raises: Error: Failed to load! """ self.data_filename = QFileDialog.getOpenFileName( self, "Load brain MRI", "Please select full MRI scan", "Nii Files (*.nii *.nii.gz)") if isinstance(self.data_filename, tuple): self.data_filename = self.data_filename[0] # Qt4/5 API difference if self.data_filename == '': msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Can not start without initial brain") msg.setInformativeText( "Please load a brain image either via the command line or via the initial load window" ) msg.setWindowTitle("Error: Failed to load") msg.setStandardButtons(QMessageBox.Ok) msg.exec_() return # self.load_initial() return self.data_filename def load(self): """Labelled data loader. This function loads labelled data. Opens a loading window through which you can select what label data file to load. Once a file is uploaded it automatically sets the app to drawing mode """ self.label_filename = QFileDialog.getOpenFileName( self, "Load labeled data", os.path.dirname(self.brain.filename), "Nii Files (*.nii *.nii.gz)") if isinstance(self.label_filename, tuple): self.label_filename = self.label_filename[0] if self.label_filename == '': return self.brain.load_label_data(self.label_filename) self.main_widget.win.see_all_labels = True self.main_widget.win.enable_drawing() self.main_widget.win.update_colormap() self.main_widget.win.refresh_image() def save_as(self): """Labelled data saver with a new name This function saves the edited labelled data into a new file Saves edits into a new .nii file. Opens a window in which you can type the name of the new file you are saving. It still does not copy the headers (something to do) """ saving_filename = QFileDialog.getSaveFileName( self, "Save Image", os.path.dirname(self.brain.filename), "Nii Files (*.nii *.nii.gz)") if (saving_filename[0])[-4:] != ".nii" and ( saving_filename[0])[-7:] != ".nii.gz": saving_filename = saving_filename[0] + ".nii.gz" else: saving_filename = saving_filename[0] self.brain.save_label_data(saving_filename) def save(self): """Labelled data saver Saves the edited labelled data into a previously saved file Saves edits into a new .nii file. If no file has been saved before it reverts to save_as It still does not copy the headers (something to do) """ if self.brain.saving_filename is None: self.save_as() else: self.brain.save_label_data([self.brain.saving_filename]) def view_edit_tools(self): """Toggle editing toolbar Switch the toolbar with editing buttons to visible or invisible Makes it the opposite of what it was previously """ switch = not self.edit_toolbar.isVisible() self.edit_toolbar.setVisible(switch) def view_visualization_tools(self): """Toggle visualization toolbar Switch the toolbar with editing buttons to visible or invisible Makes it the opposite of what it was previously """ switch = not self.optional_sliders.isVisible() self.optional_sliders.setVisible(switch) def view_histogram(self): """Toggle histogram widget Opens an intensity histogram for all voxels in the nii file. This enables the user to make intensity corrections on the loaded brain. """ switch = not self.hist_widget.isVisible() self.hist_widget.setVisible(switch) def segment(self): """Call the segmentation function Method that returns a segmented brain This function calls the brainSegmentation function in BrainData, which transforms (pre-processes) the brain file and then calls QuickNAT for running the file. """ SegmentManager(self)
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 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 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 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()