class Form(QWidget): def __init__(self): QWidget.__init__(self, flags=Qt.Widget) self.tbw = QTabWidget() self.init_widget() def init_widget(self): """ 현재 위젯의 모양등을 초기화 """ self.setWindowTitle("Tab Widget") form_lbx = QBoxLayout(QBoxLayout.TopToBottom, parent=self) self.setLayout(form_lbx) # 탭 추가 버튼 생성 tbw_addbtn = QToolButton() self.tbw.setCornerWidget(tbw_addbtn, Qt.TopLeftCorner) # 버튼 위치 tbw_addbtn.setAutoRaise(True) # 마우스가 올라오면 올라옴 tbw_addbtn.setIcon(QIcon("plus_icon.png")) # 아이콘 지정 tbw_addbtn.clicked.connect(self.add_new_tab) # 클릭시 시그널 지정 form_lbx.addWidget(self.tbw) # 기본 탭 생성 self.add_new_tab() @pyqtSlot() def add_new_tab(self): """ 텍스트 에디트를 가진 텝을 생성 """ self.tbw.addTab(QTextEdit(), "tab #%d" % (self.tbw.count() + 1))
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): url = "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" if "url" in kwargs: url = kwargs["url"] del kwargs["url"] if "zeronet_path" in kwargs: self.zeronet_path = kwargs["zeronet_path"] del kwargs["zeronet_path"] super(MainWindow, self).__init__(*args, **kwargs) # Tabs self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) # New tab button #self.tab_add_button_index = self.tabs.addTab(QWidget(), '+') self.add_tab_button = QToolButton() self.add_tab_button.setText('+') self.add_tab_button.setStyleSheet( 'QToolButton {border: none; margin: 4px 20px 4px 0px; height: 480px; border-left: 1px solid lightgrey; padding: 0px 4px 0px 4px; font-weight: bold; color: #5d5b59}' 'QToolButton:hover { background-color: lightgrey }' 'QToolButton:pressed { background-color: grey }') self.add_tab_button.clicked.connect(self.new_tab_clicked) self.tabs.setCornerWidget(self.add_tab_button) # Navigation bar self.navigation = NavigationBar() self.navigation.url_bar.returnPressed.connect(self.navigate_to_url) # Back self.navigation.back_btn.triggered.connect( lambda: self.tabs.currentWidget().back()) # Next self.navigation.next_btn.triggered.connect( lambda: self.tabs.currentWidget().forward()) # Reload self.navigation.reload_btn.triggered.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload.activated.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload_f5.activated.connect( lambda: self.tabs.currentWidget().reload()) # Home self.navigation.home_btn.triggered.connect(self.go_home) # Menu: Edit config action self.navigation.edit_config_action.triggered.connect( self.edit_zeronet_config_file) # Add new tab self.add_new_tab(url, "Home") # Get everything fitting in the main window self.addToolBar(self.navigation) self.setCentralWidget(self.tabs) self.show() self.setWindowTitle("ZeroNet Browser") self.setWindowIcon(QIcon("icons/zeronet-logo.svg")) self.showMaximized() def contextMenuEvent(self, event): print(event) def update_url_bar(self, q, browser=None): if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return url_array = q.toString().split('/')[3:] formatted_url = '/'.join(str(x) for x in url_array) self.navigation.url_bar.setText('zero://' + formatted_url) self.navigation.url_bar.setCursorPosition(0) if (self.tabs.currentWidget().can_go_back()): self.navigation.back_btn.setDisabled(False) else: self.navigation.back_btn.setDisabled(True) if (self.tabs.currentWidget().can_go_forward()): self.navigation.next_btn.setDisabled(False) else: self.navigation.next_btn.setDisabled(True) def navigate_to_url(self): # Get url url = self.navigation.url_bar.text() if url.startswith('zero://'): # ZeroNet protocol url_array = url.split('/') url = 'http://127.0.0.1:43110/' + url_array[2] elif url.startswith('http://'): # http protocol pass else: # Nothing mentionned url = 'http://127.0.0.1:43110/' + url self.tabs.currentWidget().setUrl(QUrl(url)) def go_home(self): self.tabs.currentWidget().setUrl( QUrl("http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/")) def new_tab_clicked(self): self.add_new_tab( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/", "Home") def get_link_url_from_context_menu(self): tab = self.tabs.currentWidget() page = tab.page() context = page.contextMenuData() qurl = context.linkUrl() return qurl.url() def open_in_new_tab(self): url = self.get_link_url_from_context_menu() self.add_new_tab(url, "Home") # Doesnt feel right to have it here but it is working def open_in_new_window(self): url = self.get_link_url_from_context_menu() kwargs = {"url": url} self.window = self.__class__(**kwargs) def add_new_tab(self, qurl, label): # Instead of browser it should be called WebView ! browser = Browser() # Triggered open in new tab openLinkInNewTabAction = browser.pageAction( QWebEnginePage.OpenLinkInNewTab) openLinkInNewWindowAction = browser.pageAction( QWebEnginePage.OpenLinkInNewWindow) openLinkInNewTabAction.triggered.connect(self.open_in_new_tab) openLinkInNewWindowAction.triggered.connect(self.open_in_new_window) self.addAction(openLinkInNewTabAction) browser.urlChanged.connect( lambda qurl, browser=browser: self.update_url_bar(qurl, browser)) indexTab = self.tabs.addTab(browser, label) # Maybe change current index after loading? self.tabs.setCurrentIndex(indexTab) # We need to update the url ! if qurl.startswith('zero://'): # ZeroNet protocol url_array = qurl.split('/') qurl = 'http://127.0.0.1:43110/' + url_array[2] elif qurl.startswith('http://'): # http protocol pass else: # Nothing mentionned qurl = 'http://127.0.0.1:43110/' + qurl currentTab = self.tabs.currentWidget() currentTab.loadFinished.connect(self.page_loaded) index = self.tabs.currentIndex() currentTab.titleChanged.connect( lambda title, index=index: self.tabs.setTabText(index, title)) currentTab.iconChanged.connect( lambda icon, index=index: self.tabs.setTabIcon(index, icon)) currentTab.setUrl(QUrl(qurl)) return indexTab def page_loaded(self, ok): if ok: currentTab = self.tabs.currentWidget() index = self.tabs.currentIndex() label = currentTab.title() icon = currentTab.icon() self.tabs.setTabIcon(index, icon) self.tabs.setTabText(index, label) def close_tab(self, index): if self.tabs.count() == 1: self.tabs.currentWidget().setUrl( QUrl( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" )) return self.tabs.removeTab(index) def edit_zeronet_config_file(self): filepath = os.path.join(os.sep, self.zeronet_path, "zeronet.conf") if sys.platform.startswith('darwin'): # macOS subprocess.run(['open', filepath]) elif sys.platform.startswith('win'): # Windows os.startfile(filepath) else: # linux variants subprocess.run(['xdg-open', filepath])
class MyTableWidget(QWidget): def __init__(self, parent, cornerwidget): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) self.history = {} # Initialize tab screen self.tabs = QTabWidget(movable=True, tabsClosable=True, tabShape=1) self.tabs.resize(0, 500) # Add tabs self.tabs.tabCloseRequested.connect(self.remove_tab) #self.tabs.tabBarDoubleClicked.connect( self.new_tab ) # Create first tab # Add tabs to widget self.layout.addWidget(self.tabs) self.layout.setContentsMargins(0, 10, 0, 0) self.layout.setSpacing(0) self.url = 'http://google.com' self.gridLayout = QGridLayout() self.searchBar = QLineEdit() self.goback = QPushButton('B') self.goforward = QPushButton('F') self.reload = QPushButton('r') self.home = QPushButton('h') self.gridLayout.addWidget(self.goback, 0, 0) self.gridLayout.addWidget(self.goforward, 0, 1) self.gridLayout.addWidget(self.reload, 0, 2) self.gridLayout.addWidget(self.home, 0, 3) self.gridLayout.addWidget(self.searchBar, 0, 4) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 5, 5, 4) self.layout.addLayout(self.gridLayout) #self.layout.addWidget(self.gridLayout) self.searchBar.setText(self.url) self.goback.clicked.connect(lambda: self.tabs.currentWidget().back()) self.goforward.clicked.connect( lambda: self.tabs.currentWidget().forward()) self.reload.clicked.connect(lambda: self.tabs.currentWidget().reload()) self.searchBar.returnPressed.connect(self.loadpage) self.setLayout(self.layout) self.new_tab() self.goback.setStyleSheet( ":hover{background-color:#00596b;}" "QPushButton{background-color: #004551;border-left-style: none;border-top-style: none;border-bottom-style: none;width:40px;height:29px!important; margin:0!important;}" ) self.goforward.setStyleSheet( ":hover{background-color:#00596b;}" "QPushButton{background-color: #004551;border-left-style: none;border-top-style: none;border-bottom-style: none;width:40px;height:29px!important; margin:0!important;}" ) self.reload.setStyleSheet( ":hover{background-color:#00596b;}" "QPushButton{background-color: #004551;border-left-style: none;border-top-style: none;border-bottom-style: none;width:40px;height:29px!important; margin:0!important;}" ) self.home.setStyleSheet( ":hover{background-color:#00596b;}" "QPushButton{background-color: #004551;border-left-style: none;border-top-style: none;border-bottom-style: none;width:40px;height:29px!important; margin:0!important;}" ) self.addtabbutton = QPushButton("+") self.cn = QWidget() self.layout1 = QHBoxLayout(self.cn) self.layout1.addWidget(self.addtabbutton) self.addtabbutton.setStyleSheet( ":hover{background-color:#00596b;color:white;}" "QPushButton{background-color: transparent;width:40px;height:30px;padding-top:3px;border:1px solid black;border-top:none;}" ) self.layout1.setSpacing(0) self.cn.setStyleSheet("background-color: #004551;") self.layout1.setContentsMargins(0, 0, 0, 0) self.addtabbutton.clicked.connect(self.new_tab) #self.tabs.tabBarDoubleClicked.connect( self.new_tab ) self.tabs.setCornerWidget(cornerwidget) self.tabs.setCornerWidget(self.cn, corner=Qt.TopLeftCorner) self.tabs.currentChanged.connect(self.changeurl) def forward(self): self.view.page().triggerAction(QWebEnginePage.Forward) def back(self, browser=None): #self.view.page().action(QWebEnginePage.Back) self.view.page().triggerAction(QWebEnginePage.Back) print('forward enabled', self.view.page().action(QWebEnginePage.Forward).isEnabled()) def changeurl(self): if self.tabs.currentWidget().url(): qurl = self.tabs.currentWidget().url() self.searchBar.setText(qurl.toString()) else: self.searchBar.setText("History") def mouseDoubleClickEvent(self, event): self.offset = event.pos() y_w = self.offset.y() if y_w < 10: self.new_tab() def loadpage(self): if self.searchBar.text().lower() == "history": self.showHistory() else: self.url = QUrl(self.searchBar.text()) if self.url.scheme() == "": self.url.setScheme("https") self.tabs.currentWidget().setUrl(self.url) def update_history(self): if self.tabs.currentWidget().url(): qurl = self.tabs.currentWidget().url() x = dt.now() self.history[x.strftime("%c")] = qurl.toString() print(self.history) def showHistory(self): d = QDialog() vbox = QVBoxLayout() for h in self.history: h1 = QLabel() h1.setText(h + " -->" + self.history[h]) vbox.addWidget(h1) d.setLayout(vbox) d.setWindowModality(Qt.ApplicationModal) d.exec_() def remove_tab(self, currentIndex): self.tabs.removeTab(currentIndex) def new_tab(self, z=0): if z == 0: self.view = QWebEngineView() self.defaulturl = 'http://google.com' self.view.load(PyQt5.QtCore.QUrl(self.defaulturl)) i = self.tabs.addTab(self.view, "Google") self.view.urlChanged.connect(self.changeurl) self.view.loadFinished.connect(self.update_history) self.view.loadFinished.connect( lambda _, i=i, browser=self.view: self.tabs.setTabText( i, browser.page().title())) else: self.tab1 = QWidget() return self.tabs.addTab(self.tab1, "History")
class TabToolbar(QToolBar): """[summary] Args: _TabToolbar ([type]): [description] Returns: [type]: [description] """ Minimized = QtCore.pyqtSignal() Maximized = QtCore.pyqtSignal() SpecialTabClicked = QtCore.pyqtSignal() StyleChanged = QtCore.pyqtSignal() def __init__(self, parent: QWidget = None, group_maxheight: int = 75, group_rowcount: int = 3): super(TabToolbar, self).__init__(parent) style.register_default_styles() self.group_rowcount = group_rowcount self.group_maxheight = group_maxheight self.has_specialtab = False self.current_index = 0 self.ignore_styleevent = False self.is_shown = True self._is_minimized = False self.maxheight = QtWidgets.QWIDGETSIZE_MAX self._style = style.StyleParams() self.setObjectName("TabToolbar") # self.tempShowTimer = QtCore.QTimer() # self.tempShowTimer.setSingleShot(True) # self.tempShowTimer.setInterval(QApplication.doubleClickInterval()) self.setProperty("TabToolbar", QtCore.QVariant(True)) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.setContentsMargins(0, 0, 0, 0) self.setFloatable(False) self.setMovable(False) self.setAllowedAreas(QtCore.Qt.TopToolBarArea) self.tabBar = QTabWidget(self) self.tabBar.setProperty("TTWidget", QtCore.QVariant(True)) self.tabBar.tabBar().setProperty("TTTab", QtCore.QVariant(True)) self.tabBarHandle = self.addWidget(self.tabBar) self.tabBar.setUsesScrollButtons(True) self.cornerActions = QFrame(self) self.cornerActions.setFrameShape(QFrame.NoFrame) self.cornerActions.setLineWidth(0) self.cornerActions.setContentsMargins(0, 0, 0, 0) self.cornerActions.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self.cornerLayout = QHBoxLayout(self.cornerActions) self.cornerLayout.setContentsMargins(0, 0, 0, 0) self.cornerLayout.setSpacing(0) self.cornerLayout.setDirection(QBoxLayout.LeftToRight) self.cornerActions.setLayout(self.cornerLayout) self.hideAction = QAction(self) self.hideAction.setCheckable(True) self.hideAction.setText("▲") self.hideButton = QToolButton(self.tabBar) self.hideButton.setProperty("TTHide", QtCore.QVariant(True)) self.hideButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly) self.hideButton.setDefaultAction(self.hideAction) self.hideButton.setAutoRaise(True) self.hideButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hideAction.triggered.connect(self._hideaction) self.tabBar.tabBarDoubleClicked.connect(self.hideAction.trigger) self.tabBar.tabBarClicked.connect(self.current_tabchanged) self.tabBar.currentChanged.connect(self.focus_changed) self.cornerLayout.addWidget(self.hideButton) self.tabBar.setCornerWidget(self.cornerActions) self.set_style(style.get_defaultstyle()) def _hideaction(self): # self.tempShowTimer.start() self._is_minimized = self.hideAction.isChecked() self.hideAction.setText("▼" if self._is_minimized else "▲") self.hide_at(self.tabBar.currentIndex()) if self._is_minimized: self.Minimized.emit() else: self.Maximized.emit() def event(self, event: QtCore.QEvent): if event.type( ) == QtCore.QEvent.StyleChange and not self.ignore_styleevent: # TODO: Validatre if we need a timer stylename = (self._style.objectName() if self._style else style.get_defaultstyle()) self.set_style(stylename) return super(TabToolbar, self).event(event) def focus_changed(self, old: QWidget = None, now: QWidget = None): if now and now != self: if self.isMinimized() and self.is_shown: parent = now while parent: parent = parent.parent() if parent == self: return self.hide_at(self.current_index) def rowcount(self): return self.group_rowcount def group_maxheight(self): return self.group_maxheight * style.get_scalefactor(self) def set_style(self, stylename: str): self.ignore_styleevent = True self._style = style.create_style(stylename) stylesheet = style.get_stylesheet(self._style) self.setStyleSheet(stylesheet) self.ignore_styleevent = False self.StyleChanged.emit() def get_style(self) -> str: if self._style: return self._style.objectName() return "" def add_corneraction(self, action: QAction): action_button = QToolButton(self.tabBar) action_button.setProperty("TTInternal", QtCore.QVariant(True)) action_button.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) action_button.setDefaultAction(action) action_button.setAutoRaise(True) action_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.cornerActions.layout().addWidget(action_button) def set_specialtabenabled(self, enabled: bool): self.has_specialtab = enabled self.tabBar.tabBar().setProperty("TTSpecial", QtCore.QVariant(enabled)) if enabled and self.tabBar.count() > 0: self.tabBar.setCurrentIndex(1) def hide_action(self) -> QAction: return self.hideAction def tab_clicked(self, index: int): if self.tempShowTimer.isActive() or (index == 0 and self.has_specialtab): return if self._is_minimized: if self.is_shown and index != self.current_index: return self._is_minimized = self.is_shown self.hide_at(index) self._is_minimized = True def current_tabchanged(self, index: int): QtCore.QSignalBlocker(self.tabBar) if index == 0 and self.has_specialtab: self.tabBar.setCurrentIndex(self.current_index) self.SpecialTabClicked.emit() else: self.current_index = index @property def current_tab(self) -> int: return self.current_index def set_currenttab(self, index: int): self.tabBar.setCurrentIndex(index) def hide_at(self, index: int): if self._is_minimized: minheight = self.tabBar.tabBar().height() + 2 self.tabBar.setMaximumHeight(minheight) self.tabBar.setMinimumHeight(minheight) self.setMaximumHeight(minheight) self.setMinimumHeight(minheight) self.is_shown = False else: self.tabBar.setCurrentIndex(index) if not self.is_shown: self.tabBar.setMaximumHeight(self.maxheight) self.tabBar.setMinimumHeight(self.maxheight) self.setMaximumHeight(self.maxheight) self.setMinimumHeight(self.maxheight) self.tabBar.adjustSize() self.setFocus() self.is_shown = True def hide_tab(self, index: int): page = self.sender() QtCore.QSignalBlocker(page) for i in range(self.tabBar.count()): if self.tabBar.widget(i) == page: self.tabBar.removeTab(i) return self.current_index = self.tabBar.currentIndex() @QtCore.pyqtSlot(int) def _adjustverticalsize(self, vsize: int): self.maxheight = vsize + self.tabBar.tabBar().height() + 6 self.setMaximumHeight(self.maxheight) self.setMinimumHeight(self.maxheight) def adjust_verticalsize(self, vsize: int): QtCore.QTimer.singleShot(0, lambda: self._adjustverticalsize(vsize) ) # type: ignore[attr-defined] # self._AdjustVerticleSize(vSize) def show_tab(self, index: int): tab_page = self.sender() QtCore.QSignalBlocker(tab_page) self.tabBar.insertTab(index, tab_page, tab_page.objectName()) self.current_index = self.tabBar.currentIndex() def add_page(self, page_name: str) -> page.Page: tab_page = page.Page(self.tabBar.count(), page_name) QtCore.QSignalBlocker(tab_page) tab_page.Hiding.connect(self.hide_tab) tab_page.Showing.connect(self.show_tab) self.tabBar.addTab(tab_page, page_name) return tab_page
super(TabCornerWidget, self).resizeEvent(event) # 更新按钮高度 if hasattr(self, 'buttonAdd'): self.buttonAdd.setFixedSize(self.height(), self.height()) if __name__ == '__main__': import sys app = QApplication(sys.argv) app.setStyle(TabCornerStyle()) tab1 = QTabWidget() cor1 = TabCornerWidget(tab1) cor1.signalTabAdd.connect(lambda: tab1.addTab(QWidget(tab1), 'tab' + str(tab1.count() + 1))) tab1.setCornerWidget(cor1, Qt.TopRightCorner) tab1.show() tab2 = QTabWidget() tab2.setTabPosition(QTabWidget.South) # tab 标签方向 cor2 = TabCornerWidget(tab2) cor2.signalTabAdd.connect(lambda: tab2.addTab(QWidget(tab2), 'tab' + str(tab2.count() + 1))) tab2.setCornerWidget(cor2, Qt.BottomRightCorner) tab2.show() for i in range(10): tab1.addTab(QWidget(tab1), 'tab' + str(i + 1)) tab2.addTab(QWidget(tab1), 'tab' + str(i + 1)) sys.exit(app.exec_())
class TableWidget(QSplitter): def __init__(self): super(TableWidget, self).__init__() # vbox = QVBoxLayout(self) # vbox.setContentsMargins(0, 0, 0, 0) self._tabs = QTabWidget() self._tabs.setAutoFillBackground(True) p = self._tabs.palette() p.setColor(p.Window, QColor("white")) self._tabs.setPalette(p) self._other_tab = QTabWidget() self._other_tab.setAutoFillBackground(True) self._other_tab.setPalette(p) self.addWidget(self._tabs) self.addWidget(self._other_tab) self.setSizes([1, 1]) self._other_tab.hide() self.relations = {} # Stack self.stacked = QStackedWidget() self._tabs.addTab(self.stacked, "Workspace") self.stacked_result = QStackedWidget() self._tabs.addTab(self.stacked_result, self.tr("Resultados")) btn_split = QToolButton() btn_split.setToolTip(self.tr("Click para dividir la pantalla")) btn_split.setAutoRaise(True) btn_split.setIcon(QIcon(":img/split")) self._tabs.setCornerWidget(btn_split) btn_split.clicked.connect(self._split) btn_split = QToolButton() btn_split.setToolTip(self.tr("Click para juntar las pantallas")) btn_split.setAutoRaise(True) btn_split.setIcon(QIcon(":img/split")) btn_split.clicked.connect(self._unsplit) self._other_tab.setCornerWidget(btn_split) # self.setContextMenuPolicy(Qt.CustomContextMenu) # self.customContextMenuRequested.connect(self._show_menu) lateral_widget = Pireal.get_service("lateral_widget") lateral_widget.resultClicked.connect(self._on_result_list_clicked) lateral_widget.resultSelectionChanged.connect( lambda index: self.stacked_result.setCurrentIndex(index)) # lateral_widget.newRowsRequested.connect(self._insert_rows) def insert_rows(self, tuplas): current_view = self.current_table() if current_view is not None: model = current_view.model() for tupla in tuplas: model.insertRow(model.rowCount(), tupla) current_view.adjust_columns() def _on_result_list_clicked(self, index): self.stacked_result.setCurrentIndex(index) if not self._other_tab.isVisible(): self._tabs.setCurrentIndex(1) def _unsplit(self): self._other_tab.hide() result_widget = self._other_tab.widget(0) self._tabs.addTab(result_widget, self.tr("Resultados")) self._tabs.cornerWidget().show() def _split(self): result_widget = self._tabs.widget(1) self._other_tab.addTab(result_widget, self.tr("Resultados")) self._other_tab.show() self.setSizes([1, 1]) self._tabs.cornerWidget().hide() self.setOrientation(Qt.Horizontal) def _show_menu(self, position): menu = QMenu(self) if self.count() > 0: add_tuple_action = menu.addAction(self.tr("Agregar Tupla")) add_col_action = menu.addAction(self.tr("Add Column")) add_tuple_action.triggered.connect(self.add_tuple) add_col_action.triggered.connect(self.add_column) menu.addSeparator() add_relation_action = menu.addAction(self.tr("Create new Relation")) add_relation_action.triggered.connect(self.__new_relation) menu.exec_(self.mapToGlobal(position)) def __new_relation(self): central_service = Pireal.get_service("central") central_service.create_new_relation() def count(self): return self.stacked.count() def remove_table(self, index): widget = self.stacked.widget(index) self.stacked.removeWidget(widget) del widget def current_table(self): return self.stacked.currentWidget() def remove_relation(self, name): del self.relations[name] def add_relation(self, name, rela): if self.relations.get(name, None) is None: self.relations[name] = rela return True return False def add_table(self, rela, name, table): """ Add new table from New Relation Dialog """ self.add_relation(name, rela) self.stacked.addWidget(table) def add_tuple(self): current_view = self.current_table() if current_view is not None: model = current_view.model() model.insertRow(model.rowCount()) def add_column(self): current_view = self.current_table() if current_view is not None: model = current_view.model() model.insertColumn(model.columnCount()) def delete_tuple(self): current_view = self.current_table() if current_view is not None: model = current_view.model() selection = current_view.selectionModel() if selection.hasSelection(): selection = selection.selection() rows = set([index.row() for index in selection.indexes()]) rows = sorted(list(rows)) previous = -1 i = len(rows) - 1 while i >= 0: current = rows[i] if current != previous: model.removeRow(current) i -= 1 def delete_column(self): """ Elimina la/las columnas seleccionadas """ current_view = self.current_table() if current_view is not None: model = current_view.model() selection = current_view.selectionModel() if selection.hasSelection(): selection = selection.selection() columns = set( [index.column() for index in selection.indexes()]) columns = sorted(list(columns)) previous = -1 i = len(columns) - 1 while i >= 0: current = columns[i] if current != previous: model.removeColumn(current) i -= 1 def create_table(self, rela, editable=True): """ Se crea la vista y el modelo """ _view = view.View() _model = model.Model(rela) if not editable: _model.editable = False _view.setModel(_model) _view.setItemDelegate(delegate.Delegate()) _view.setHorizontalHeader(view.Header()) return _view
class MainWindow(QMainWindow): """Show the main window of SCCT.""" EXIT_CODE_REBOOT = -123 def __init__(self, controller, app, showChangelog): """Init the main window.""" try: super().__init__() self._save = True self.tlock = TriggerLock() self.controller = controller self.max_no_sets = scctool.settings.max_no_sets self.scoreWidth = 35 self.raceWidth = 45 self.labelWidth = 25 self.mimumLineEditWidth = 130 self.createTabs() self.createMatchDataTabs() self.createHorizontalGroupBox() self.createLowerTabWidget() self.createMenuBar() mainLayout = QVBoxLayout() mainLayout.addWidget(self.tabs, 0) mainLayout.addWidget(self.matchDataTabWidget, 1) # mainLayout.addWidget(self.fromMatchDataBox, 1) mainLayout.addWidget(self.lowerTabWidget, 0) mainLayout.addWidget(self.horizontalGroupBox, 0) self.setWindowTitle("StarCraft Casting Tool v{}".format( scctool.__version__)) self.window = QWidget() self.window.setLayout(mainLayout) self.setCentralWidget(self.window) # self.size self.statusBar() self.leds = dict() for scope in self.controller.websocketThread.get_primary_scopes(): self.leds[scope] = LedIndicator(self) for key, led in self.leds.items(): self.controller.toogleLEDs(0, key, self) self.statusBar().addPermanentWidget(led) self.app = app self.controller.setView(self) self.controller.refreshButtonStatus() self.processEvents() self.settings = QSettings(ClientConfig.APP_NAME, ClientConfig.COMPANY_NAME) self.restoreGeometry( self.settings.value("geometry", self.saveGeometry())) self.restoreState( self.settings.value("windowState", self.saveState())) self.mysubwindows = dict() self.show() self.raise_() if showChangelog: self.openChangelog() except Exception as e: module_logger.exception("message") def showAbout(self): """Show subwindow with about info.""" html = markdown2.markdown_path(scctool.settings.getResFile("about.md")) html = html.replace("%VERSION%", scctool.__version__) if (not scctool.__new_version__): new_version = _("StarCraft Casting Tool is up to date.") else: new_version = _("The new version {} is available!").format( scctool.__latest_version__) html = html.replace('%NEW_VERSION%', new_version) # use self as parent here QMessageBox.about(self, _("StarCraft Casting Tool - About"), html) def closeEvent(self, event): """Close and clean up window.""" try: try: for name, window in self.mysubwindows.items(): if (window and window.isVisible()): window.close() finally: self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.controller.cleanUp(self._save) QMainWindow.closeEvent(self, event) # event.accept() except Exception as e: module_logger.exception("message") def createMenuBar(self): """Create the menu bar.""" try: menubar = self.menuBar() settingsMenu = menubar.addMenu(_('Settings')) apiAct = QAction(QIcon(scctool.settings.getResFile('browser.png')), _('Browser Sources'), self) apiAct.setToolTip(_('Edit Settings for all Browser Sources')) apiAct.triggered.connect(self.openBrowserSourcesDialog) settingsMenu.addAction(apiAct) apiAct = QAction(QIcon(scctool.settings.getResFile('twitch.png')), _('Twitch && Nightbot'), self) apiAct.setToolTip( _('Edit Intro-Settings and API-Settings' ' for Twitch and Nightbot')) apiAct.triggered.connect(self.openApiDialog) settingsMenu.addAction(apiAct) styleAct = QAction( QIcon(scctool.settings.getResFile('pantone.png')), _('Styles'), self) styleAct.setToolTip('') styleAct.triggered.connect(self.openStyleDialog) settingsMenu.addAction(styleAct) miscAct = QAction( QIcon(scctool.settings.getResFile('settings.png')), _('Misc'), self) miscAct.setToolTip('') miscAct.triggered.connect(self.openMiscDialog) settingsMenu.addAction(miscAct) self.createBrowserSrcMenu() ProfileMenu(self, self.controller) self.createLangMenu() infoMenu = menubar.addMenu(_('Info && Links')) myAct = QAction(QIcon(scctool.settings.getResFile('about.png')), _('About'), self) myAct.triggered.connect(self.showAbout) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('readme.ico')), _('Readme'), self) myAct.triggered.connect(self.openReadme) infoMenu.addAction(myAct) websiteAct = QAction( QIcon(scctool.settings.getResFile('youtube.png')), _('Video Tutorial'), self) websiteAct.triggered.connect(lambda: self.controller.openURL( "https://youtu.be/j5iWa4JB8bM")) infoMenu.addAction(websiteAct) myAct = QAction(QIcon(scctool.settings.getResFile('update.png')), _('Check for new version'), self) myAct.triggered.connect(lambda: self.controller.checkVersion(True)) infoMenu.addAction(myAct) myAct = QAction( QIcon(scctool.settings.getResFile('changelog.png')), _('Changelog'), self) myAct.triggered.connect(self.openChangelog) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('folder.png')), _('Open log folder'), self) myAct.triggered.connect(lambda: self.controller.open_file( scctool.settings.getAbsPath(scctool.settings.getLogDir()))) infoMenu.addAction(myAct) infoMenu.addSeparator() websiteAct = QAction( QIcon(scctool.settings.getResFile('scct.ico')), 'StarCraft Casting Tool', self) websiteAct.triggered.connect(lambda: self.controller.openURL( "https://teampheenix.github.io/StarCraft-Casting-Tool/")) infoMenu.addAction(websiteAct) discordAct = QAction( QIcon(scctool.settings.getResFile('discord.png')), 'Discord', self) discordAct.triggered.connect( lambda: self.controller.openURL("https://discord.gg/G9hFEfh")) infoMenu.addAction(discordAct) ixAct = QAction(QIcon(scctool.settings.getResFile('icon.png')), 'team pheeniX', self) ixAct.triggered.connect( lambda: self.controller.openURL("http://team-pheenix.de")) infoMenu.addAction(ixAct) alphaAct = QAction(QIcon(scctool.settings.getResFile('alpha.png')), 'AlphaTL', self) alphaAct.triggered.connect( lambda: self.controller.openURL("https://alpha.tl")) infoMenu.addAction(alphaAct) rstlAct = QAction(QIcon(scctool.settings.getResFile('rstl.png')), 'RSTL', self) rstlAct.triggered.connect( lambda: self.controller.openURL("http://hdgame.net/en/")) infoMenu.addAction(rstlAct) infoMenu.addSeparator() myAct = QAction(QIcon(scctool.settings.getResFile('patreon.png')), _('Become a Patron'), self) myAct.triggered.connect(lambda: self.controller.openURL( "https://www.patreon.com/StarCraftCastingTool")) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('donate.ico')), _('Donate via PayPal'), self) myAct.triggered.connect(lambda: self.controller.openURL( "https://paypal.me/StarCraftCastingTool")) infoMenu.addAction(myAct) except Exception as e: module_logger.exception("message") def createLangMenu(self): menubar = self.menuBar() langMenu = menubar.addMenu(_('Language')) language = scctool.settings.config.parser.get("SCT", "language") languages = [] languages.append({ 'handle': 'de_DE', 'icon': 'de.png', 'name': 'Deutsch', 'active': True }) languages.append({ 'handle': 'en_US', 'icon': 'en.png', 'name': 'English', 'active': True }) languages.append({ 'handle': 'fr_FR', 'icon': 'fr.png', 'name': 'Français', 'active': True }) languages.append({ 'handle': 'ru_RU', 'icon': 'ru.png', 'name': 'Pусский', 'active': True }) for lang in languages: myAct = QAction(QIcon(scctool.settings.getResFile(lang['icon'])), lang['name'], self, checkable=True) myAct.setChecked(language == lang['handle']) myAct.setDisabled(not lang['active']) myAct.triggered.connect( lambda x, handle=lang['handle']: self.changeLanguage(handle)) langMenu.addAction(myAct) def createBrowserSrcMenu(self): menubar = self.menuBar() main_menu = menubar.addMenu(_('Browser Sources')) srcs = [] srcs.append({ 'name': _('Intro'), 'file': 'intro.html', 'settings': lambda: self.openBrowserSourcesDialog('intro') }) srcs.append({ 'name': _('Mapstats'), 'file': 'mapstats.html', 'settings': lambda: self.openBrowserSourcesDialog('mapstats') }) srcs.append({'name': _('Score'), 'file': 'score.html'}) srcs.append({ 'name': _('Map Icons Box'), 'settings': lambda: self.openBrowserSourcesDialog('mapicons_box'), 'sub': [{ 'name': _('Icon Set {}').format(1), 'file': 'mapicons_box_1.html' }, { 'name': _('Icon Set {}').format(2), 'file': 'mapicons_box_2.html' }, { 'name': _('Icon Set {}').format(3), 'file': 'mapicons_box_3.html' }] }) srcs.append({ 'name': _('Map Icons Landscape'), 'settings': lambda: self.openBrowserSourcesDialog("mapicons_landscape"), 'sub': [{ 'name': _('Icon Set {}').format(1), 'file': 'mapicons_landscape_1.html' }, { 'name': _('Icon Set {}').format(2), 'file': 'mapicons_landscape_2.html' }, { 'name': _('Icon Set {}').format(3), 'file': 'mapicons_landscape_3.html' }] }) srcs.append({ 'name': _('Misc'), 'sub': [{ 'name': _('Logo {}').format(1), 'file': 'logo1.html' }, { 'name': _('Logo {}').format(2), 'file': 'logo2.html' }, { 'name': _('UI Logo {}').format(1), 'file': 'ui_logo_1.html' }, { 'name': _('UI Logo {}').format(2), 'file': 'ui_logo_2.html' }, { 'name': _('Aligulac (only 1vs1)'), 'file': 'aligulac.html' }, { 'name': _('Countdown'), 'file': 'countdown.html' }, { 'name': _('League (ALphaTL && RSTL only)'), 'file': 'league.html' }, { 'name': _('Matchbanner (AlphaTL)'), 'file': 'matchbanner.html', 'settings': lambda: self.openMiscDialog('alphatl') }] }) act = QAction(QIcon(scctool.settings.getResFile('folder.png')), _('Open Folder'), self) act.triggered.connect(lambda: self.controller.open_file( scctool.settings.getAbsPath(scctool.settings.casting_html_dir))) main_menu.addAction(act) main_menu.addSeparator() for src in srcs: myMenu = QMenu(src['name'], self) sub = src.get('sub', False) if sub: for icon in sub: mySubMenu = QMenu(icon['name'], self) icon['file'] = os.path.join( scctool.settings.casting_html_dir, icon['file']) act = QAction( QIcon(scctool.settings.getResFile('html.png')), _('Open in Browser'), self) act.triggered.connect( lambda x, file=icon['file']: self.controller.openURL( scctool.settings.getAbsPath(file))) mySubMenu.addAction(act) act = QAction( QIcon(scctool.settings.getResFile('copy.png')), _('Copy URL to Clipboard'), self) act.triggered.connect( lambda x, file=icon['file']: QApplication.clipboard( ).setText(scctool.settings.getAbsPath(file))) mySubMenu.addAction(act) if icon.get('settings', None) is not None: act = QAction( QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) act.triggered.connect(icon['settings']) mySubMenu.addAction(act) myMenu.addMenu(mySubMenu) else: src['file'] = os.path.join(scctool.settings.casting_html_dir, src['file']) act = QAction(QIcon(scctool.settings.getResFile('html.png')), _('Open in Browser'), self) act.triggered.connect( lambda x, file=src['file']: self.controller.openURL( scctool.settings.getAbsPath(file))) myMenu.addAction(act) act = QAction(QIcon(scctool.settings.getResFile('copy.png')), _('Copy URL to Clipboard'), self) act.triggered.connect( lambda x, file=src['file']: QApplication.clipboard( ).setText(scctool.settings.getAbsPath(file))) myMenu.addAction(act) if src.get('settings', None) is not None: act = QAction( QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) act.triggered.connect(src['settings']) myMenu.addAction(act) main_menu.addMenu(myMenu) main_menu.addSeparator() apiAct = QAction(QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) apiAct.setToolTip(_('Edit Settings for all Browser Sources')) apiAct.triggered.connect(self.openBrowserSourcesDialog) main_menu.addAction(apiAct) styleAct = QAction(QIcon(scctool.settings.getResFile('pantone.png')), _('Styles'), self) styleAct.setToolTip('') styleAct.triggered.connect(self.openStyleDialog) main_menu.addAction(styleAct) def openApiDialog(self): """Open subwindow with connection settings.""" self.mysubwindows['connections'] = SubwindowConnections() self.mysubwindows['connections'].createWindow(self) self.mysubwindows['connections'].show() def openStyleDialog(self): """Open subwindow with style settings.""" self.mysubwindows['styles'] = SubwindowStyles() self.mysubwindows['styles'].createWindow(self) self.mysubwindows['styles'].show() def openMiscDialog(self, tab=''): """Open subwindow with misc settings.""" self.mysubwindows['misc'] = SubwindowMisc() self.mysubwindows['misc'].createWindow(self, tab) self.mysubwindows['misc'].show() def openBrowserSourcesDialog(self, tab=''): """Open subwindow with misc settings.""" self.mysubwindows['browser'] = SubwindowBrowserSources() self.mysubwindows['browser'].createWindow(self, tab) self.mysubwindows['browser'].show() def openReadme(self): """Open subwindow with readme viewer.""" self.mysubwindows['readme'] = SubwindowMarkdown() self.mysubwindows['readme'].createWindow( self, _("Readme"), scctool.settings.getResFile('readme.ico'), scctool.settings.getResFile("../README.md")) self.mysubwindows['readme'].show() def openChangelog(self): """Open subwindow with readme viewer.""" self.mysubwindows['changelog'] = SubwindowMarkdown() self.mysubwindows['changelog'].createWindow( self, "StarCraft Casting Tool " + _("Changelog"), scctool.settings.getResFile("changelog.png"), scctool.settings.getResFile("../CHANGELOG.md")) self.mysubwindows['changelog'].show() def changeLanguage(self, language): """Change the language.""" scctool.settings.config.parser.set("SCT", "language", language) self.restart() def updateAllMapCompleters(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updateMapCompleters() def updateAllPlayerCompleters(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updatePlayerCompleters() def updateAllMapButtons(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updateMapButtons() def createMatchDataTabs(self): self.matchDataTabWidget = QTabWidget() self.matchDataTabWidget.setMovable(True) closeable = self.controller.matchControl.countMatches() > 1 self.matchDataTabWidget.setUsesScrollButtons(True) for match in self.controller.matchControl.getMatches(): MatchDataWidget(self, self.matchDataTabWidget, match, closeable) container = QWidget() buttonLayout = QHBoxLayout() buttonLayout.setContentsMargins(2, 1, 1, 2) buttonLayout.setSpacing(1) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('add.png')) button.setIcon(pixmap) button.setFixedSize(28, 28) # button.setFlat(True) button.setToolTip(_('Add Match Tab')) button.clicked.connect(self.addMatchTab) buttonLayout.addWidget(button) button = QPushButton() button.setFixedSize(28, 28) pixmap = QIcon(scctool.settings.getResFile('copy.png')) button.setIcon(pixmap) # button.setFlat(True) button.setToolTip(_('Copy Match Tab')) button.clicked.connect(self.copyMatchTab) buttonLayout.addWidget(button) container.setLayout(buttonLayout) self.matchDataTabWidget.setCornerWidget(container) tabBar = self.matchDataTabWidget.tabBar() tabBar.setExpanding(True) self.matchDataTabWidget.currentChanged.connect( self.currentMatchTabChanged) tabBar.tabMoved.connect(self.tabMoved) def addMatchTab(self): match = self.controller.matchControl.newMatchData() MatchDataWidget(self, self.matchDataTabWidget, match) count = self.matchDataTabWidget.count() self.matchDataTabWidget.setCurrentIndex(count - 1) if count > 1: for idx in range(count): self.matchDataTabWidget.widget(idx).setClosable(True) def copyMatchTab(self): matchId = self.controller.matchControl.selectedMatchId() data = self.controller.matchControl.selectedMatch().getData() match = self.controller.matchControl.newMatchData(data) self.controller.logoManager.copyMatch(match.getControlID(), matchId) MatchDataWidget(self, self.matchDataTabWidget, match) count = self.matchDataTabWidget.count() self.matchDataTabWidget.setCurrentIndex(count - 1) if count > 1: for idx in range(count): self.matchDataTabWidget.widget(idx).setClosable(True) def currentMatchTabChanged(self, idx): dataWidget = self.matchDataTabWidget.widget(idx) ident = dataWidget.matchData.getControlID() self.controller.matchControl.selectMatch(ident) with self.tlock: self.controller.updateMatchFormat() def tabMoved(self, toIdx, fromIdx): self.controller.matchControl.updateOrder(toIdx, fromIdx) def createTabs(self): """Create tabs in main window.""" try: # Initialize tab screen self.tabs = QTabWidget() self.tab1 = QWidget() self.tab2 = QWidget() # self.tabs.resize(300,200) # Add tabs self.tabs.addTab(self.tab1, _("Match Grabber for AlphaTL && RSTL")) self.tabs.addTab(self.tab2, _("Custom Match")) # Create first tab self.tab1.layout = QVBoxLayout() self.le_url = MatchComboBox(self) self.le_url.returnPressed.connect(self.refresh_click) minWidth = self.scoreWidth + 2 * self.raceWidth + \ 2 * self.mimumLineEditWidth + 4 * 6 self.le_url.setMinimumWidth(minWidth) self.pb_openBrowser = QPushButton(_("Open in Browser")) self.pb_openBrowser.clicked.connect(self.openBrowser_click) width = (self.scoreWidth + 2 * self.raceWidth + 2 * self.mimumLineEditWidth + 4 * 6) / 2 - 2 self.pb_openBrowser.setMinimumWidth(width) container = QHBoxLayout() label = QLabel() label.setFixedWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match-URL:")) label.setMinimumWidth(80) container.addWidget(label, 0) container.addWidget(self.le_url, 1) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('alpha.png')) button.setIcon(pixmap) button.clicked.connect( lambda: self.controller.openURL("https://alpha.tl/")) container.addWidget(button, 0) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('rstl.png')) button.setIcon(pixmap) button.clicked.connect( lambda: self.controller.openURL("http://hdgame.net/en/")) container.addWidget(button, 0) self.tab1.layout = QFormLayout() self.tab1.layout.addRow(container) container = QHBoxLayout() # self.pb_download = QPushButton("Download Images from URL") # container.addWidget(self.pb_download) label = QLabel() label.setFixedWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel() label.setMinimumWidth(80) container.addWidget(label, 0) self.pb_refresh = QPushButton(_("Load Data from URL")) self.pb_refresh.clicked.connect(self.refresh_click) container.addWidget(self.pb_openBrowser, 3) container.addWidget(self.pb_refresh, 3) self.tab1.layout.addRow(container) self.tab1.setLayout(self.tab1.layout) # Create second tab self.tab2.layout = QVBoxLayout() container = QHBoxLayout() label = QLabel() label.setMinimumWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match Format:")) label.setMinimumWidth(80) container.addWidget(label, 0) container.addWidget(QLabel(_("Best of")), 0) self.cb_bestof = QComboBox() for idx in range(0, scctool.settings.max_no_sets): self.cb_bestof.addItem(str(idx + 1)) self.cb_bestof.setCurrentIndex(3) string = _('"Best of 6/4": First, a Bo5/3 is played and the' ' ace map gets extended to a Bo3 if needed;' ' Best of 2: Bo3 with only two maps played.') self.cb_bestof.setToolTip(string) self.cb_bestof.setMaximumWidth(40) self.cb_bestof.currentIndexChanged.connect(self.changeBestOf) container.addWidget(self.cb_bestof, 0) container.addWidget(QLabel(_(" but at least")), 0) self.cb_minSets = QComboBox() self.cb_minSets.setToolTip( _('Minimum number of maps played (even if the match' ' is decided already)')) self.cb_minSets.setMaximumWidth(40) container.addWidget(self.cb_minSets, 0) container.addWidget(QLabel(" " + _("maps") + " "), 0) self.cb_minSets.currentIndexChanged.connect( lambda idx: self.highlightApplyCustom()) self.cb_allkill = QCheckBox(_("All-Kill Format")) self.cb_allkill.setChecked(False) self.cb_allkill.setToolTip( _('Winner stays and is automatically' ' placed into the next set')) self.cb_allkill.stateChanged.connect(self.allkill_change) container.addWidget(self.cb_allkill, 0) self.cb_solo = QCheckBox(_("1vs1")) self.cb_solo.setChecked(False) self.cb_solo.setToolTip(_('Select for solo (non-team matches)')) container.addWidget(self.cb_solo, 0) self.cb_solo.stateChanged.connect( lambda idx: self.highlightApplyCustom()) label = QLabel("") container.addWidget(label, 1) self.applycustom_is_highlighted = False self.pb_applycustom = QToolButton() action = QAction(_("Apply Format")) action.triggered.connect(self.applycustom_click) self.pb_applycustom.setDefaultAction(action) self.custom_menu = QMenu(self.pb_applycustom) for format, icon in \ self.controller.matchControl.getCustomFormats(): if icon: action = self.custom_menu.addAction( QIcon(scctool.settings.getResFile(icon)), format) else: action = self.custom_menu.addAction(format) action.triggered.connect( lambda x, format=format: self.applyCustomFormat(format)) self.pb_applycustom.setMenu(self.custom_menu) self.pb_applycustom.setPopupMode(QToolButton.MenuButtonPopup) self.pb_applycustom.setFixedWidth(150) container.addWidget(self.pb_applycustom, 0) self.defaultButtonPalette = self.pb_applycustom.palette() self.tab2.layout.addLayout(container) container = QHBoxLayout() label = QLabel() label.setMinimumWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match-URL:")) label.setMinimumWidth(80) container.addWidget(label, 0) self.le_url_custom = MonitoredLineEdit() self.le_url_custom.setAlignment(Qt.AlignCenter) self.le_url_custom.setToolTip( _('Optionally specify the Match-URL,' ' e.g., for Nightbot commands')) self.le_url_custom.setPlaceholderText( _("Specify the Match-URL of your Custom Match")) completer = QCompleter(["http://"], self.le_url_custom) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) completer.setWrapAround(True) self.le_url_custom.setCompleter(completer) self.le_url_custom.setMinimumWidth(360) self.le_url_custom.textModified.connect(self.highlightApplyCustom) container.addWidget(self.le_url_custom, 11) label = QLabel("") container.addWidget(label, 1) self.pb_resetdata = QPushButton(_("Reset Match Data")) self.pb_resetdata.setFixedWidth(150) self.pb_resetdata.clicked.connect(self.resetdata_click) container.addWidget(self.pb_resetdata, 0) self.tab2.layout.addLayout(container) self.tab2.setLayout(self.tab2.layout) except Exception as e: module_logger.exception("message") def allkill_change(self): try: self.controller.matchControl.\ selectedMatch().setAllKill(self.cb_allkill.isChecked()) except Exception as e: module_logger.exception("message") def changeBestOf(self, bestof): """Change the minimum sets combo box on change of BoX.""" bestof = bestof + 1 self.cb_minSets.clear() self.highlightApplyCustom() for idx in range(0, bestof): self.cb_minSets.addItem(str(idx + 1)) if bestof == 2: self.cb_minSets.setCurrentIndex(1) else: self.cb_minSets.setCurrentIndex((bestof - 1) / 2) def createHorizontalGroupBox(self): """Create horizontal group box for tasks.""" try: self.horizontalGroupBox = QGroupBox(_("Tasks")) layout = QHBoxLayout() self.pb_twitchupdate = QPushButton(_("Update Twitch Title")) self.pb_twitchupdate.clicked.connect(self.updatetwitch_click) self.pb_nightbotupdate = QPushButton(_("Update Nightbot")) self.pb_nightbotupdate.clicked.connect(self.updatenightbot_click) self.pb_resetscore = QPushButton(_("Reset Score")) self.pb_resetscore.clicked.connect(self.resetscore_click) layout.addWidget(self.pb_twitchupdate) layout.addWidget(self.pb_nightbotupdate) layout.addWidget(self.pb_resetscore) self.horizontalGroupBox.setLayout(layout) except Exception as e: module_logger.exception("message") def createLowerTabWidget(self): """Create the tab widget at the bottom.""" try: self.lowerTabWidget = QTabWidget() self.createBackgroundTasksTab() self.countdownTab = CountdownWidget(self.controller, self) self.lowerTabWidget.addTab(self.backgroundTasksTab, _("Background Tasks")) self.lowerTabWidget.addTab(self.countdownTab, _("Countdown")) except Exception as e: module_logger.exception("message") def createBackgroundTasksTab(self): """Create group box for background tasks.""" try: self.backgroundTasksTab = QWidget() self.cb_autoUpdate = QCheckBox(_("Auto Score Update")) self.cb_autoUpdate.setChecked(False) string = _('Automatically detects the outcome' + ' of SC2 matches that are ' + 'played/observed in your SC2-client' + ' and updates the score accordingly.') self.cb_autoUpdate.setToolTip(string) self.cb_autoUpdate.stateChanged.connect(self.autoUpdate_change) self.cb_autoToggleScore = QCheckBox(_("Set Ingame Score")) self.cb_autoToggleScore.setChecked(False) string = _('Automatically sets the score of your ingame' + ' UI-interface at the begining of a game.') self.cb_autoToggleScore.setToolTip(string) self.cb_autoToggleScore.stateChanged.connect( self.autoToggleScore_change) self.cb_autoToggleProduction = QCheckBox( _("Toggle Production Tab")) self.cb_autoToggleProduction.setChecked(False) string = _('Automatically toggles the production tab of your' + ' ingame UI-interface at the begining of a game.') self.cb_autoToggleProduction.setToolTip(string) self.cb_autoToggleProduction.stateChanged.connect( self.autoToggleProduction_change) self.cb_autoTwitch = QCheckBox(_("Auto Twitch Update")) self.cb_autoTwitch.setChecked(False) self.cb_autoTwitch.stateChanged.connect(self.autoTwitch_change) self.cb_autoNightbot = QCheckBox(_("Auto Nightbot Update")) self.cb_autoNightbot.setChecked(False) self.cb_autoNightbot.stateChanged.connect(self.autoNightbot_change) if (not scctool.settings.windows): self.cb_autoToggleScore.setEnabled(False) self.cb_autoToggleScore.setAttribute(Qt.WA_AlwaysShowToolTips) self.cb_autoToggleScore.setToolTip(_('Only Windows')) self.cb_autoToggleProduction.setEnabled(False) self.cb_autoToggleProduction.setAttribute( Qt.WA_AlwaysShowToolTips) self.cb_autoToggleProduction.setToolTip(_('Only Windows')) layout = QGridLayout() layout.addWidget(self.cb_autoTwitch, 0, 0) layout.addWidget(self.cb_autoNightbot, 0, 1) layout.addWidget(self.cb_autoUpdate, 1, 0) layout.addWidget(self.cb_autoToggleScore, 1, 1) layout.addWidget(self.cb_autoToggleProduction, 1, 2) self.backgroundTasksTab.setLayout(layout) except Exception as e: module_logger.exception("message") def autoTwitch_change(self): """Handle change of auto twitch check box.""" try: if (self.cb_autoTwitch.isChecked()): self.controller.autoRequestsThread.activateTask('twitch') else: self.controller.autoRequestsThread.deactivateTask('twitch') except Exception as e: module_logger.exception("message") def autoNightbot_change(self): """Handle change of auto twitch check box.""" try: if (self.cb_autoNightbot.isChecked()): self.controller.autoRequestsThread.activateTask('nightbot') else: self.controller.autoRequestsThread.deactivateTask('nightbot') except Exception as e: module_logger.exception("message") def autoUpdate_change(self): """Handle change of auto score update check box.""" try: if (self.cb_autoUpdate.isChecked()): self.controller.runSC2ApiThread("updateScore") else: self.controller.stopSC2ApiThread("updateScore") except Exception as e: module_logger.exception("message") def autoToggleScore_change(self): """Handle change of toggle score check box.""" try: if (self.cb_autoToggleScore.isChecked()): self.controller.runSC2ApiThread("toggleScore") else: self.controller.stopSC2ApiThread("toggleScore") except Exception as e: module_logger.exception("message") def autoToggleProduction_change(self): """Handle change of toggle production tab check box.""" try: if (self.cb_autoToggleProduction.isChecked()): self.controller.runSC2ApiThread("toggleProduction") else: self.controller.stopSC2ApiThread("toggleProduction") except Exception as e: module_logger.exception("message") def applyCustomFormat(self, format): """Handle click to apply custom format.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: self.controller.matchControl.\ selectedMatch().applyCustomFormat(format) self.controller.updateMatchFormat() matchWidget = self.matchDataTabWidget.currentWidget() matchWidget.updateForms() self.resizeWindow() self.highlightApplyCustom(False) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def applycustom_click(self): """Handle click to apply custom match.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: self.statusBar().showMessage(_('Applying Custom Match...')) msg = self.controller.applyCustom( int(self.cb_bestof.currentText()), self.cb_allkill.isChecked(), self.cb_solo.isChecked(), int(self.cb_minSets.currentText()), self.le_url_custom.text().strip()) self.statusBar().showMessage(msg) self.highlightApplyCustom(False) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def resetdata_click(self): """Handle click to reset the data.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: msg = self.controller.resetData() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def refresh_click(self): """Handle click to refresh/load data from an URL.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: url = self.le_url.lineEdit().text() with self.tlock: self.statusBar().showMessage(_('Reading {}...').format(url)) msg = self.controller.refreshData(url) self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def openBrowser_click(self): """Handle request to open URL in browser.""" try: url = self.le_url.text() self.controller.openURL(url) except Exception as e: module_logger.exception("message") def updatenightbot_click(self): """Handle click to change nightbot command.""" try: self.statusBar().showMessage(_('Updating Nightbot Command...')) msg = self.controller.updateNightbotCommand() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") def updatetwitch_click(self): """Handle click to change twitch title.""" try: self.statusBar().showMessage(_('Updating Twitch Title...')) msg = self.controller.updateTwitchTitle() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") def resetscore_click(self, myteam=False): """Handle click to reset the score.""" try: self.statusBar().showMessage(_('Resetting Score...')) with self.tlock: matchDataWidget = self.matchDataTabWidget.currentWidget() for set_idx in range(self.max_no_sets): matchDataWidget.sl_score[set_idx].setValue(0) self.controller.matchControl.selectedMatch().setMapScore( set_idx, 0, overwrite=True) self.controller.autoSetNextMap() if myteam: matchDataWidget.sl_team.setValue(0) self.controller.matchControl.selectedMatch().setMyTeam(0) if not self.controller.resetWarning(): self.statusBar().showMessage('') except Exception as e: module_logger.exception("message") def highlightApplyCustom(self, highlight=True, force=False): if not force and not self.tlock.trigger(): return try: if self.applycustom_is_highlighted == highlight: return highlight except AttributeError: return False if highlight: myPalette = self.pb_applycustom.palette() myPalette.setColor(QPalette.Background, Qt.darkBlue) myPalette.setColor(QPalette.ButtonText, Qt.darkBlue) self.pb_applycustom.setPalette(myPalette) else: self.pb_applycustom.setPalette(self.defaultButtonPalette) self.applycustom_is_highlighted = highlight return highlight def logoDialog(self, team, matchDataWidget): """Open dialog for team logo.""" self.controller.logoManager.resetLogoChanged() self.mysubwindows['icons'] = SubwindowLogos() self.mysubwindows['icons'].createWindow(self, self.controller, team, matchDataWidget) self.mysubwindows['icons'].show() def resizeWindow(self): """Resize the window height to size hint.""" if (not self.isMaximized()): self.processEvents() self.resize(self.width(), self.sizeHint().height()) def processEvents(self): """Process ten PyQt5 events.""" for i in range(0, 10): self.app.processEvents() def restart(self, save=True): """Restart the main window.""" self._save = save self.close() self.app.exit(self.EXIT_CODE_REBOOT)
class Centro(QWidget): def __init__(self, parent=None): super(Centro, self).__init__() self.widget_abas = None self.menu = None self.indexer = None self.parent = parent self.pacotes = dict() self.temp_build = mkdtemp('build') self.temp_cache = mkdtemp('cache') self.log = None self.init_ui() # noinspection PyUnresolvedReferences def init_ui(self): # Define grid layout = QGridLayout(self) layout.setRowStretch(0, 7.5) layout.setRowStretch(1, 2.5) layout.setColumnMinimumWidth(0, 60) layout.setSpacing(5) layout.setContentsMargins(0, 0, 0, 0) # Cria menu self.menu = Menu.Menu(self) layout.addWidget(self.menu, 0, 0, 2, 2) # Botao para criar nova aba btn = QPushButton(self) btn.clicked.connect(self.nova_aba) btn.setStatusTip("Abrir nova aba") # Cria o widget abas self.widget_abas = QTabWidget(self.parent) self.widget_abas.tabCloseRequested.connect(self.remover_aba) self.widget_abas.setTabsClosable(False) self.widget_abas.setCornerWidget(btn, Qt.TopRightCorner) self.widget_abas.setStyleSheet("background:#252525;") layout.addWidget(self.widget_abas, 0, 1, 1, 2) # Cria log self.log = QPlainTextEdit(self) self.log.setStyleSheet( "border-radius:5px;background:#101010;margin-bottom:5px;margin-right:5px;" ) self.log.setReadOnly(True) self.log.setStatusTip("Log") layout.addWidget(self.log, 1, 1, 1, 2) # Carrega pacotes de hardware self.init_pacotes() # Cria menu de placas self.criar_menu_placas() self.criar_menu_exemplos() # Adiciona a aba de boas vindas self.widget_abas.addTab(BoasVindas(self), "Bem-Vindo") self.show() def init_pacotes(self): """ Carrega os pacotes de hardware do Arduino :return: None """ pasta_hardware = os.path.join('builder', 'hardware') self.indexer = IndexadorContribuicao(os.path.join('builder'), pasta_hardware) self.indexer.parse_index() self.indexer.sincronizar_com_arquivos() self.carregar_hardware(pasta_hardware) self.carregar_hardware_contribuido(self.indexer) self.carregar_hardware( os.path.join(Main.get_caminho_padrao(), 'hardware')) def remover_aba(self, index, fechando=False): """ Remove a aba :param index: Indice da aba :param fechando: Indica se o programa esta fechando default: False :return: None """ if self.widget_abas.count() > 1 or fechando: # Se o index for argumento padrao do sinal (QT) if type(index) is not int: self.remover_aba(self.widget_abas.currentIndex()) else: arquivo = self.widget_abas.widget(index) self.widget_abas.setCurrentIndex(index) if not arquivo.salvo: ret = QMessageBox(self) ret.setText( "Gostaria de salvar este código antes de sair?") ret.setIcon(QMessageBox.Question) ret.addButton("Não Salvar", QMessageBox.NoRole) ret.addButton("Cancelar", QMessageBox.RejectRole) ret.addButton("Salvar", QMessageBox.AcceptRole) ret = ret.exec_() if ret == 1: return False elif ret == 2: self.salvar() if arquivo is not None: arquivo.deleteLater() self.widget_abas.removeTab(index) if self.widget_abas.count() == 1: self.widget_abas.setTabsClosable(False) return True def nova_aba(self, path="", salvar_caminho=True): """ Criar nova aba de editor de texto :param path: Caminho para o arquivo a ser aberto :param salvar_caminho: Se o caminho deve ser definido como local para salvar :return: None """ if self.widget_abas.count() == 0 or path: editor = EditorDeTexto.CodeEditor(self.widget_abas, False, path=path, salvar_caminho=salvar_caminho) else: editor = EditorDeTexto.CodeEditor(self.widget_abas, True, path=path, salvar_caminho=salvar_caminho) if self.widget_abas.count() == 1: self.widget_abas.setTabsClosable(True) identificador_aba = editor.get_nome() if len(identificador_aba) > 10: identificador_aba = identificador_aba[:10] + "..." editor.setStyleSheet("background:#252525") # Adiciona a aba se o arquivo tiver nome if editor.get_nome(): self.widget_abas.addTab(editor, identificador_aba) if editor.get_nome() == "": self.remover_aba(self.widget_abas.count() - 1) else: self.widget_abas.setCurrentIndex(self.widget_abas.count() - 1) # Define que nao eh necessario salvar pois acabou de ser aberto editor.set_salvo(True) def abrir(self, caminho=None, exemplo=True): """ Abrir arquivo .ino ou .brpp em nova aba :param caminho: endereço para abrir :return: None """ if caminho is None or not caminho: salvar_caminho = True dialogo = self.criar_dialogo_arquivo("Abrir arquivo", "Abrir") if dialogo.exec_() == QFileDialog.Accepted: caminho = dialogo.selectedFiles()[0] # Testa se o arquivo existe if os.path.exists(caminho): self.nova_aba(caminho, salvar_caminho) else: QMessageBox(QMessageBox.Warning, "Erro", "O arquivo não existe", QMessageBox.NoButton, self).show() else: self.nova_aba(caminho) widget = self.widget_abas.widget(self.widget_abas.currentIndex()) if exemplo: widget.caminho = "" def salvar(self): """ Salvar arquivo da aba atual :return: None """ editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0: return # Testa se a aba eh a de boas vindas editor.set_salvo(True) if caminho != "": if not os.path.exists(os.path.dirname(caminho)): try: os.makedirs(os.path.dirname(caminho)) except OSError as exc: # Guard against race condition if exc.errno != exc.errno.EEXIST: raise with open(editor.get_caminho(), "w") as arquivo: arquivo.write(editor.get_texto()) else: self.salvar_como() def salvar_como(self): """ Salvar arquivo atual como :return: None """ editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0: return caminho = editor.get_caminho() dialogo = self.criar_dialogo_arquivo('Salvar arquivo', 'Salvar') if dialogo.exec_() == QFileDialog.Accepted: caminho = dialogo.selectedFiles()[0] # Verifica se a pessoa selecionou a pasta ao inves do arquivo em si if not ntpath.basename(caminho).__contains__(".brpp"): caminho = os.path.join(caminho, ntpath.basename(caminho) + ".brpp") # Troca o identificador da aba identificador_aba = ntpath.basename(caminho).replace(".brpp", "") if len(identificador_aba) > 10: identificador_aba = identificador_aba[:10] + "..." self.widget_abas.setTabText(self.widget_abas.currentIndex(), identificador_aba) editor.set_caminho(caminho) self.salvar() def selecionar_texto(self, cursor, texto, indice_inicial, comprimento): """ Seleciona texto :param cursor: Cursor do documento :param texto: Texto a ser selecionado :param indice_inicial: Ponto de onde comecar a busca :param comprimento: Tamanho do texto :return cursor: Cursor com a selecao """ conteudo = self.widget_abas.widget( self.widget_abas.currentIndex()).toPlainText() indice_comeco = conteudo.find(texto, indice_inicial) if indice_comeco == -1: indice_comeco = conteudo.find(texto, 0) if not indice_comeco == -1: cursor.setPosition(indice_comeco, QTextCursor.MoveAnchor) cursor.setPosition(indice_comeco + comprimento, QTextCursor.KeepAnchor) return cursor return -1 def comentar_linha(self): """ comenta a linha :return: None """ editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0: return cursor_atual = editor.textCursor() posicao = cursor_atual.position() bloco_atual = cursor_atual.block() cursor = QTextCursor(bloco_atual) editor.setTextCursor(cursor) texto = bloco_atual.text() if texto.strip().startswith("//"): cursor = self.selecionar_texto(cursor, '/', cursor.position(), 2) cursor.removeSelectedText() cursor.setPosition(posicao - 2) editor.setTextCursor(cursor) else: editor.insertPlainText('//') cursor.setPosition(posicao + 2) editor.setTextCursor(cursor) def achar(self): """ Achar palavra chave no codigo :return: None """ editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0: return texto, ok = QInputDialog.getText(None, "Buscar", "Achar:") if ok and texto != "": cursor = editor.textCursor() cursor = self.selecionar_texto(cursor, texto, cursor.position(), len(texto)) if not cursor == -1: editor.setTextCursor(cursor) def achar_e_substituir(self): """ Achar e substituir palavras chave por outras :return: None """ editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0: return texto, ok = QInputDialog.getText(None, "Achar", "Achar:") subs, ok = QInputDialog.getText(None, "Substituir", "Substituir:") if ok and texto != "": cursor = editor.textCursor() cursor = self.selecionar_texto(cursor, texto, cursor.position(), len(texto)) if not cursor == -1: cursor.removeSelectedText() editor.setTextCursor(cursor) editor.insertPlainText(subs) return @staticmethod def criar_dialogo_arquivo(titulo, acao): """ Cria dialogo personalizado para buscar arquivos :param titulo: Titulo de aba :param acao: Texto do botao de selecionar :return dialogo: dialogo """ dialogo = QFileDialog() dialogo.setWindowTitle(titulo) dialogo.setLabelText(QFileDialog.FileName, "Arquivo:") dialogo.setLabelText(QFileDialog.LookIn, "Buscar em:") dialogo.setLabelText(QFileDialog.FileType, "Tipo de arquivo:") dialogo.setLabelText(QFileDialog.Accept, acao) dialogo.setLabelText(QFileDialog.Reject, "Cancelar") dialogo.setNameFilters( ["Rascunhos Br.ino (*.brpp)", "Rascunhos Arduino (*.ino)"]) dialogo.selectNameFilter("Rascunhos Br.ino (*.brpp)") dialogo.setDirectory(get_caminho_padrao()) return dialogo def abrir_serial(self): """ Abre o monitor serial :return: None """ self.parent.abrir_serial() def carregar_hardware(self, pasta): """ Carrega as opcoes de hardware do Arduino :param pasta: Diretorio do hardware :return: None """ if not os.path.isdir(pasta): return lista = [ os.path.join(pasta, pasta_) for pasta_ in os.listdir(pasta) if os.path.isdir(os.path.join(pasta, pasta_)) ] if len(lista) == 0: return lista = sorted(lista, key=str.lower) lista.remove(os.path.join(pasta, "tools")) for item in lista: nome_item = os.path.basename(item) if nome_item in self.pacotes: pacote_alvo = self.pacotes.get(nome_item) else: pacote_alvo = PacoteAlvo(nome_item) self.pacotes[nome_item] = pacote_alvo self.carregar_pacote_alvo(pacote_alvo, item) def carregar_hardware_contribuido(self, indexer): """ :param indexer: Indexador de contribuicoes :return: None """ for pacote in indexer.criar_pacotes_alvo(): if self.pacotes.get(pacote.get_id(), False): self.pacotes[pacote.get_id()] = pacote @staticmethod def carregar_pacote_alvo(pacote_alvo, pasta): """ Carrega o pacote alvo :param pacote_alvo: Pacote de hardware :param pasta: Diretorio do pacote :return: None """ pastas = os.listdir(pasta) if len(pastas) == 0: return for item in pastas: plataforma_alvo = PlataformaAlvo(item, os.path.join(pasta, item), pacote_alvo) pacote_alvo.get_plataformas()[item] = plataforma_alvo def criar_menu_placas(self): """ Cria o menu das placas :return: None """ self.menus_personalizados = list() titulos_menus_personalizados = list() for pacote_alvo in self.pacotes.values(): for plataforma_alvo in pacote_alvo.get_lista_plataformas(): titulos_menus_personalizados += plataforma_alvo.get_menus( ).values() for titulo_menu_personalizado in titulos_menus_personalizados: menu = QMenu(titulo_menu_personalizado) self.menus_personalizados.append(menu) placas = QActionGroup(self.parent) placas.setExclusive(True) for pacote_alvo in self.pacotes.values(): for plataforma_alvo in pacote_alvo.get_lista_plataformas(): nome = plataforma_alvo.get_preferencias().get("name") self.parent.menu_placas.addAction(QAction(nome, self)) for placa in plataforma_alvo.get_placas().values(): if not placa.get_preferencias().get('hide'): self.parent.menu_placas.addAction( placa.criar_acao(self)) def criar_menu_portas(self): """ Cria o menu das portas :return: None """ for acao in self.parent.menu_portas.actions(): self.parent.menu_portas.removeAction(acao) portas = QActionGroup(self.parent) portas.setExclusive(True) n_portas = len(self.serial_ports()) if n_portas > 0: for porta in self.serial_ports(): porta_acao = Porta.criar_acao(porta, self) self.parent.menu_portas.addAction(porta_acao) if n_portas == 1: Preferencias.set('serial.port', porta) else: self.parent.menu_portas.addAction( QAction("Não há portas disponíveis", self)) def criar_menu_exemplos(self): """ Cria o menu exemplos :return: None """ caminho_exemplos = os.path.join('recursos', 'exemplos') pastas_exemplo = [ x for x in os.listdir(caminho_exemplos) if os.path.isdir(os.path.join(caminho_exemplos, x)) ] pastas_exemplo.sort() for pasta_exemplo in pastas_exemplo: menu = self.parent.menu_exemplos.addMenu(pasta_exemplo) for exemplo in os.listdir( os.path.join(caminho_exemplos, pasta_exemplo)): exemplo_acao = QAction(exemplo, self) caminho_exemplo = os.path.join(caminho_exemplos, pasta_exemplo, exemplo, exemplo + ".brpp") menu.addAction(exemplo_acao) exemplo_acao.triggered.connect( functools.partial(self.abrir, caminho_exemplo, True)) def on_troca_placa_ou_porta(self): """ Troca a placa :return: None """ plataforma = self.get_plataforma_alvo() pastas_bibliotecas = list() # if plataforma: # core = self.get_preferencias_placa() pasta_plataforma = plataforma.get_pasta() pastas_bibliotecas.append(os.path.join(pasta_plataforma, 'libraries')) pastas_bibliotecas.append( os.path.join(get_caminho_padrao(), 'bibliotecas')) def get_preferencias_placa(self): """ Busca as preferencias da palca que esta sendo utilizada :return prefs: Retorna as preferencias """ placa_alvo = self.get_placa_alvo() if placa_alvo is None: return None id_placa = placa_alvo.get_id() prefs = placa_alvo.get_preferencias() nome_extendido = prefs.get("name") for id_menu in placa_alvo.get_ids_menus(): if not placa_alvo.tem_menu(id_menu): continue entrada = Preferencias.get("custom_" + id_menu) if entrada is not None and entrada.startswith(id_placa): id_selecao = entrada[len(id_placa) + 1:] prefs.update( placa_alvo.get_preferencias_menu(id_menu, id_selecao)) nome_extendido += ", " + placa_alvo.get_label_menu( id_menu, id_selecao) prefs['name'] = nome_extendido ferramentas = list() plataforma = self.indexer.get_plataforma_contribuida( self.get_plataforma_alvo()) if plataforma is not None: ferramentas.extend(plataforma.get_ferramentas_resolvidas()) core = prefs.get("build.core") if core is not None and core.__contains__(":"): separado = core.split(":") referenciada = self.get_plataforma_atual_do_pacote(separado[0]) if referenciada is not None: plat_referenciada = self.indexer.get_plataforma_contribuida( referenciada) ferramentas.extend( plat_referenciada.get_ferramentas_resolvidas()) prefix = "runtime.tools." for tool in ferramentas: pasta = tool.get_pasta_instalada() caminho = os.path.abspath(pasta) prefs[(prefix + tool.get_nome() + ".path")] = caminho Preferencias.set(prefix + tool.get_nome() + ".path", caminho) Preferencias.set( prefix + tool.get_nome() + "-" + tool.get_versao() + ".path", caminho) return prefs def get_placa_alvo(self): """ Busca a placa alvo :return placa alvo: """ plataforma_alvo = self.get_plataforma_alvo() if plataforma_alvo: placa = Preferencias.get('board') return plataforma_alvo.get_placa(placa) def get_plataforma_alvo(self, pacote=None, plataforma=None): """ Pega a plataforma alvo :param pacote: Pacote da plataforma :param plataforma: A plataforma :return plataforma_alvo: Plataforma alvo """ if pacote is None: pacote = Preferencias.get('target_package') if plataforma is None: plataforma = Preferencias.get('target_platform') p = self.pacotes.get(pacote) plataforma_alvo = p.get(plataforma) return plataforma_alvo def get_plataforma_atual_do_pacote(self, pacote): """ :param pacote: Pacote da plataforma :return: Retorna a plataforma alvo """ return self.get_plataforma_alvo(pacote, Preferencias.get("target_platform")) def compilar(self): """ Compila o codigo da aba atual :return: None """ self.salvar() self.log.clear() editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0 or caminho == '': return None placa_alvo = self.get_placa_alvo() plataforma_alvo = placa_alvo.get_plataforma() pacote_alvo = plataforma_alvo.get_pacote() # Transforma o codigo brpp em ino traduzir(caminho) resultado = compilar_arduino_builder(caminho, placa_alvo, plataforma_alvo, pacote_alvo, self.temp_build, self.temp_cache) self.log.insertPlainText(str(resultado, sys.stdout.encoding)) def upload(self): """ Compila e carrega o codigo da aba atual :return: None """ self.compilar() editor = self.widget_abas.widget(self.widget_abas.currentIndex()) caminho = editor.get_caminho() # Testa se a aba eh a de boas vindas if caminho == 0 or caminho == '': return None caminho = editor.get_caminho() # Ajustes do Arduino # TODO Terminar ajustes caminho_temp = self.temp_build uploader = None if uploader is None: uploader = Uploader.get_uploader_por_preferencias() uploader = Uploader.UploaderSerial(False) sucesso = False nome = os.path.basename(caminho).replace("brpp", "ino") sucesso = uploader.upload_usando_preferencias(self, caminho_temp, os.path.basename(nome)) @staticmethod def serial_ports(): """ Lista as portas seriais disponiveis :raises EnvironmentError: Plataforma desconhecida ou nao suportada :returns: Lista das portas seriais disponiveis """ if sys.platform.startswith('win'): ports = ['COM%s' % (i + 1) for i in range(256)] elif sys.platform.startswith('linux') or sys.platform.startswith( 'cygwin'): # this excludes your current terminal "/dev/tty" ports = glob.glob('/dev/tty[A-Za-z]*') elif sys.platform.startswith('darwin'): ports = glob.glob('/dev/tty.*') else: raise EnvironmentError('Unsupported platform') result = [] for port in ports: try: s = serial.Serial(port) s.close() result.append(port) except (OSError, serial.SerialException): pass return result def get_menu_personalizado_placa(self, title): for menu in self.menus_personalizados: if menu.title() == title: return menu def instalar_biblioteca(self): caminho_bibliotecas = os.path.join(get_caminho_padrao(), "bibliotecas") dialogo = QFileDialog() dialogo.setWindowTitle("Escolher biblioteca") dialogo.setLabelText(QFileDialog.FileName, "Arquivo:") dialogo.setLabelText(QFileDialog.LookIn, "Buscar em:") dialogo.setLabelText(QFileDialog.FileType, "Tipo de arquivo:") dialogo.setLabelText(QFileDialog.Accept, "Escolher") dialogo.setLabelText(QFileDialog.Reject, "Cancelar") dialogo.setFileMode(QFileDialog.DirectoryOnly) dialogo.setDirectory(get_caminho_padrao()) if dialogo.exec_() == QFileDialog.Accepted: caminho = dialogo.selectedUrls()[0].path() if (caminho.startswith("/") and os.name == 'nt'): caminho = caminho[1:] # Testa se o arquivo existe if os.path.exists(caminho): try: shutil.copytree( caminho, os.path.join(caminho_bibliotecas, os.path.basename(caminho))) # Directories are the same except shutil.Error as e: print('Directory not copied. Error: %s' % e) # Any error saying that the directory doesn't exist except OSError as e: print('Directory not copied. Error: %s' % e) else: QMessageBox(QMessageBox.Warning, "Erro", "O arquivo não existe", QMessageBox.NoButton, self).show() else: return
class MsUiDesign(QMainWindow): def __init__(self, parent=None): self.appctxt = ApplicationContext() super(MsUiDesign, self).__init__(parent) self.setWindowTitle("MikroScript") self.setStyleSheet(COLOR_BG) self.resize(1128, 768) self.setMinimumSize(1128, 768) self.setWindowIcon(QIcon(self.appctxt.get_resource("favicon.png"))) # Updated by Pinkesh Shah on 29-Apr-20 # Start Region self.activeJobTabs = [] # End Region self.tabCtr = 0 self.tabview = QTabWidget() # self.tabview.setStyleSheet(CSS.CSS_TAB) self.conn = sqlite3.connect("MikroScript.db") self.changeStyle('Windows') self.createMenuBar() self.tree_dock_widget() self.card_dock_widget() self.create_dock_widget_area() # self.createBottomArea() # mainLayout = QGridLayout() # # mainLayout.addLayout(self.TopArea , 0, 0, 1, 3) # # mainLayout.addLayout(self.LeftArea , 1, 0, 1, 1) # mainLayout.addLayout(self.RightArea , 1, 1, 1, 2) # # mainLayout.addLayout(self.BottomArea, 2, 0, 1, 3) # # mainLayout.addWidget(self.topLeftGroupBox, 1, 0) # # mainLayout.addWidget(self.topRightGroupBox, 1, 1) # # mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0) # # mainLayout.addWidget(self.bottomRightGroupBox, 2, 1) # # mainLayout.addWidget(self.progressBar, 3, 0, 1, 2) # # mainLayout.setRowStretch(1, 1) # mainLayout.setColumnStretch(1, 1) # mainLayout.setColumnStretch(2, 1) # self.plusButton.setFixedSize(20, 20) # Small Fixed size addButton = QPushButton( QIcon(self.appctxt.get_resource("add-24px.svg")), "") addButton.clicked.connect(self.ActionNew) self.tabview.setMovable(True) self.tabview.setTabsClosable(True) self.tabview.tabCloseRequested.connect(self.removeTab) self.tabview.setCornerWidget(addButton) # self.tabview = CustomTabWidget() self.setCentralWidget(self.tabview) self.ActionNew() self.get_job_list() def removeTab(self, index): tab = self.tabview.widget(index) # Updated by Pinkesh Shah on 30-Apr-20 # Start Region tab_Name = self.tabview.tabText(index) self.activeJobTabs.remove(tab_Name) # End Region if tab is not None: tab = None self.tabview.removeTab(index) def createMenuBar(self): actNew = QAction( QIcon( self.appctxt.get_resource( "outline_insert_drive_file_black_18dp.png")), "&New") actNew.triggered.connect(self.ActionNew) actNew.setShortcut(QKeySequence.New) # actOpen = QAction(QIcon(self.appctxt.get_resource("outline_folder_black_18dp.png")), "&Open") # actOpen.triggered.connect(self.ActionOpen) # actOpen.setShortcut(QKeySequence.Open) actSave = QAction( QIcon(self.appctxt.get_resource("outline_save_black_18dp.png")), "&Save") actSave.triggered.connect(self.ActionSave) actSave.setShortcut(QKeySequence.Save) actQuit = QAction( QIcon( self.appctxt.get_resource( "outline_exit_to_app_black_18dp.png")), "&Quit") actQuit.triggered.connect(self.ActionQuit) actQuit.setShortcut(QKeySequence.Quit) self.actExecute = QAction( QIcon( self.appctxt.get_resource( "outline_play_circle_outline_black_18dp.png")), "E&xecute") self.actExecute.triggered.connect(self.ActionExecute) self.actExecute.setShortcut(QKeySequence.Refresh) actPause = QAction( QIcon(self.appctxt.get_resource("pause_circle_outline-24px.svg")), "Pause") actPause.triggered.connect(self.ActionPause) actPause.setShortcut(QKeySequence.Refresh) actStop = QAction(QIcon(self.appctxt.get_resource("stop-24px.svg")), "Stop") actStop.triggered.connect(self.ActionStop) actStop.setShortcut(QKeySequence.Refresh) actReset = QAction( QIcon(self.appctxt.get_resource("outline_replay_black_18dp.png")), "&Reset") actReset.triggered.connect(self.ActionReset) actReset.setShortcut(QKeySequence.Replace) # actCut = QAction(QIcon(self.appctxt.get_resource("outline_file_copy_black_18dp.png")), "Cu&t") # actCut.setShortcut(QKeySequence.Cut) # # actCopy = QAction(QIcon(self.appctxt.get_resource("outline_insert_drive_file_black_18dp.png")), "&Copy") # actCopy.setShortcut(QKeySequence.Copy) # # actPaste = QAction(QIcon(self.appctxt.get_resource("outline_insert_drive_file_black_18dp.png")), "&Paste") # actPaste.setShortcut(QKeySequence.Paste) # actAbout = QAction("&About") # menuFile = self.menuBar().addMenu("&File") # menuFile.addAction(actNew) # # menuFile.addAction(actOpen) # menuFile.addAction(actSave) # menuFile.addSeparator() # menuFile.addAction(actQuit) # menuEdit = self.menuBar().addMenu("&Edit") # menuEdit.addAction(actCut) # menuEdit.addAction(actCopy) # menuEdit.addAction(actPaste) # menuView = self.menuBar().addMenu("&View") # menuRun = self.menuBar().addMenu("&Run") # menuRun.addAction(self.actExecute) # menuWin = self.menuBar().addMenu("&Window") # menuHelp = self.menuBar().addMenu("Help") # menuHelp.addAction(actAbout) ################################################################### toolbtnNew = QToolButton() toolbtnNew.setDefaultAction(actNew) toolbtnNew.setToolTip("New Job - CTRL + N") # toolbtnOpen = QToolButton() # toolbtnOpen.setDefaultAction(actOpen) # toolbtnOpen.setToolTip("Open File") toolbtnSave = QToolButton() toolbtnSave.setDefaultAction(actSave) toolbtnSave.setToolTip("Save File") toolbtnExecute = QToolButton() toolbtnExecute.setDefaultAction(self.actExecute) toolbtnExecute.setToolTip("Execute - F5") toolbtnPause = QToolButton() toolbtnPause.setDefaultAction(actPause) toolbtnPause.setToolTip("Pause") toolbtnStop = QToolButton() toolbtnStop.setDefaultAction(actStop) toolbtnStop.setToolTip("Stop") toolbtnReset = QToolButton() toolbtnReset.setDefaultAction(actReset) toolbtnReset.setToolTip("Reset") styleComboBox = QComboBox() styleComboBox.addItems(QStyleFactory.keys()) styleLabel = QLabel("&Style:") styleLabel.setBuddy(styleComboBox) styleComboBox.activated[str].connect(self.changeStyle) self.toolBar = QToolBar() self.toolBar.addWidget(toolbtnNew) # toolBar.addWidget(toolbtnOpen) self.toolBar.addWidget(toolbtnSave) self.toolBar.addSeparator() self.toolBar.addWidget(toolbtnExecute) self.toolBar.addWidget(toolbtnPause) self.toolBar.addWidget(toolbtnStop) self.toolBar.addWidget(toolbtnReset) self.toolBar.addWidget(styleComboBox) self.toolBar.setMovable(False) self.toolBar.addSeparator() # toolBar.addWidget(styleLabel) # toolBar.addWidget(styleComboBox) self.addToolBar(self.toolBar) def build_job_frame(self, name): job_frame = QFrame() # job_frame. connect(self.load_job) job_frame.setMinimumSize(220, 80) # job_frame.setFrameShape(QFrame.Box) # job_frame.setFrameShadow(QFrame.Raised) job_frame.setFrameShape(QFrame.StyledPanel) job_frame.setFrameShadow(QFrame.Plain) job_frame.setLineWidth(0) job_frame.setMidLineWidth(0) # job_frame.setFrameStyle() job_frame.setStyleSheet(CSS.CSS_FRAME) layout = QHBoxLayout() label = QLabel(name) label.setStyleSheet(CSS.CSS_FRAME_MIKROTIK_LABEL) # label.setStyleSheet(CSS.CSS_FRAME_LABEL) layout.addWidget(label, 0, Qt.AlignTop) # layout.addWidget(QPushButton(QIcon(self.appctxt.get_resource("outline_play_circle_outline_black_18dp.png")), "")) # Updated by Pinkesh Shah on 27-Apr-20 # Start Region layout.addSpacing(2) vendorLabel = QLabel("Mikrotik") vendorLabel.setFixedSize(50, 15) vendorLabel.setStyleSheet(CSS.CSS_FRAME_LABEL) # mikrotikLabel.setStyleSheet(CSS.CSS_FRAME_MIKROTIK_LABEL) layout.addWidget(vendorLabel, 0, Qt.AlignTop) # End Region img = QImage( self.appctxt.get_resource( "outline_play_circle_outline_black_18dp.png")) lbl_img = QLabel("") lbl_img.setPixmap(QPixmap.fromImage(img)) # lbl_img.resize(img.width(), img.height()) job_frame.setLayout(layout) # count = self.dock_layout.count() self.dock_layout.addWidget(job_frame) # Updated by Pinkesh Shah on 27-Apr-20 # Start Region self.dock_layout.setSpacing(10) # End Region # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region self.groupBox.setMinimumHeight(self.groupBox.sizeHint().height() + FRAME_SIZE) # self.jobsGroupBox.setMinimumHeight(self.jobsGroupBox.sizeHint().height() + FRAME_SIZE) # End Region # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region # Updated by Pinkesh Shah on 28-Apr-20 # Start Region self.groupBox.setMaximumWidth(250) # End Region # self.jobsGroupBox.setMaximumWidth(250) # End Region # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region # self.groupBox.setStyleSheet(CSS.CSS_FRAME_GROUP_BOX) # self.jobsGroupBox.setStyleSheet(CSS.CSS_GROUP_BOX) # End Region return job_frame def tree_dock_widget(self): self.dock_widget = QTreeView() def card_dock_widget(self): # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region # self.dockWidgetGroupBox = QGroupBox("") # self.dockWidgetLayout = QVBoxLayout() # self.jobsGroupBox = QGroupBox("Jobs") # Scheduler # self.scheduledGroupBox = QGroupBox("Scheduled") # self.scheduledLayout = QVBoxLayout() # self.scheduledGroupBox.setLayout(self.scheduledLayout) # self.scheduledScroll = QScrollArea() # self.scheduledScroll.setWidget(self.scheduledGroupBox) # self.scheduledScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) # self.scheduledLayout.addWidget(QLabel("Test Label")) # self.dockWidgetGroupBox.setLayout(self.dockWidgetLayout) # End Region self.dock_layout = QVBoxLayout() # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region self.groupBox = QGroupBox("") self.groupBox.setLayout(self.dock_layout) self.groupBox.setStyleSheet(CSS.CSS_FRAME_GROUP_BOX) # self.jobsGroupBox.setLayout(self.dock_layout) # End Region # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region # Updated by Pinkesh Shah on 27-Apr-20 # Start Region # self.groupBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.groupBox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # End Region # self.jobsGroupBox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # End Region self.dock_widget = QScrollArea() self.dock_widget.setStyleSheet(CSS.CSS_SCROLLBAR) # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region self.dock_widget.setWidget(self.groupBox) # self.dock_widget.setWidget(self.jobsGroupBox) # End Region self.dock_widget.setWidgetResizable(True) # Updated by Pinkesh Shah on 28-Apr-20 # Start Region self.dock_widget.setMinimumWidth(270) self.dock_widget.setMaximumWidth(400) self.dock_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # End Region # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region # self.dockWidgetLayout.addWidget(self.dock_widget) # self.dockWidgetLayout.addWidget(self.scheduledScroll) # End Region def create_dock_widget_area(self): docWidget = QDockWidget("Script Jobs") # Updated by Pinkesh Shah on 05-May-20 to disable Scheduler layout # Start Region docWidget.setWidget(self.dock_widget) # docWidget.setWidget(self.dockWidgetGroupBox) # End Region # docWidget.setLayout(layout) docWidget.DockWidgetFeatures(QDockWidget.DockWidgetVerticalTitleBar) docWidget.setFloating(False) self.addDockWidget(Qt.LeftDockWidgetArea, docWidget, Qt.Vertical) docWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) def createBottomArea(self): status = QStatusBar() self.setStatusBar(status) ################# All Action starts from here def ActionOpen(self): print("Open") def ActionPause(self): print("Pause") def ActionStop(self): print("Stop") def ActionReset(self): print("Reset") def ActionQuit(self): print("Quit") def ActionSave(self): self.create_table() current_tab_index = self.tabview.currentIndex() if current_tab_index >= 0: tab = self.tabview.currentWidget() job_data = tab.property("OBJECT").get_job_data() if len(job_data) > 0: if tab.property("NAME") == "": win = SaveWindow() if win.is_accepted(): name = win.get_name() _id = self.get_id() tab.setToolTip(name) tab.setWindowTitle(name) tab.setProperty("NAME", name) tab.setProperty("_ID", _id) string = "insert into jobs (id, name, jobs_data, created_on) values (?, ?, ?, ?)" values = [ _id, name, json.dumps(job_data), datetime.now() ] self.build_job_frame(name) self.conn.execute(string, values) self.conn.commit() else: string = "update jobs set name = ?, jobs_data = ?, updated_on = ? where id = ?" values = [ tab.property("NAME"), json.dumps(job_data), datetime.now(), tab.property("_ID") ] self.conn.execute(string, values) self.conn.commit() def ActionNew(self, name=None): try: print("Sub-class") self.tabCtr += 1 tab = QWidget() ## tab.setStyleSheet(CSS.CSS_TAB_BASE) ## tab.setStyleSheet("{ border-style: solid; background-color:#FBFBFB }") center = ScriptExecutor() result = center.getExecutorLayout() tab.setLayout(result) tab.setProperty("OBJECT", center) tab.setProperty("TAB_CTR", self.tabCtr) tab.setProperty("NAME", "") tab.setProperty("_ID", 0) # center.getFirstWidget().setFocus(Qt.ActiveWindowFocusReason) if name == None: name = "Untitled " + str(self.tabCtr) self.tabview.addTab(tab, name) self.tabview.setCurrentWidget(tab) center.host.setFocus() except Exception: raise return tab def ActionExecute(self): current_tab_index = self.tabview.currentIndex() if current_tab_index >= 0: tab = self.tabview.currentWidget() tab.property("OBJECT").Execute() def load_job(self, frame, event): job_data = json.loads(frame.property(JOB_DATA)) # Updated by Pinkesh Shah on 30-Apr-20 # Start Region if frame.property(NAME) not in self.activeJobTabs: self.activeJobTabs.append(frame.property(NAME)) tab = self.ActionNew(frame.property(NAME)) center = tab.property("OBJECT") center.LoadData(job_data) # End Region # Updated by Pinkesh Shah on 02-May-20 # Start Region else: for i in range(self.tabview.count()): if frame.property(NAME) == self.tabview.tabText(i): self.tabview.setCurrentIndex(i) # End Region def create_table(self): cursor = self.conn.execute( ''' SELECT count(name) FROM sqlite_master WHERE type='table' AND name='Jobs' ''' ) # if the count is 1, then table exists if not cursor.fetchone()[0] == 1: string = "create table Jobs (id INT PRIMARY KEY NOT NULL, name text, jobs_data json, " \ "created_on CURRENT_TIMESTAMP, " \ "updated_on CURRENT_TIMESTAMP )" self.conn.execute(string) print("Create Statement:", string) def get_id(self): cursor = self.conn.execute(''' SELECT MAX(id) as id FROM jobs ''') _id = 0 for rec in cursor: _id = (0 if rec[0] == None else rec[0]) _id = _id + 1 cursor.close() return _id def get_job_list(self): string = "select id, name, jobs_data from Jobs order by id" cursor = self.conn.execute(string) for rec in cursor: frame = self.build_job_frame(rec[1]) frame.setProperty(_ID, rec[0]) frame.setProperty(NAME, rec[1]) frame.setProperty(JOB_DATA, rec[2]) frame.mouseDoubleClickEvent = partial(self.load_job, frame) def changeStyle(self, styleName): QApplication.setStyle(QStyleFactory.create(styleName))
class ClientTabManager(QWidget): opened_tabs = [] tab_counter = 0 client = None def __init__(self, parent, clt): super().__init__(parent) self.client_layout = QVBoxLayout(self) self.client = clt # Initialize tab screen self.client_tabs = QTabWidget() self.client_tabs.setTabsClosable(False) self.client_tabs.tabCloseRequested.connect(self.close_tab) self.client_tabs.tabBarClicked.connect(lambda x: print(x)) self.client_tabs.resize(900,600) # Add tabs self.add_tab_button = QToolButton(self) self.add_tab_button.setText('+') font = self.add_tab_button.font() font.setBold(True) self.add_tab_button.setFont(font) self.client_tabs.setCornerWidget(self.add_tab_button) self.add_tab_button.clicked.connect(self.add_tab) tab1 = QWidget() self.setup_tab(tab1) self.opened_tabs.append((tab1, "New Tab")) self.client_tabs.addTab(self.opened_tabs[0][0], self.opened_tabs[0][1]) # Add tabs to widget self.client_layout.addWidget(self.client_tabs) self.setLayout(self.client_layout) def connect(self, ip, port, layout): #old_layout = copy.deepcopy(layout) deleteLayout(layout) try: self.client.connect_to_server(ip, int(port)) msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText("Connection successful!") msg.setWindowTitle("Connection info") msg.exec_() layout.addWidget(NetworkFolderManager(self, self.client)) except FailedToConnect as e: msg = QMessageBox() msg.setIcon(QMessageBox.Error) msg.setText("Connection Failed!") msg.setInformativeText(str(e)) msg.setWindowTitle("Connection info") msg.exec_() self.setup_tab(layout) except ClientAlreadyExists as e: msg = ReconnectDialog() msg.exec_(); """msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText("Connection Failed!") msg.setInformativeText(str(e)) msg.setWindowTitle("Connection info") msg.exec_() self.setup_tab(layout)""" def setup_tab(self, tab_widget): tab1_layout = QVBoxLayout() conn_btn = QPushButton("Connect...") conn_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btn_layout = QHBoxLayout() btn_layout.addLayout(QVBoxLayout(), 2) btn_layout.addWidget(conn_btn, 3) btn_layout.addLayout(QVBoxLayout(), 2) ip_layout = QHBoxLayout() ip_main_layout = QHBoxLayout() ip_label = QLabel("IP Address: ") ip_edit = QLineEdit() ip_layout.addWidget(ip_label) ip_layout.addWidget(ip_edit) ip_main_layout.addLayout(QVBoxLayout(), 2) ip_main_layout.addLayout(ip_layout, 3) ip_main_layout.addLayout(QVBoxLayout(), 2) port_layout = QHBoxLayout() port_main_layout = QHBoxLayout() port_label = QLabel("Port: ") port_edit = QLineEdit() port_edit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) port_layout.addWidget(port_label) port_layout.addWidget(port_edit) port_main_layout.addLayout(QVBoxLayout(), 2) port_main_layout.addLayout(port_layout, 3) port_main_layout.addLayout(QVBoxLayout(), 2) tab_main = QVBoxLayout() tab1_layout.addLayout(ip_main_layout, 2) tab1_layout.addLayout(port_main_layout, 2) tab1_layout.addLayout(btn_layout, 2) tab1_layout.setSpacing(20) conn_btn.clicked.connect(lambda x: self.connect(ip_edit.text(), port_edit.text(), tab1_layout)) tab_main.addLayout(QVBoxLayout(), 2) tab_main.addLayout(tab1_layout, 3) tab_main.addLayout(QVBoxLayout(), 2) #tab1_layout.addStretch(100) tab_widget.setLayout(tab_main) def add_tab(self): if not self.tab_counter: self.client_tabs.setTabsClosable(True) self.tab_counter += 1 new_tab = (QWidget(), "New Tab") self.setup_tab(new_tab[0]) self.opened_tabs.append(new_tab) self.client_tabs.addTab(new_tab[0], new_tab[1]) def close_tab(self, index): if self.tab_counter == 1: self.client_tabs.setTabsClosable(False) # TODO: Close any client connections and do cleaning up here self.client_tabs.removeTab(index) self.tab_counter -= 1
class CreateApp(QMainWindow): '''The main application. The on_configure() method is called when the 'Reconfigure' button pressed on config pane.''' def __init__(self): super().__init__() self.setGeometry(10, 10, 0, 0) self.setWindowTitle('Prato Fiorito') # °°°°° START MENU BAR °°°°° menubar = self.menuBar() # Barra dei Menu # FILE fileMenu = menubar.addMenu('File') self.saving = QAction('Save', self, triggered=self.save, shortcut='Ctrl+S') self.restoring = QAction('Load', self, triggered=self.restore, shortcut='Ctrl+L') fileMenu.addAction(self.saving) fileMenu.addAction(self.restoring) exists = os.path.isfile(sys.path[0] + '/Temp/objs.pkl') if (exists == False): self.restoring.setDisabled(True) # VISTA viewMenu = menubar.addMenu('Vista') self.day_action = QAction('Day Mode', self, checkable=True, triggered=self.setDayMode, shortcut='Ctrl+D') self.day_action.setChecked(True) self.night_action = QAction('Night Mode', self, checkable=True, triggered=self.setNightMode, shortcut='Ctrl+N') self.day_action.setDisabled(True) viewMenu.addAction(self.day_action) viewMenu.addAction(self.night_action) # REGOLE rulesMenu = menubar.addMenu('Regole') rulesMenu.addAction( QAction('Open Rules', self, triggered=self.openRuleFile, shortcut='Ctrl+R')) # °°°°° END MENU BAR °°°°° # °°°°° START TAB WIDGET °°°°° self._root = QTabWidget() self._root.setCornerWidget(self._root.tabBar(), Qt.TopLeftCorner) self.setCentralWidget(self._root) # CONFIGURAZIONE self._config = ConfigPane(parameters=[('Beginner', [9, 9, 10]), ('Intermediate', [16, 16, 40]), ('Expert', [16, 30, 99]), ('Custom', [20, 30, 145])], on_configure=self.on_configure) exists = os.path.isfile(sys.path[0] + '/Temp/objs.pkl') if (exists): self.restore() # Recupero variabili salvate diff = ['Beginner', 'Intermediate', 'Expert', 'Custom'] self._config._textinputs[diff.index( self.difficulties)]['radio_btn'].setChecked(True) else: self._config._configbutton.click() # Inizializzazione del gioco # RANKING self._ranking = ShowRanking() self._root.addTab(self._config, 'Configuration') self._root.addTab(self._buttons, 'Game') self._root.addTab(self._ranking, 'Ranking') self._root.currentChanged.connect(self.resize_window) self._root.setCurrentIndex(1) # °°°°° END TAB WIDGET °°°°° # function called when the index of the clicked Tab changes def resize_window( self ): # Ridmensione l'area della finestra, in relazione allo spazio occupato dai widgets presenti if (self._root.currentIndex() == 0): self.setFixedSize(500, 500) elif (self._root.currentIndex() == 1): width = self._buttons.sizeHint().width() height = self._buttons.sizeHint().height() + 30 self.setFixedSize(QSize(width, height)) elif ((self._root.currentIndex() == 2)): width = self._ranking.sizeHint().width() + 40 height = self._ranking.sizeHint().height() + 40 self.setFixedSize(QSize(width, height)) # This is the callback we pass into the constructor of ConfigPane # so that it can call us back with the 'New Game' button is # pressed. def on_configure(self): '''Callback for re-configure request on config pane.''' self.saving.setDisabled(False) self.difficulties = self._config.get_param()[0]['difficulties'] parameters = self._config.get_param()[0]['values'] if (len(parameters) > 0): n_rows = int(parameters[0].text()) n_cols = int(parameters[1].text()) self.bombs = int(parameters[2].text()) if (n_rows < 1): n_rows = 1 elif (n_rows > 25): n_rows = 25 if (n_cols < 8): n_cols = 8 elif (n_cols > 45): n_cols = 45 if (n_rows == 1 and n_cols == 8): self.bombs = 2 elif (n_rows == 25 and n_cols == 45): self.bombs = 500 elif (n_rows // n_cols >= 1 and self.bombs > 500): self.bombs = int(n_rows * n_cols * 0.5) elif (n_cols // n_rows >= 1 and self.bombs > 500): self.bombs = int(n_rows * n_cols * 0.5) elif (self.bombs >= n_rows * n_cols): self.bombs = int(n_rows * n_cols * 0.5) self._config._textinputs[3]['values'][0].setText(str(n_rows)) self._config._textinputs[3]['values'][1].setText(str(n_cols)) self._config._textinputs[3]['values'][2].setText(str(self.bombs)) self.matrix = create_matrix_of_game(n_rows, n_cols, self.bombs) self.m_sum = compute_matrix_sum(self.matrix, n_rows, n_cols) self.path_matrix = np.asarray([[' ' for _ in range(n_cols)] for _ in range(n_rows)]) print( '__________________________________________________________________________' ) print('matrix') print() print(self.matrix) print( '__________________________________________________________________________' ) print('m_sum') print() print(self.m_sum) print( '__________________________________________________________________________' ) print() self._buttons = ButtonPane(width=n_rows, height=n_cols, bombs=self.bombs, diff=self.difficulties, matrix=self.matrix, m_sum=self.m_sum, path_matrix=self.path_matrix, saving=self.saving, restoring=self.restoring, on_configure=self.on_configure) self._buttons.manage_ranking.connect(self.set_ranking) self._root.removeTab(1) self._root.insertTab(1, self._buttons, 'Game') self._root.setCurrentIndex(1) self._buttons.timer.start(1000) def set_ranking( self, time ): # Funzione responsabile dell'inserimento in classifica di un eventuale vincitore if (self.difficulties != 'Custom'): position = Ranking(class_type=self.difficulties).compute_pos(time) if (position <= 10 and position != -1): self.time = time self._buttons.name_zone.show() self.setFixedSize(self._buttons.sizeHint()) self._buttons.submit_btn.clicked.connect( self.result_registration ) # Cliccando sul bottone 'Submit' viene chiamata la funzione 'result registration', responsabile della registrazione in classifica def result_registration(self): name = self._buttons.name.text() if (name == ''): name = 'unknown' my_elem = name + '#' + self.time Ranking(class_type=self.difficulties).insert(my_elem=my_elem) self._buttons.name_zone.hide() self.setFixedSize(self._buttons.sizeHint()) # Aggiornamento vista classifica self._root.removeTab(2) self._ranking = ShowRanking() self._root.insertTab(2, self._ranking, 'Ranking') def setDayMode(self): self.setStyleSheet('color: black') self.day_action.setDisabled(True) self.day_action.setChecked(True) self.night_action.setDisabled(False) self.night_action.setChecked(False) QDay = QPalette() self.setPalette(QDay) def setNightMode(self): self.setStyleSheet('color: gray') self.day_action.setDisabled(False) self.day_action.setChecked(False) self.night_action.setDisabled(True) self.night_action.setChecked(True) QDark = QDarkPalette() self.setPalette(QDark) def openRuleFile(self): os.system('open ' + sys.path[0] + '/Regole.pdf') def save(self): # Funzione responsabile del salvataggio della partita with open(sys.path[0] + '/Temp/objs.pkl', 'wb') as f: pickle.dump([ self.matrix, self.m_sum, self.path_matrix, self.bombs, self.difficulties, self._buttons.curr_time, self._buttons.label_1.text(), self._buttons.label_3.text() ], f) self.restoring.setDisabled(False) def restore( self ): # Funzione responsabile del caricamento della partita salvata in precedenza exists = os.path.isfile(sys.path[0] + '/Temp/objs.pkl') if (exists == True): with open(sys.path[0] + '/Temp/objs.pkl', 'rb') as f: self.matrix, self.m_sum, self.path_matrix, self.bombs, self.difficulties, curr_time, label_1, label_3 = pickle.load( f) width = self.matrix.shape[0] height = self.matrix.shape[1] self._buttons = ButtonPane(width=width, height=height, bombs=self.bombs, diff=self.difficulties, matrix=self.matrix, m_sum=self.m_sum, path_matrix=self.path_matrix, saving=self.saving, restoring=self.restoring, on_configure=self.on_configure) self._buttons.label_1.setText(label_1) self._buttons.label_3.setText(label_3) self._buttons.manage_ranking.connect(self.set_ranking) self._buttons.curr_time = curr_time self._buttons.timer.start(1000) self._root.removeTab(1) self._root.insertTab(1, self._buttons, 'Game') self._root.setCurrentIndex(1) self.saving.setDisabled(False) print( '__________________________________________________________________________' ) print('matrix') print() print(self.matrix) print( '__________________________________________________________________________' ) print('m_sum') print() print(self.m_sum) print( '__________________________________________________________________________' ) print() for i in range(width): for j in range(height): self._buttons.btn_matrix[i][j].update()
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): url = "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" if "url" in kwargs: url = kwargs["url"] print url del kwargs["url"] super(MainWindow, self).__init__(*args, **kwargs) # Tabs self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) # New tab button #self.tab_add_button_index = self.tabs.addTab(QWidget(), '+') self.add_tab_button = QToolButton() self.add_tab_button.setText('+') self.add_tab_button.setStyleSheet( 'QToolButton {border: none; margin: 4px 20px 4px 0px; height: 480px; border-left: 1px solid lightgrey; padding: 0px 4px 0px 4px; font-weight: bold; color: #5d5b59}' 'QToolButton:hover { background-color: lightgrey }' 'QToolButton:pressed { background-color: grey }') self.add_tab_button.clicked.connect(self.new_tab_clicked) self.tabs.setCornerWidget(self.add_tab_button) # Navigation bar self.navigation = NavigationBar() self.navigation.url_bar.returnPressed.connect(self.navigate_to_url) # Back self.navigation.back_btn.triggered.connect( lambda: self.tabs.currentWidget().back()) # Next self.navigation.next_btn.triggered.connect( lambda: self.tabs.currentWidget().forward()) # Reload self.navigation.reload_btn.triggered.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload.activated.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload_f5.activated.connect( lambda: self.tabs.currentWidget().reload()) # Home self.navigation.home_btn.triggered.connect(self.go_home) # Add new tab self.add_new_tab(url, "Home") # Get everything fitting in the main window self.addToolBar(self.navigation) self.setCentralWidget(self.tabs) self.show() self.setWindowTitle("ZeroNet Browser") self.setWindowIcon(QIcon("icons/zeronet-logo.svg")) self.showMaximized() def update_url_bar(self, q, browser=None): if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return url_array = q.toString().split('/')[3:] formatted_url = '/'.join(str(x) for x in url_array) self.navigation.url_bar.setText('zero://' + formatted_url) self.navigation.url_bar.setCursorPosition(0) if (self.tabs.currentWidget().can_go_back()): self.navigation.back_btn.setDisabled(False) else: self.navigation.back_btn.setDisabled(True) if (self.tabs.currentWidget().can_go_forward()): self.navigation.next_btn.setDisabled(False) else: self.navigation.next_btn.setDisabled(True) def navigate_to_url(self): # Get url url = self.navigation.url_bar.text() if url.startswith('zero://'): # ZeroNet protocol url_array = url.split('/') url = 'http://127.0.0.1:43110/' + url_array[2] elif url.startswith('http://'): # http protocol pass else: # Nothing mentionned url = 'http://127.0.0.1:43110/' + url self.tabs.currentWidget().setUrl(QUrl(url)) def go_home(self): self.tabs.currentWidget().setUrl( QUrl("http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/")) def new_tab_clicked(self): self.add_new_tab( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/", "Home") def add_new_tab(self, qurl, label): # Instead of browser it should be called WebView ! browser = Browser() browser.urlChanged.connect( lambda qurl, browser=browser: self.update_url_bar(qurl, browser)) indexTab = self.tabs.addTab(browser, label) self.tabs.setCurrentIndex(indexTab) # We need to update the url ! if qurl.startswith('zero://'): # ZeroNet protocol url_array = qurl.split('/') qurl = 'http://127.0.0.1:43110/' + url_array[2] elif qurl.startswith('http://'): # http protocol pass else: # Nothing mentionned qurl = 'http://127.0.0.1:43110/' + qurl self.tabs.currentWidget().setUrl(QUrl(qurl)) return indexTab def close_tab(self, index): if self.tabs.count() == 1: self.tabs.currentWidget().setUrl( QUrl( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" )) return self.tabs.removeTab(index)
class MainWindow(QMainWindow): """ the main render proccess of the browser including all updating methods and the webview """ def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.url = {} self.setWindowTitle("Bird-Browser") self.wordlist = [ "dev://", "bookmark://", "bookmarks://", "search://", "http://", "https://", "pwd://" ] self.tabs = QTabWidget() self.tabcreate = QPushButton("+") self.shortcreatetab = QShortcut(self) self.shortcreatetab.setKey("Ctrl+T") self.pwdman = pwdmanager.PwdManager( config["key"], f"{Path.home()}/.config/bird/database.db") if "style" in config: innerstyle = f""" color: {config['style']['color']}; background-color: {config['style']['background-color']}; """ self.setStyleSheet(f""" QMainWindow{ {innerstyle} } """) self.tabs.setStyleSheet(f""" QTabWidget{ {innerstyle} } """) else: self.setStyleSheet(""" background-color: rgb(50, 50, 50); color: rgb(255, 255, 255); """) self.tabs.setStyleSheet(""" background-color: rgb(50, 50, 50); color: rgb(255, 255, 255); """) self.tabs.clear() self.tabs.setTabsClosable(True) self.createtab() #connections self.shortcreatetab.activated.connect(self.createtab) self.tabcreate.clicked.connect(self.createtab) self.tabs.tabCloseRequested.connect(self.closetab) self.tabs.setCornerWidget(self.tabcreate) #show window self.setCentralWidget(self.tabs) self.show() def updatewin(self, browser: QWebEngineView, boolean=True): """ This Method updates the shown browser window to the current url of the search bar it also contains the search engine-integration, and bookmarking """ url = self.url[str(self.tabs.currentIndex)] if url.startswith("search://"): search = url.split("search://", 1)[1] url = config["search-engine"].format(search=search) elif url.startswith("bookmarks://"): try: name = url.split("bookmarks://", 1)[1] url = bmk.getBookmark(cursor, name)[name] except ValueError: url = "about:blank" elif url.startswith("bookmark://"): if not "|" in url.split("bookmark://", 1)[1]: url = "about:blank" else: parts = url.split("bookmark://", 1)[1].split("|") if parts[1] == "-": parts[1] = browser.url().toString() try: bmk.getBookmark(cursor, parts[0]) bmk.modifyBookmark(cursor, parts[0], parts[0], parts[1]) conn.commit() except: bmk.addBookmark(cursor, parts[0], parts[1]) conn.commit() elif url.startswith("file://"): path = url.split("file://", 1)[1] if path.startswith("~/"): path = f"{Path.home()}/{path.split('~/', 1)[1]}" try: with open(path, "r") as reqfile: reqcontent = reqfile.read() browser.page().setHtml(reqcontent) return except: print(f"Error on requested page: Path '{path}' doesn't exist") browser.page().loadUrl(QUrl("about:blank")) elif url.startswith("dev://"): url = url.split("dev://", 1)[1] if not url.startswith("https://") and not url.startswith( "http://"): url = "http://" + url self.tabs.addTab(devtools.DevToolWidget(url), "dev-tools") elif url.startswith("pwd://"): self.tabs.addTab(pwdmanager.PwdManagerWidget(self.pwdman), "Passwords") return elif "additional-search-engines" in config: for source in config["additional-search-engines"]: if url.startswith(source): search = url.split(source, 1)[1] url = config["additional-search-engines"][source].format( search=search) break else: pass else: if not url.startswith("https://") and not url.startswith( "http://"): url = "http://" + url elif not url.startswith("https://") and not url.startswith("http://"): url = "http://" + url browser.page().load(QUrl(url)) self.wordlist.append(url) def updatetext(self, text: str): """ This Method updates the internal text of the search bar """ self.url[str(self.tabs.currentIndex)] = text def updateurl(self, url, bar): """ This method updates the url bar to the currently displayed url. It gets triggered if the displayed page directs you to a different page """ bar.setText(url.toString()) def updatetab(self, arg_1, index, browser): if len(browser.page().title()) > 20: self.tabs.setTabText(index, browser.page().title()[0:17] + "...") else: self.tabs.setTabText(index, browser.page().title()) self.tabs.setTabIcon(index, browser.page().icon()) def updatetitle(self, title: str): self.tabs.setTabText(self.tabs.currentIndex(), title) def updateicon(self, index, icon): self.tabs.setTabIcon(index, icon) def closetab(self, index): if index == self.tabs.currentIndex(): self.tabs.setCurrentIndex(index - 1 if index != 0 else index + 1) self.tabs.removeTab(index) if self.tabs.count() == 0: sys.exit(0) @pyqtSlot() def createtab(self, url: str = config["startup-url"]): layout = QGridLayout() widget = QWidget() widget.setLayout(layout) bar = QLineEdit() completer = QCompleter(self.wordlist) browser = QWebEngineView() backbtn = QPushButton("←") reloadbtn = QPushButton("reload") gotocurrenturlbutton = QPushButton("go!") reloadshort = QShortcut(self) reloadshort.setKey("Ctrl+R") bar.setCompleter(completer) reloadshort.activated.connect(browser.reload) gotocurrenturlbutton.clicked.connect( lambda clicked, browser=browser: self.updatewin(browser, clicked)) reloadbtn.clicked.connect(browser.reload) bar.returnPressed.connect( lambda browser=browser: self.updatewin(browser, True)) bar.textChanged.connect(self.updatetext) browser.load(QUrl(url)) browser.page().urlChanged.connect( lambda qurl, bar=bar: self.updateurl(qurl, bar)) browser.page().loadFinished.connect( lambda arg__1, index=self.tabs.indexOf(browser), browser=browser: self.updatetab(arg__1, index, browser)) browser.page().iconChanged.connect(lambda qicon, index=self.tabs.count( ): self.updateicon(index, qicon)) browser.page().setUrlRequestInterceptor(NetworkFilter) browser.page().titleChanged.connect( lambda title=browser.page().title(): self.updatetitle(title)) backbtn.clicked.connect(browser.back) layout.addWidget(bar, 1, 3) layout.addWidget(reloadbtn, 1, 2) layout.addWidget(browser, 2, 1, 1, 5) layout.addWidget(backbtn, 1, 1) layout.addWidget(gotocurrenturlbutton, 1, 4) self.tabs.addTab(widget, browser.icon(), browser.title()) self.tabs.setCurrentIndex(self.tabs.count() - 1) def renderDevToolsView(self, webview): return self.tabs.setCurrentWidget( devtools.DevToolWidget(webview.page().url().toString()))
class MainWindow(CustomTitleBarWindowMixin, QMainWindow): hintSignal = pyqtSignal(str, str, str) # type(error, warning, info), title, msg # statusBarSignal = pyqtSignal(str, str) updateSignal = pyqtSignal(object) # countUpdateSignal = pyqtSignal(int, int) reloadWindowSignal = pyqtSignal(str, str, object) receiveCount = 0 sendCount = 0 DataPath = "./" app = None needRestart = False def __init__(self, app, eventFilter, config): QMainWindow.__init__(self) self.app = app self.eventFilter = eventFilter self.DataPath = parameters.dataPath self.config = config log.i("init main window") self.initVar() self.initWindow() self.uiLoadConfigs() log.i("init main window complete") self.loadPluginsInfoList() self.loadPluginItems() log.i("load plugin items complete") self.initEvent() def initVar(self): self.loadPluginStr = _("Load plugin from file") self.closeTimerId = None self.items = [] self.pluginClasses = [] self.helpWindow = None def loadPluginsInfoList(self): for id, pluginClass in builtinPlugins.items(): self.addPluginInfo(pluginClass) self.pluginClasses.append(pluginClass) rm = [] for uid, info in self.config["pluginsInfo"]["external"].items(): pluginClass = self._importPlugin(info["path"], test=True) if pluginClass: self.addPluginInfo(pluginClass) self.pluginClasses.append(pluginClass) else: rm.append(uid) for uid in rm: self.config["pluginsInfo"]["external"].pop(uid) # find in python packages ignore_paths = ["DLLs"] for path in sys.path: if os.path.isdir(path) and not path in ignore_paths: for name in os.listdir(path): if name.lower().startswith( "comtool_plugin_" ) and not name.endswith("dist-info"): log.i(f"find plugin package <{name}>") pluginClass = __import__(name).Plugin print(pluginClass, self.pluginClasses) if not pluginClass in self.pluginClasses: self.addPluginInfo(pluginClass) self.pluginClasses.append(pluginClass) def getPluginClassById(self, id): ''' must call after loadPluginsInfoList ''' for pluginClass in self.pluginClasses: if id == pluginClass.id: return pluginClass return None def loadPluginItems(self): items = self.config["items"] if items: for item in items: log.i("load plugin item", item["name"]) pluginClass = self.getPluginClassById(item["pluginId"]) if pluginClass: setCurr = False if self.config["currItem"] == item["name"]: setCurr = True # check language change, update item name to current lanuage old_name_tail = item["name"].split(" ")[-1] try: int(old_name_tail) item["name"] = pluginClass.name + " " + old_name_tail except Exception: # for no number tailed name item["name"] = pluginClass.name self.addItem(pluginClass, nameSaved=item["name"], setCurrent=setCurr, connsConfigs=item["config"]["conns"], pluginConfig=item["config"]["plugin"]) else: # load builtin plugins for id, pluginClass in builtinPlugins.items(): self.addItem(pluginClass) def addItem(self, pluginClass, nameSaved=None, setCurrent=False, connsConfigs=None, pluginConfig=None): ''' @name add saved item, not add new item ''' # set here, not set in arg, cause arg can lead to multi item use one object if not connsConfigs: connsConfigs = {} if not pluginConfig: pluginConfig = {} if nameSaved: name = nameSaved else: numbers = [] for item in self.items: if item.plugin.id == pluginClass.id: name = item.name.replace(item.plugin.name, "").split(" ") if len(name) > 1: number = int(name[-1]) numbers.append(number) else: numbers.append(0) if numbers: numbers = sorted(numbers) if (not numbers) or numbers[0] != 0: name = pluginClass.name else: last = numbers[0] number = -1 for n in numbers[1:]: if n != last + 1: number = last + 1 break last = n if number < 0: number = numbers[-1] + 1 name = f'{pluginClass.name} {number}' item = PluginItem(name, pluginClass, conns, connsConfigs, self.config, pluginConfig, self.hintSignal, self.reloadWindowSignal) self.tabAddItem(item) self.items.append(item) if setCurrent: self.tabWidget.setCurrentWidget(item.widget) if not nameSaved: self.config["items"].append({ "name": name, "pluginId": pluginClass.id, "config": { "conns": connsConfigs, "plugin": pluginConfig } }) return item def tabAddItem(self, item): self.tabWidget.addTab(item.widget, item.name) def addPluginInfo(self, pluginClass): self.pluginsSelector.insertItem( self.pluginsSelector.count() - 1, f'{pluginClass.name} - {pluginClass.id}') def _importPlugin(self, path, test=False): if not os.path.exists(path): return None dir = os.path.dirname(path) name = os.path.splitext(os.path.basename(path))[0] sys.path.insert(0, dir) try: print("import") pluginClass = __import__(name).Plugin except Exception as e: import traceback msg = traceback.format_exc() self.hintSignal.emit("error", _("Error"), '{}: {}'.format(_("Load plugin failed"), msg)) return None if test: sys.path.remove(dir) return pluginClass def loadExternalPlugin(self, path): extPlugsInfo = self.config["pluginsInfo"]["external"] found = False for uid, info in extPlugsInfo.items(): if info["path"] == path: for pluginClass in self.pluginClasses: # same plugin if uid == pluginClass.id: self.addItem(pluginClass, setCurrent=True) found = True break if found: return True, "" pluginClass = self._importPlugin(path) if not pluginClass: return False, _("Load plugin fail") extPlugsInfo[pluginClass.id] = {"path": path} if not pluginClass in self.pluginClasses: self.pluginClasses.append(pluginClass) self.addPluginInfo(pluginClass) # find old item config for this plugin and recover oldFound = False for item in self.config["items"]: if item["pluginId"] == pluginClass.id: self.addItem(pluginClass, nameSaved=item["name"], setCurrent=True, connsConfigs=item["config"]["conns"], pluginConfig=item["config"]["plugin"]) oldFound = True break if not oldFound: self.addItem(pluginClass, setCurrent=True) return True, "" def onPluginSelectorChanged(self, idx): text = self.pluginsSelector.currentText() if text == self.loadPluginStr: oldPath = os.getcwd() fileName_choose, filetype = QFileDialog.getOpenFileName( self, _("Select file"), oldPath, _("python script (*.py)")) if fileName_choose != "": ok, msg = self.loadExternalPlugin(fileName_choose) if not ok: self.hintSignal.emit("error", _("Error"), f'{_("Load plugin error")}: {msg}') else: loadID = text.split("-")[-1].strip() for pluginClass in self.pluginClasses: if loadID == pluginClass.id: self.addItem(pluginClass, setCurrent=True) break def initWindow(self): # set skin for utils_ui utils_ui.setSkin(self.config["skin"]) # menu layout self.settingsButton = QPushButton() self.skinButton = QPushButton("") self.languageCombobox = ButtonCombbox(icon="fa.language", btnClass="smallBtn2") self.languages = i18n.get_languages() for locale in self.languages: self.languageCombobox.addItem(self.languages[locale]) self.aboutButton = QPushButton() self.functionalButton = QPushButton() self.encodingCombobox = ComboBox() self.supportedEncoding = parameters.encodings for encoding in self.supportedEncoding: self.encodingCombobox.addItem(encoding) self.settingsButton.setProperty("class", "menuItem1") self.skinButton.setProperty("class", "menuItem2") self.aboutButton.setProperty("class", "menuItem3") self.functionalButton.setProperty("class", "menuItem4") self.settingsButton.setObjectName("menuItem") self.skinButton.setObjectName("menuItem") self.aboutButton.setObjectName("menuItem") self.functionalButton.setObjectName("menuItem") # plugins slector self.pluginsSelector = ButtonCombbox(icon="fa.plus", btnClass="smallBtn2") self.pluginsSelector.addItem(self.loadPluginStr) self.pluginsSelector.activated.connect(self.onPluginSelectorChanged) # title bar title = parameters.appName + " v" + version.__version__ iconPath = self.DataPath + "/" + parameters.appIcon log.i("icon path: " + iconPath) self.titleBar = TitleBar(self, icon=iconPath, title=title, brothers=[], widgets=[[self.skinButton, self.aboutButton], []]) CustomTitleBarWindowMixin.__init__(self, titleBar=self.titleBar, init=True) # root layout self.frameWidget = QWidget() self.frameWidget.setMouseTracking(True) self.frameWidget.setLayout(self.rootLayout) self.setCentralWidget(self.frameWidget) # tab widgets self.tabWidget = QTabWidget() self.tabWidget.setTabsClosable(True) # tab left menu tabConerWidget = QWidget() tabConerLayout = QHBoxLayout() tabConerLayout.setSpacing(0) tabConerLayout.setContentsMargins(0, 0, 0, 0) tabConerWidget.setLayout(tabConerLayout) tabConerLayout.addWidget(self.settingsButton) # tab right menu tabConerWidgetRight = QWidget() tabConerLayoutRight = QHBoxLayout() tabConerLayoutRight.setSpacing(0) tabConerLayoutRight.setContentsMargins(0, 0, 0, 0) tabConerWidgetRight.setLayout(tabConerLayoutRight) tabConerLayoutRight.addWidget(self.pluginsSelector) tabConerLayoutRight.addWidget(self.languageCombobox) tabConerLayoutRight.addWidget(self.encodingCombobox) tabConerLayoutRight.addWidget(self.functionalButton) self.tabWidget.setCornerWidget(tabConerWidget, Qt.TopLeftCorner) self.tabWidget.setCornerWidget(tabConerWidgetRight, Qt.TopRightCorner) self.contentLayout = QVBoxLayout() self.contentWidget.setLayout(self.contentLayout) self.contentLayout.setContentsMargins(10, 0, 10, 10) self.contentLayout.addWidget(self.tabWidget) if sys.platform == 'darwin': self.macOsAddDockMenu() self.resize(850, 500) self.MoveToCenter() self.show() def add_new_window(self): import copy mainWindow = MainWindow(self.app, self.eventFilter, copy.deepcopy(self.config)) self.eventFilter.listenWindow(mainWindow) g_all_windows.append(mainWindow) def macOsAddDockMenu(self): self.dockMenu = QMenu(self) self.dockMenu.addAction(_('New Window'), self.add_new_window) self.dockMenu.setAsDockMenu() self.app.setAttribute(Qt.AA_DontShowIconsInMenus, True) def initEvent(self): # menu self.settingsButton.clicked.connect(self.toggleSettings) self.languageCombobox.currentIndexChanged.connect( self.onLanguageChanged) self.encodingCombobox.currentIndexChanged.connect(lambda: self.bindVar( self.encodingCombobox, self.config, "encoding")) self.functionalButton.clicked.connect(self.toggleFunctional) self.skinButton.clicked.connect(self.skinChange) self.aboutButton.clicked.connect(self.showAbout) # main self.tabWidget.currentChanged.connect(self.onSwitchTab) self.tabWidget.tabCloseRequested.connect(self.closeTab) # others self.updateSignal.connect(self.showUpdate) self.hintSignal.connect(self.showHint) self.reloadWindowSignal.connect(self.onReloadWindow) def bindVar(self, uiObj, varObj, varName: str, vtype=None, vErrorMsg="", checkVar=lambda v: v, invert=False): objType = type(uiObj) if objType == QCheckBox: v = uiObj.isChecked() if hasattr(varObj, varName): varObj.__setattr__(varName, v if not invert else not v) else: varObj[varName] = v if not invert else not v return elif objType == QLineEdit: v = uiObj.text() elif objType == ComboBox: if hasattr(varObj, varName): varObj.__setattr__(varName, uiObj.currentText()) else: varObj[varName] = uiObj.currentText() return elif objType == QRadioButton: v = uiObj.isChecked() if hasattr(varObj, varName): varObj.__setattr__(varName, v if not invert else not v) else: varObj[varName] = v if not invert else not v return else: raise Exception("not support this object") if vtype: try: v = vtype(v) except Exception: uiObj.setText(str(varObj.__getattribute__(varName))) self.hintSignal.emit("error", _("Error"), vErrorMsg) return try: v = checkVar(v) except Exception as e: self.hintSignal.emit("error", _("Error"), str(e)) return varObj.__setattr__(varName, v) def onSwitchTab(self, idx): self.config["currItem"] = self.items[idx].name item = self.getCurrentItem() item.plugin.onActive() def closeTab(self, idx): # only one, ignore if self.tabWidget.count() == 1: return self.tabWidget.removeTab(idx) item = self.items.pop(idx) for _item in self.config["items"]: if _item["name"] == item.name: self.config["items"].remove(_item) break def updateStyle(self, widget): self.frameWidget.style().unpolish(widget) self.frameWidget.style().polish(widget) self.frameWidget.update() def onLanguageChanged(self): idx = self.languageCombobox.currentIndex() locale = list(self.languages.keys())[idx] self.config["locale"] = locale i18n.set_locale(locale) reply = QMessageBox.question( self, _('Restart now?'), _("language changed to: ") + self.languages[self.config["locale"]] + "\n" + _("Restart software to take effect now?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.needRestart = True self.close() def onReloadWindow(self, title, msg, callback): if not title: title = _('Restart now?') reply = QMessageBox.question(self, title, msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: callback(True) self.needRestart = True self.close() else: callback(False) def MoveToCenter(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def showHint(self, info_type: str, title: str, msg: str): if info_type == "info": QMessageBox.information(self, title, msg) elif info_type == "warning": QMessageBox.warning(self, title, msg) elif info_type == "error": QMessageBox.critical(self, title, msg) def closeEvent(self, event): if self.closeTimerId: event.accept() return print("----- close event") # reply = QMessageBox.question(self, 'Sure To Quit?', # "Are you sure to quit?", QMessageBox.Yes | # QMessageBox.No, QMessageBox.No) if 1: # reply == QMessageBox.Yes: self.receiveProgressStop = True # inform plugins for item in self.items: item.onDel() self.saveConfig() # actual exit after 500ms self.closeTimerId = self.startTimer(500) self.setWindowTitle(_("Closing ...")) self.titleBar.setTitle(_("Closing ...")) self.setEnabled(False) if self.helpWindow: self.helpWindow.close() event.ignore() else: event.ignore() def timerEvent(self, e): if self.closeTimerId: log.i("Close window") self.killTimer(self.closeTimerId) self.close() def saveConfig(self): # print("save config:", self.config) self.config.save(parameters.configFilePath) print("save config compelte") def uiLoadConfigs(self): # language try: idx = list(self.languages.keys()).index(self.config["locale"]) except Exception: idx = 0 self.languageCombobox.setCurrentIndex(idx) # encoding self.encodingCombobox.setCurrentIndex( self.supportedEncoding.index(self.config["encoding"])) def keyPressEvent(self, event): CustomTitleBarWindowMixin.keyPressEvent(self, event) item = self.getCurrentItem() item.onKeyPressEvent(event) def keyReleaseEvent(self, event): CustomTitleBarWindowMixin.keyReleaseEvent(self, event) item = self.getCurrentItem() item.onKeyReleaseEvent(event) def getCurrentItem(self): widget = self.tabWidget.currentWidget() for item in self.items: if item.widget == widget: return item def toggleSettings(self): widget = self.getCurrentItem().settingWidget if widget.isVisible(): self.hideSettings() else: self.showSettings() def showSettings(self): widget = self.getCurrentItem().settingWidget widget.show() self.settingsButton.setStyleSheet( parameters.strStyleShowHideButtonLeft.replace( "$DataPath", self.DataPath)) def hideSettings(self): widget = self.getCurrentItem().settingWidget widget.hide() self.settingsButton.setStyleSheet( parameters.strStyleShowHideButtonRight.replace( "$DataPath", self.DataPath)) def toggleFunctional(self): widget = self.getCurrentItem().functionalWidget if widget is None: return if widget.isVisible(): self.hideFunctional() else: self.showFunctional() def showFunctional(self): widget = self.getCurrentItem().functionalWidget if not widget is None: widget.show() self.functionalButton.setStyleSheet( parameters.strStyleShowHideButtonRight.replace( "$DataPath", self.DataPath)) def hideFunctional(self): widget = self.getCurrentItem().functionalWidget if not widget is None: widget.hide() self.functionalButton.setStyleSheet( parameters.strStyleShowHideButtonLeft.replace( "$DataPath", self.DataPath)) def skinChange(self): if self.config["skin"] == "light": # light file = open(self.DataPath + '/assets/qss/style-dark.qss', "r", encoding="utf-8") self.config["skin"] = "dark" else: # elif self.config["skin"] == 2: # dark file = open(self.DataPath + '/assets/qss/style.qss', "r", encoding="utf-8") self.config["skin"] = "light" self.app.setStyleSheet(file.read().replace("$DataPath", self.DataPath)) utils_ui.setSkin(self.config["skin"]) def showAbout(self): help = helpAbout.HelpInfo() pluginsHelp = {_("About"): help} for p in self.pluginClasses: if not p.help is None: pluginsHelp[p.name] = p.help iconPath = self.DataPath + "/" + parameters.appIcon self.helpWindow = HelpWidget(pluginsHelp, icon=iconPath) self.helpWindow.closed.connect(self.helpWindowClosed) self.eventFilter.listenWindow(self.helpWindow) def helpWindowClosed(self): self.eventFilter.unlistenWindow(self.helpWindow) self.helpWindow = None # self.helpWindow def showUpdate(self, versionInfo): versionInt = versionInfo.int() if self.config.basic["skipVersion"] and self.config.basic[ "skipVersion"] >= versionInt: return msgBox = QMessageBox() desc = versionInfo.desc if len( versionInfo.desc) < 300 else versionInfo.desc[:300] + " ... " link = '<a href="https://github.com/Neutree/COMTool/releases">github.com/Neutree/COMTool/releases</a>' info = '{}<br>{}<br><br>v{}: {}<br><br>{}'.format( _("New versioin detected, please click learn more to download"), link, '{}.{}.{}'.format(versionInfo.major, versionInfo.minor, versionInfo.dev), versionInfo.name, desc) learn = msgBox.addButton(_("Learn More"), QMessageBox.YesRole) skip = msgBox.addButton(_("Skip this version"), QMessageBox.YesRole) nextTime = msgBox.addButton(_("Remind me next time"), QMessageBox.NoRole) msgBox.setWindowTitle(_("Need update")) msgBox.setText(info) result = msgBox.exec_() if result == 0: auto = autoUpdate.AutoUpdate() auto.OpenBrowser() elif result == 1: self.config.basic["skipVersion"] = versionInt def autoUpdateDetect(self): auto = autoUpdate.AutoUpdate() needUpdate, versionInfo = auto.detectNewVersion() if needUpdate: self.updateSignal.emit(versionInfo)