class MainWidget(QWidget): def __init__(self): super().__init__() self.initMe() def initMe(self): self.v = QVBoxLayout() self.h = QHBoxLayout() # mod_window = QGridLayout() scroll = QScrollArea() self.h.addWidget(scroll) scroll.setWidgetResizable(True) self.scrollcontent = QListWidget(scroll) for mod in mods: a = QListWidgetItem(mod.name) a.mod = mod self.scrollcontent.addItem(a) scroll.setWidget(self.scrollcontent) self.scrollcontent.itemSelectionChanged.connect(self.update_RPanel) self.mod_info = RPanel(mods[0]) self.h.addWidget(self.mod_info) self.mod_info.show() searchbox = SearchBox(self) self.v.addWidget(searchbox) searchbox.show() self.v.addLayout(self.h) self.setLayout(self.v) # self.show() def update_RPanel(self): self.mod_info.setParent(None) self.mod_info.pixmap = None items = self.scrollcontent.selectedItems() if not items: return item = items[0] self.mod_info = RPanel(item.mod) self.h.addWidget(self.mod_info) self.mod_info.show() def update_RPanel_With_Search(self, keyword): items = self.scrollcontent.findItems(keyword, Qt.MatchContains) if not items: self.error = ErrorBox("No Mod found with matching name") else: item = items[0] item.setSelected(True) self.scrollcontent.scrollToItem(item, QAbstractItemView.PositionAtTop)
def remove_faulty(node_string, list_name: QtWidgets.QListWidget, all_elements=True): """Removes duplicates and nodes that shouldn't be on the list""" qlistitem = list_name.findItems(node_string, QtCore.Qt.MatchExactly) if qlistitem: if all_elements: for node in qlistitem: list_name.takeItem(list_name.row(node)) elif len(qlistitem) > 1: qlistitem.reverse() list_name.takeItem(list_name.row(qlistitem[0]))
class CustomFunctionsPanel(QWidget): save_script = QtCore.pyqtSignal() def __init__(self, settings): super().__init__() self.settings = settings # Initializing GUI elements self.lib_name = QLineEdit() self.functions_list = QListWidget() self.save_script_button = QPushButton('Save build script') # Preparing elements by giving initial values and etc self.lib_name.setPlaceholderText('Custom library name...') # Setting all widgets in their places layout = QVBoxLayout() layout.addWidget(self.lib_name) layout.addWidget(self.functions_list) layout.addWidget(self.save_script_button) self.setLayout(layout) self.save_script_button.clicked.connect(self.on_save_script) self.settings.package_changed.connect(self.reset) def on_save_script(self): self.save_script.emit() def reset(self): self.functions_list.clear() def add_function(self, function): """ Adds new function to required list :param function: name if function """ self.functions_list.addItem(QListWidgetItem(function)) def remove_function(self, function): """ Removes function from left list """ item = self.functions_list.findItems(function, QtCore.Qt.MatchExactly) if item: self.functions_list.takeItem(self.functions_list.row(item[0]))
def find_list_widget_item(self, list_widget: QListWidget): """ Находит и окрашивает выбраный в списке несовпадений элемент в заданном списке. :param list_widget: Виджет списка. :return: """ if self.sender().currentItem().text() != '': for index in range(list_widget.count()): list_widget.item(index).setBackground(QColor(255, 255, 255)) items = list_widget.findItems( self.sender().currentItem().text(), QtCore.Qt.MatchExactly) if len(items) > 0: for item in items: item.setBackground(QColor("red")) list_widget.scrollToItem(item, QAbstractItemView.PositionAtCenter)
def fill_list_widget(self, dictionary: dict, list_widget: QListWidget, counter=None): """ Заполняет list widget словарем. :param dictionary: Словарь. :param list_widget: Объект list widget :param counter: Счетчик (только для поиска несовпадений). :return: """ for key in sorted(dictionary): list_widget.addItem(key) items = list_widget.findItems(key, QtCore.Qt.MatchExactly) if len(items) > 0: for item in items: item.setFont(QFont("Serif", 10, QFont.Bold)) for i in sorted(dictionary[key]): list_widget.addItem(i) if counter is not None: counter[0] += 1 list_widget.addItem('')
class RecycleApp(QWidget): def __init__(self, parent=None): super().__init__(parent) self.dataController = DataController() # load data try: self.dataController.loadData() except (EOFError): self.dataController.parsingData() self.dataController.saveData(self.dataController.recycles) finally: self.recycleData = self.dataController.loadData() # listWindow self.listWindow = QListWidget(self) self.listWindow.setMinimumSize(700, 200) for data in self.recycleData: item = QListWidgetItem() item.setText(data["name"]) self.listWindow.addItem(item) self.listWindow.sortItems() # list layout listLayout = QGridLayout() listLayout.addWidget(self.listWindow, 0, 0) # status layout statusLayout = QGridLayout() # dump label self.dumpTitle = QLabel("How to dump") self.dumpTitle.setAlignment(Qt.AlignLeft) font = self.dumpTitle.font() font.setPointSize(font.pointSize() + 8) font.setWeight(75) self.dumpTitle.setFont(font) statusLayout.addWidget(self.dumpTitle, 0, 0, 1, 3) # dump result self.dumpResult = QLineEdit() self.dumpResult.setReadOnly(True) statusLayout.addWidget(self.dumpResult, 1, 0, 1, 2) # dump check button self.checkButton = QToolButton() self.checkButton.setText("check") self.checkButton.setMinimumWidth(60) self.checkButton.clicked.connect(self.checkBtnClicked) statusLayout.addWidget(self.checkButton, 1, 2) # add name label self.addNameLabel = QLabel("Name: ") statusLayout.addWidget(self.addNameLabel, 2, 0) # add name self.addName = QLineEdit() statusLayout.addWidget(self.addName, 2, 1) # add dump label self.addDumpLabel = QLabel("Dump: ") statusLayout.addWidget(self.addDumpLabel, 3, 0) # add dump self.addDump = QLineEdit() statusLayout.addWidget(self.addDump, 3, 1) # add button self.addButton = QToolButton() self.addButton.setText("Add") self.addButton.setMinimumHeight(50) self.addButton.setMinimumWidth(60) self.addButton.clicked.connect(self.addBtnClicked) statusLayout.addWidget(self.addButton, 2, 2, 2, 1) # search self.searchName = QLineEdit() self.searchName.setPlaceholderText("search for name") statusLayout.addWidget(self.searchName, 4, 0, 1, 2) # search button self.searchButton = QToolButton() self.searchButton.setText("search") self.searchButton.setMinimumWidth(60) self.searchButton.clicked.connect(self.searchBtnClicked) statusLayout.addWidget(self.searchButton, 4, 2) # delete self.deleteName = QLineEdit() self.deleteName.setPlaceholderText("delete for name") statusLayout.addWidget(self.deleteName, 5, 0, 1, 2) #delete button self.deleteButton = QToolButton() self.deleteButton.setText("delete") self.deleteButton.setMinimumWidth(60) self.deleteButton.clicked.connect(self.delBtnClicked) statusLayout.addWidget(self.deleteButton, 5, 2) # mainlayout mainLayout = QGridLayout() mainLayout.addLayout(listLayout, 0, 0) mainLayout.addLayout(statusLayout, 1, 0) # alert self.alert = QMessageBox() self.setWindowTitle('분리배출') self.setLayout(mainLayout) def checkBtnClicked(self): for item in self.recycleData: if item["name"] == self.listWindow.currentItem().text(): self.dumpResult.setText(item["dump"]) def addBtnClicked(self): if self.addName.text() == "" or self.addDump.text() == "": self.alert.information(self, "alert", "can`t add blank") else: same = self.listWindow.findItems(self.addName.text(), Qt.MatchExactly) if len(same) > 0: self.alert.information(self, "alert", "already exist") else: item = {} item["name"] = self.addName.text() item["dump"] = self.addDump.text() self.recycleData.append(item) # save data to recycle.dat self.dataController.saveData(self.recycleData) self.listWindow.addItem(self.addName.text()) self.listWindow.repaint() self.listWindow.sortItems() self.addName.clear() self.addDump.clear() def searchBtnClicked(self): sameList = self.listWindow.findItems(self.searchName.text(), Qt.MatchExactly) if len(sameList) > 0: sameList[0].setSelected(True) self.listWindow.scrollToItem(sameList[0], QAbstractItemView.PositionAtTop) self.searchName.clear() else: self.alert.information(self, "alert", "no such item") def delBtnClicked(self): deleteList = self.listWindow.findItems(self.deleteName.text(), Qt.MatchExactly) if len(deleteList) > 0: for item in self.recycleData: if item["name"] == self.deleteName.text(): self.recycleData.remove(item) # save data to recycle.dat self.dataController.saveData(self.recycleData) self.listWindow.takeItem(self.listWindow.row(deleteList[0])) self.deleteName.clear() else: self.alert.information(self, "alert", "no such item")
class MainWindow(CenterWindow): """ Displays list with tasks assigned to current user in JIRA """ def __init__(self, controller): super().__init__() self.setStyleSheet(QSS) self.controller = controller self.resize(1000, 600) self.setWindowTitle('JIRA Quick Reporter') self.setWindowIcon(QIcon(LOGO_PATH)) self.center() self.current_item = None self.vbox = QVBoxLayout() self.save_btn_box = QHBoxLayout() self.filter_name_label = QLabel() self.filter_name_label.setObjectName('filter_name_label') self.filter_name_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.filter_name_label.setAlignment(Qt.AlignLeft) self.filter_edited_label = QLabel('-> edited') self.filter_edited_label.setObjectName('filter_edited_label') self.filter_edited_label.hide() self.filter_edited_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.save_filter_btn = QPushButton('Save as') self.save_filter_btn.setObjectName('save_filter_btn') self.save_filter_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.save_filter_btn.clicked.connect(self.controller.save_filter) self.overwrite_filter_button = QPushButton('Save') self.overwrite_filter_button.setToolTip('You need to edit filter query first') self.overwrite_filter_button.setObjectName('save_filter_btn') self.overwrite_filter_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.overwrite_filter_button.clicked.connect(lambda: self.controller.save_filter(True)) self.delete_filter_btn = QPushButton() self.delete_filter_btn.setObjectName('delete_filter_btn') self.delete_filter_btn.clicked.connect(self.delete_filter) self.delete_filter_btn.setIcon(QIcon(DELETE_FILTER_ICON)) self.delete_filter_btn.setIconSize( QSize( self.delete_filter_btn.sizeHint().height(), self.delete_filter_btn.sizeHint().height() ) ) self.delete_filter_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.delete_filter_btn.setToolTip('Delete filter') self.save_btn_box.addWidget(self.filter_name_label, Qt.AlignLeft) self.save_btn_box.addWidget(self.filter_edited_label, Qt.AlignLeft) self.save_btn_box.addWidget(self.save_filter_btn, Qt.AlignLeft) self.save_btn_box.addWidget(self.overwrite_filter_button, Qt.AlignLeft) self.save_btn_box.addStretch() self.save_btn_box.addWidget(self.delete_filter_btn, Qt.AlignRight) self.create_filter_box = QHBoxLayout() self.query_field = QLineEdit() self.query_field.setObjectName('query_field') self.query_field.setPlaceholderText('You need to write a query here') self.action_help = QAction() self.action_help.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxQuestion)) self.help_filter_url = QUrl(FILTER_FIELD_HELP_URL) self.action_help.triggered.connect(self.filter_field_help) self.query_field.addAction(self.action_help, QLineEdit.TrailingPosition) self.query_field.installEventFilter(self) self.search_issues_button = QPushButton('Search') self.search_issues_button.setObjectName('search_issues_button') self.search_issues_button.clicked.connect(self.controller.search_issues_by_query) self.create_filter_box.addWidget(self.query_field) self.create_filter_box.addWidget(self.search_issues_button) self.list_box = QVBoxLayout() self.issue_list_widget = QListWidget() self.issue_list_widget.setObjectName('issue_list') self.label_info = QLabel('You have no issues.') self.label_info.setAlignment(Qt.AlignCenter) self.list_box.addWidget(self.issue_list_widget) self.list_box.addWidget(self.label_info) self.label_info.hide() self.vbox.addLayout(self.save_btn_box) self.vbox.addLayout(self.create_filter_box) self.vbox.addLayout(self.list_box) self.filters_frame = QFrame() self.filters_frame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.filters_frame.setFrameShape(QFrame.StyledPanel) self.filters_frame.setObjectName('filters_frame') self.filters_box = QVBoxLayout(self.filters_frame) self.filters_box_label = QLabel('Issues and filters') self.filters_box_label.setObjectName('filters_box_label') self.filters_box.addWidget(self.filters_box_label) self.filters_list = QListWidget() self.filters_list.installEventFilter(self) self.filters_list.itemClicked.connect(self.on_filter_selected) self.filters_list.setObjectName('filters_list') self.filters_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.filters_box.addWidget(self.filters_list) self.add_filter_button = QPushButton('+') self.add_filter_button.clicked.connect(self.add_filter_btn_click) self.add_filter_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.filters_box.addWidget(self.add_filter_button, alignment=Qt.AlignRight) self.btn_box = QHBoxLayout() self.refresh_btn = QPushButton('Refresh') self.refresh_btn.clicked.connect(self.controller.refresh_issue_list) self.btn_box.addWidget(self.refresh_btn, alignment=Qt.AlignRight) self.vbox.addLayout(self.btn_box) self.toggle_frame_filters_btn = QPushButton('<') self.toggle_frame_filters_btn.clicked.connect(self.toggle_frame_filters) self.toggle_frame_filters_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.toggle_frame_filters_btn.setObjectName('toggle_filters_btn') self.main_box = QHBoxLayout() self.main_box.addWidget(self.filters_frame) self.main_box.addWidget(self.toggle_frame_filters_btn, alignment=Qt.AlignTop) self.main_box.addLayout(self.vbox) self.setLayout(self.main_box) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(LOGO_PATH)) self.tray_menu = QMenu() self.action_open = QAction('Open JQR', self) self.action_quit = QAction('Quit JQR', self) self.tray_menu.addAction(self.action_open) self.action_open.triggered.connect(self.show_jqr_from_tray) self.tray_menu.addAction(self.action_quit) self.action_quit.triggered.connect(self.controller.quit_app) self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.show() self.timer_log_work = QTimer() self.timer_log_work.timeout.connect(self.notification_to_log_work) self.timer_log_work.start(LOG_TIME) self.timer_refresh = QTimer() self.timer_refresh.timeout.connect(self.controller.auto_refresh_issue_list) def show_jqr_from_tray(self): self.hide() self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint) self.activateWindow() self.show() def keyPressEvent(self, event): if event.key() == Qt.Key_Return: self.controller.search_issues_by_query() def notification_to_log_work(self): QSound.play(RING_SOUND_PATH) self.tray_icon.showMessage( '1 hour had passed', 'Don\'t forget to log your work!', msecs=2000 ) self.timer_log_work.start(LOG_TIME) def update_issues(self, update_list): for issue in update_list: item = self.issue_list_widget.findItems( issue['key'], Qt.MatchExactly )[0] item.setText(issue['key']) issue_widget = self.issue_list_widget.itemWidget(item) issue_widget.set_issue_key(issue['key'], issue['link']) issue_widget.set_issue_title(issue['title']) issue_widget.set_time( issue['estimated'], issue['logged'], issue['remaining'] ) issue_widget.set_workflow.clear() issue_widget.set_workflow.addItems(self.controller.get_possible_workflows(issue)) issue_widget.set_workflow.setCurrentIndex(0) issue_widget.set_workflow.activated[str].disconnect() issue_widget.set_workflow.activated[str].connect( partial( self.controller.change_workflow, issue['workflow'], issue['issue_obj'], ) ) def delete_issues(self, delete_list): for issue in delete_list: item = self.issue_list_widget.findItems( issue['key'], Qt.MatchExactly )[0] self.issue_list_widget.takeItem( self.issue_list_widget.row(item) ) def insert_issues(self, new_issues_list): for issue in new_issues_list: issue_widget = QCustomWidget() issue_widget.set_issue_key(issue['key'], issue['link']) issue_widget.set_issue_title(issue['title']) issue_widget.set_time( issue['estimated'], issue['logged'], issue['remaining'] ) issue_widget.quick_log_btn.clicked.connect( partial( self.controller.log_work_from_list, issue['key'] ) ) issue_widget.log_work_btn.clicked.connect( partial( self.controller.open_time_log, issue['key'] ) ) issue_widget.open_pomodoro_btn.clicked.connect( partial( self.controller.open_pomodoro_window, issue['key'], issue['title'] ) ) # add workflow statuses to dropdown possible_workflows = self.controller.get_possible_workflows(issue) issue_widget.set_workflow.addItems(possible_workflows) issue_widget.set_workflow.setCurrentIndex(0) issue_widget.set_workflow.activated[str].connect( partial( self.controller.change_workflow, issue['workflow'], issue['issue_obj'], ) ) # add issue item to list issue_list_widget_item = QListWidgetItem() issue_list_widget_item.setText(issue['key']) issue_list_widget_item.setSizeHint(issue_widget.sizeHint()) self.issue_list_widget.insertItem(issue['index'], issue_list_widget_item) self.issue_list_widget.setItemWidget( issue_list_widget_item, issue_widget ) self.set_size_hint() def set_size_hint(self): self.issue_list_widget.setMinimumWidth( self.issue_list_widget.sizeHintForColumn(0) + 50 ) self.issue_list_widget.setMinimumHeight( self.issue_list_widget.sizeHintForRow(0) * 2 ) def show_filters(self, filters_dict): for index, key in enumerate(filters_dict): if key == SEARCH_ITEM_NAME: self.filters_list.insertItem(0, key) else: self.filters_list.addItem(key) self.filters_list.item(index).setToolTip(key) self.filters_list.item(0).setText(self.filters_list.item(0).text().capitalize()) # add separator after first item separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setObjectName('separator') item_separator = QListWidgetItem() item_separator.setFlags(Qt.NoItemFlags) self.filters_list.insertItem(1, item_separator) self.filters_list.setItemWidget(item_separator, separator) self.filters_list.setCurrentItem( self.filters_list.findItems( MY_ISSUES_ITEM_NAME, Qt.MatchExactly )[0]) self.filters_list.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) self.on_filter_selected(self.filters_list.currentItem()) def filter_field_help(self): QDesktopServices.openUrl(self.help_filter_url) def on_filter_selected(self, item): if not item.text(): return self.current_item = item if len(self.current_item.text()) > 50: set_text = '{}...'.format(self.current_item.text()[:50]) else: set_text = self.current_item.text() self.issue_list_widget.scrollToTop() self.controller.search_issues_by_filter_name(item.text()) self.filter_name_label.setText(set_text) self.filter_edited_label.hide() if self.filters_list.currentItem().text() == MY_ISSUES_ITEM_NAME: self.save_filter_btn.hide() self.overwrite_filter_button.hide() self.filter_edited_label.hide() self.delete_filter_btn.hide() elif self.filters_list.currentItem().text() == SEARCH_ITEM_NAME.capitalize(): # activate save button self.overwrite_filter_button.hide() self.save_filter_btn.show() self.delete_filter_btn.hide() else: # activate overwrite button self.overwrite_filter_button.show() self.overwrite_filter_button.setEnabled(False) self.save_filter_btn.hide() self.delete_filter_btn.show() def toggle_frame_filters(self): if self.toggle_frame_filters_btn.text() == '<': self.toggle_frame_filters_btn.setText('>') self.filters_frame.hide() else: self.toggle_frame_filters_btn.setText('<') self.filters_frame.show() def add_filter_btn_click(self): self.overwrite_filter_button.hide() self.save_filter_btn.show() self.delete_filter_btn.hide() self.filter_edited_label.hide() self.filters_list.setCurrentItem(None) self.query_field.setText('') self.filter_name_label.setText('Add new filter') self.controller.current_issues.clear() self.show_no_issues() def eventFilter(self, obj, event): # if user started typing in filter field if obj is self.query_field and event.type() == QEvent.KeyRelease: if not self.filters_list.currentItem(): return super().eventFilter(obj, event) current_filter_name = self.filters_list.currentItem().text().lower() # if current filter is not 'Search issues' or 'my open issues' if current_filter_name not in (SEARCH_ITEM_NAME, MY_ISSUES_ITEM_NAME): # if query of current filter has not changed if self.controller.filters_handler.get_filter_by_name( current_filter_name ) != self.query_field.text(): # show that filter has been edited self.filter_edited_label.show() self.overwrite_filter_button.setEnabled(True) else: self.filter_edited_label.hide() self.overwrite_filter_button.setEnabled(False) return super().eventFilter(obj, event) def set_current_filter(self, filter_name): items = self.filters_list.findItems( filter_name, Qt.MatchExactly ) self.filters_list.setCurrentItem(items[0]) self.on_filter_selected(items[0]) def add_filter(self, filter_name): self.filters_list.addItem(filter_name) self.set_current_filter(filter_name) def delete_filter(self): filter_name = self.filters_list.currentItem().text() reply = QMessageBox.question( self, 'Delete filter', "Are you sure you want to delete " "'{}' filter?".format(filter_name), QMessageBox.Yes | QMessageBox.Cancel ) if reply == QMessageBox.Yes: self.controller.filters_handler.delete_filter(filter_name) self.filters_list.takeItem( self.filters_list.currentRow() ) self.filters_list.setCurrentItem( self.filters_list.findItems( MY_ISSUES_ITEM_NAME, Qt.MatchExactly )[0]) self.on_filter_selected(self.filters_list.currentItem()) def show_no_issues(self, error_text=None): self.issue_list_widget.clear() self.issue_list_widget.hide() if error_text: self.label_info.setText(error_text) self.label_info.show() def set_workflow_current_state(self, issue_key): item = self.issue_list_widget.findItems( issue_key, Qt.MatchExactly )[0] custom_item = self.issue_list_widget.itemWidget(item) custom_item.set_workflow.setCurrentIndex(0) def wheelEvent(self, event): # top left corner coordinates of the issue list list_pos = self.issue_list_widget.pos() # check if cursor position is on the issue list if event.pos().x() >= list_pos.x() and event.pos().y() >= list_pos.y(): if event.angleDelta().y() < 0: self.controller.refresh_issue_list(True) event.accept() def closeEvent(self, event): event.ignore() self.hide()
class SimulationGui(QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name runSimulation = pyqtSignal() stopSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal(bool) def __init__(self): # constructor of the base class QMainWindow.__init__(self) QCoreApplication.setOrganizationName("RST") QCoreApplication.setOrganizationDomain("https://tu-dresden.de/rst") QCoreApplication.setApplicationVersion( pkg_resources.require("PyMoskito")[0].version) QCoreApplication.setApplicationName(globals()["__package__"]) # load settings self._settings = QSettings() self._read_settings() # initialize logger self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.stopSimulation.connect(self.sim.stop_simulation) self.sim.simulation_finalized.connect(self.new_simulation_data) self.currentDataset = None self.interpolator = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties icon_size = QSize(25, 25) self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.animationDock = pg.dockarea.Dock("Animation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDockPlaceholder = pg.dockarea.Dock("Placeholder") # arrange docks self.area.addDock(self.animationDock, "right") self.area.addDock(self.regimeDock, "left", self.animationDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDockPlaceholder, "bottom", self.animationDock) self.area.addDock(self.logDock, "bottom", self.dataDock) self.non_plotting_docks = list(self.area.findAll()[1].keys()) # add widgets to the docks self.propertyDock.addWidget(self.targetView) if not vtk_available: self._logger.error("loading vtk failed with:{}".format(vtk_error_msg)) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first visualizer self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.animationLayout = QVBoxLayout() if issubclass(available_vis[0][0], MplVisualizer): self.animationWidget = QWidget() self.visualizer = available_vis[0][0](self.animationWidget, self.animationLayout) self.animationDock.addWidget(self.animationWidget) elif issubclass(available_vis[0][0], VtkVisualizer): if vtk_available: # vtk window self.animationFrame = QFrame() self.vtkWidget = QVTKRenderWindowInteractor( self.animationFrame) self.animationLayout.addWidget(self.vtkWidget) self.animationFrame.setLayout(self.animationLayout) self.animationDock.addWidget(self.animationFrame) self.vtk_renderer = vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer( self.vtk_renderer) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self._logger.warning("visualizer depends on vtk which is " "not available on this system!") elif available_vis: raise NotImplementedError else: self.visualizer = None # regime window self.regime_list = QListWidget(self) self.regime_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" self.actDeleteRegimes = QAction(self.regime_list) self.actDeleteRegimes.setText("&Delete Selected Regimes") # TODO shortcut works always, not only with focus on the regime list # self.actDeleteRegimes.setShortcutContext(Qt.WindowShortcut) self.actDeleteRegimes.setShortcut(QKeySequence(Qt.Key_Delete)) self.actDeleteRegimes.triggered.connect(self.remove_regime_items) self.actSave = QAction(self) self.actSave.setText('Save Results As') self.actSave.setIcon(QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.setShortcut(QKeySequence.Save) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QAction(self) self.actLoadRegimes.setText("Load Regimes from File") self.actLoadRegimes.setIcon(QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.setShortcut(QKeySequence.Open) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExitOnBatchCompletion = QAction(self) self.actExitOnBatchCompletion.setText("&Exit On Batch Completion") self.actExitOnBatchCompletion.setCheckable(True) self.actExitOnBatchCompletion.setChecked( self._settings.value("control/exit_on_batch_completion") == "True" ) self.actExitOnBatchCompletion.changed.connect( self.update_exit_on_batch_completion_setting) # regime management self.runningBatch = False self._current_regime_index = None self._current_regime_name = None self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # data window self.dataList = QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulateCurrent = QAction(self) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.setShortcut(QKeySequence("F5")) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actSimulateAll = QAction(self) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.setShortcut(QKeySequence("F6")) self.actSimulateAll.setDisabled(True) self.actSimulateAll.triggered.connect(self.start_regime_execution) # actions for animation control self.actAutoPlay = QAction(self) self.actAutoPlay.setText("&Autoplay Simulation") self.actAutoPlay.setCheckable(True) self.actAutoPlay.setChecked( self._settings.value("control/autoplay_animation") == "True" ) self.actAutoPlay.changed.connect(self.update_autoplay_setting) self.actPlayPause = QAction(self) self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.setShortcut(QKeySequence(Qt.Key_Space)) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.actSlow = QAction(self) self.actSlow.setText("Slowest") self.actSlow.setIcon(QIcon(get_resource("slow.png"))) self.actSlow.setDisabled(False) self.actSlow.triggered.connect(self.set_slowest_playback_speed) self.actFast = QAction(self) self.actFast.setText("Fastest") self.actFast.setIcon(QIcon(get_resource("fast.png"))) self.actFast.setDisabled(False) self.actFast.triggered.connect(self.set_fastest_playback_speed) self.speedControl = QSlider(Qt.Horizontal, self) self.speedControl.setMaximumSize(200, 25) self.speedControl.setTickPosition(QSlider.TicksBothSides) self.speedControl.setDisabled(False) self.speedControl.setMinimum(0) self.speedControl.setMaximum(12) self.speedControl.setValue(6) self.speedControl.setTickInterval(6) self.speedControl.setSingleStep(2) self.speedControl.setPageStep(3) self.speedControl.valueChanged.connect(self.update_playback_speed) self.timeSlider = QSlider(Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = .0 self.playbackGain = 1 self.currentStepSize = .0 self.currentEndTime = .0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actResetCamera = QAction(self) self.actResetCamera.setText("Reset Camera") self.actResetCamera.setIcon(QIcon(get_resource("reset_camera.png"))) self.actResetCamera.setDisabled(True) if available_vis: self.actResetCamera.setEnabled(self.visualizer.can_reset_view) self.actResetCamera.triggered.connect(self.reset_camera_clicked) # postprocessing self.actPostprocessing = QAction(self) self.actPostprocessing.setText("Launch Postprocessor") self.actPostprocessing.setIcon(QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.actPostprocessing.setShortcut(QKeySequence("F7")) self.postprocessor = None # toolbar self.toolbarSim = QToolBar("Simulation") self.toolbarSim.setContextMenuPolicy(Qt.PreventContextMenu) self.toolbarSim.setMovable(False) self.toolbarSim.setIconSize(icon_size) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulateCurrent) self.toolbarSim.addAction(self.actSimulateAll) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSlow) self.toolbarSim.addWidget(self.speedControl) self.toolbarSim.addAction(self.actFast) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.actResetCamera) self.postprocessor = None # log dock self.logBox = QPlainTextEdit(self) self.logBox.setReadOnly(True) self.logDock.addWidget(self.logBox) # init logger for logging box self.textLogger = PlainTextLogger(logging.INFO) self.textLogger.set_target_cb(self.logBox.appendPlainText) logging.getLogger().addHandler(self.textLogger) # menu bar fileMenu = self.menuBar().addMenu("&File") fileMenu.addAction(self.actLoadRegimes) fileMenu.addAction(self.actSave) fileMenu.addAction("&Quit", self.close) editMenu = self.menuBar().addMenu("&Edit") editMenu.addAction(self.actDeleteRegimes) simMenu = self.menuBar().addMenu("&Simulation") simMenu.addAction(self.actSimulateCurrent) simMenu.addAction(self.actSimulateAll) simMenu.addAction(self.actExitOnBatchCompletion) simMenu.addAction(self.actPostprocessing) animMenu = self.menuBar().addMenu("&Animation") animMenu.addAction(self.actPlayPause) animMenu.addAction("&Increase Playback Speed", self.increment_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Plus)) animMenu.addAction("&Decrease Playback Speed", self.decrement_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Minus)) animMenu.addAction("&Reset Playback Speed", self.reset_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_0)) animMenu.addAction(self.actAutoPlay) animMenu.addAction(self.actResetCamera) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("&Online Documentation", self.show_online_docs) helpMenu.addAction("&About", self.show_info) # status bar self.status = QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) self._logger.info("Simulation GUI is up and running.") def _read_settings(self): # add default settings if none are present if not self._settings.contains("path/simulation_results"): self._settings.setValue("path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) if not self._settings.contains("path/postprocessing_results"): self._settings.setValue("path/postprocessing_results", os.path.join(os.path.curdir, "results", "postprocessing")) if not self._settings.contains("path/metaprocessing_results"): self._settings.setValue("path/metaprocessing_results", os.path.join(os.path.curdir, "results", "metaprocessing")) if not self._settings.contains("control/autoplay_animation"): self._settings.setValue("control/autoplay_animation", "False") if not self._settings.contains("control/exit_on_batch_completion"): self._settings.setValue("control/exit_on_batch_completion", "False") def _write_settings(self): """ Store the application state. """ pass @pyqtSlot() def update_autoplay_setting(self): self._settings.setValue("control/autoplay_animation", str(self.actAutoPlay.isChecked())) @pyqtSlot() def update_exit_on_batch_completion_setting(self, state=None): if state is None: state = self.actExitOnBatchCompletion.isChecked() self._settings.setValue("control/exit_on_batch_completion", str(state)) def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() @pyqtSlot() def play_animation(self): """ play the animation """ self._logger.debug("Starting Playback") # if we are at the end, start from the beginning if self.playbackTime == self.currentEndTime: self.timeSlider.setValue(0) self.actPlayPause.setText("Pause Animation") self.actPlayPause.setIcon(QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) @pyqtSlot() def pause_animation(self): """ pause the animation """ self._logger.debug("Pausing Playback") self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) def stop_animation(self): """ Stop the animation if it is running and reset the playback time. """ self._logger.debug("Stopping Playback") if self.actPlayPause.text() == "Pause Animation": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.timeSlider.setValue(0) @pyqtSlot() def start_simulation(self): """ start the simulation and disable start button """ if self._current_regime_index is None: regime_name = "" else: regime_name = str(self.regime_list.item( self._current_regime_index).text()) self.statusLabel.setText("simulating {}".format(regime_name)) self._logger.info("Simulating: {}".format(regime_name)) self.actSimulateCurrent.setIcon(QIcon( get_resource("stop_simulation.png"))) self.actSimulateCurrent.setText("Abort &Simulation") self.actSimulateCurrent.triggered.disconnect(self.start_simulation) self.actSimulateCurrent.triggered.connect(self.stop_simulation) if not self.runningBatch: self.actSimulateAll.setDisabled(True) self.guiProgress = QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() @pyqtSlot() def stop_simulation(self): self.stopSimulation.emit() def export_simulation_data(self, ok): """ Query the user for a custom name and export the current simulation results. :param ok: unused parameter from QAction.triggered() Signal """ self._save_data() def _save_data(self, file_path=None): """ Save the current simulation results. If *fie_name* is given, the result will be saved to the specified location, making automated exporting easier. Args: file_path(str): Absolute path of the target file. If `None` the use will be asked for a storage location. """ regime_name = self._regimes[self._current_regime_index]["Name"] if file_path is None: # get default path path = self._settings.value("path/simulation_results") # create canonic file name suggestion = self._simfile_name(regime_name) else: path = os.path.dirname(file_path) suggestion = os.path.basename(file_path) # check if path exists otherwise create it if not os.path.isdir(path): box = QMessageBox() box.setText("Export Folder does not exist yet.") box.setInformativeText("Do you want to create it? \n" "{}".format(os.path.abspath(path))) box.setStandardButtons(QMessageBox.Ok | QMessageBox.No) box.setDefaultButton(QMessageBox.Ok) ret = box.exec_() if ret == QMessageBox.Ok: os.makedirs(path) else: path = os.path.abspath(os.path.curdir) file_path = None # If no path was given, present the default and let the user choose if file_path is None: dialog = QFileDialog(self) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.AnyFile) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Results (*.pmr)") dialog.selectFile(suggestion) if dialog.exec_(): file_path = dialog.selectedFiles()[0] else: self._logger.warning("Export Aborted") return -1 # ask whether this should act as new default path = os.path.abspath(os.path.dirname(file_path)) if path != self._settings.value("path/simulation_results"): box = QMessageBox() box.setText("Use this path as new default?") box.setInformativeText("{}".format(path)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.Yes) ret = box.exec_() if ret == QMessageBox.Yes: self._settings.setValue("path/simulation_results", path) self.currentDataset.update({"regime name": regime_name}) with open(file_path, "wb") as f: pickle.dump(self.currentDataset, f, protocol=4) self.statusLabel.setText("results saved to {}".format(file_path)) self._logger.info("results saved to {}".format(file_path)) def _simfile_name(self, regime_name): """ Create a canonical name for a simulation result file """ suggestion = (time.strftime("%Y%m%d-%H%M%S") + "_" + regime_name + ".pmr") return suggestion def load_regime_dialog(self): regime_path = os.path.join(os.curdir) dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setDirectory(regime_path) dialog.setNameFilter("Simulation Regime files (*.sreg)") if dialog.exec_(): file = dialog.selectedFiles()[0] self.load_regimes_from_file(file) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info("loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode(), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actSimulateAll.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage("loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format(reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) @pyqtSlot(QListWidgetItem) def regime_dclicked(self, item): """ Apply the selected regime to the current target. """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ Apply the regime given by `regime_name` und update the regime index. Returns: bool: `True` if successful, `False` if errors occurred. """ # get regime idx try: idx = list(map(itemgetter("Name"), self._regimes)).index(regime_name) except ValueError as e: self._logger.error("apply_regime_by_name(): Error no regime called " "'{0}'".format(regime_name)) return False # apply return self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): """ Apply the given regime. Args: index(int): Index of the regime in the `RegimeList` . Returns: bool: `True` if successful, `False` if errors occurred. """ if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return False reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self._current_regime_name = reg_name return self.sim.set_regime(self._regimes[index]) @pyqtSlot() def start_regime_execution(self): """ Simulate all regimes in the regime list. """ self.actSimulateAll.setText("Stop Simulating &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("stop_batch.png"))) self.actSimulateAll.triggered.disconnect(self.start_regime_execution) self.actSimulateAll.triggered.connect(self.stop_regime_excecution) self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ Execute the next regime in the regime batch. """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit(True) return suc = self._apply_regime_by_idx(self._current_regime_index + 1) if not suc: self.finishedRegimeBatch.emit(False) return self.start_simulation() @pyqtSlot() def stop_regime_excecution(self): """ Stop the batch process. """ self.stopSimulation.emit() self.finishedRegimeBatch.emit(False) def regime_batch_finished(self, status): self.runningBatch = False self.actSimulateAll.setDisabled(False) self.actSave.setDisabled(True) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.triggered.disconnect(self.stop_regime_excecution) self.actSimulateAll.triggered.connect(self.start_regime_execution) if status: self.statusLabel.setText("All regimes have been simulated") self._logger.info("All Regimes have been simulated") else: self._logger.error("Batch simulation has been aborted") if self._settings.value("control/exit_on_batch_completion") == "True": self._logger.info("Shutting down SimulationGUI") self.close() @pyqtSlot(str, dict, name="new_simulation_data") def new_simulation_data(self, status, data): """ Slot to be called when the simulation interface has completed the current job and new data is available. Args: status (str): Status of the simulation, either - `finished` : Simulation has been finished successfully or - `failed` : Simulation has failed. data (dict): Dictionary, holding the simulation data. """ self._logger.info("Simulation {}".format(status)) self.statusLabel.setText("Simulation {}".format(status)) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.triggered.disconnect(self.stop_simulation) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actPlayPause.setDisabled(False) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedControl.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.stop_animation() self.currentDataset = data if data: self._read_results() self._update_data_list() self._update_plots() if self._settings.value("control/autoplay_animation") == "True": self.actPlayPause.trigger() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] file_name = self._simfile_name(regime_name) self._save_data(os.path.join( self._settings.value("path/simulation_results"), file_name)) self.regimeFinished.emit() else: self.actSimulateAll.setDisabled(False) def _read_results(self): state = self.currentDataset["results"]["Solver"] self.interpolator = interp1d(self.currentDataset["results"]["time"], state, axis=0, bounds_error=False, fill_value=(state[0], state[-1])) self.currentStepSize = 1.0/self.currentDataset["simulation"][ "measure rate"] self.currentEndTime = self.currentDataset["simulation"]["end time"] self.validData = True def increment_playback_speed(self): self.speedControl.setValue(self.speedControl.value() + self.speedControl.singleStep()) def decrement_playback_speed(self): self.speedControl.setValue(self.speedControl.value() - self.speedControl.singleStep()) def reset_playback_speed(self): self.speedControl.setValue((self.speedControl.maximum() - self.speedControl.minimum())/2) def set_slowest_playback_speed(self): self.speedControl.setValue(self.speedControl.minimum()) def set_fastest_playback_speed(self): self.speedControl.setValue(self.speedControl.maximum()) def update_playback_speed(self, val): """ adjust playback time to slider value :param val: """ maximum = self.speedControl.maximum() self.playbackGain = 10**(3.0 * (val - maximum / 2) / maximum) @pyqtSlot() def increment_playback_time(self): """ go one time step forward in playback """ if self.playbackTime == self.currentEndTime: self.pause_animation() return increment = self.playbackGain * self.playbackTimeout / 1000 self.playbackTime = min(self.currentEndTime, self.playbackTime + increment) pos = int(self.playbackTime / self.currentEndTime * self.timeSliderRange) self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value()/self.timeSliderRange*self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText("current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursors() # update state of rendering if self.visualizer: state = self.interpolator(self.playbackTime) self.visualizer.update_scene(state) if isinstance(self.visualizer, MplVisualizer): pass elif isinstance(self.visualizer, VtkVisualizer): self.vtkWidget.GetRenderWindow().Render() def _update_data_list(self): self.dataList.clear() for module_name, results in self.currentDataset["results"].items(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, module_name) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, )) ) elif len(results.shape) == 3: for col in range(results.shape[1]): for der in range(results.shape[2]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, der)) ) def _build_entry_name(self, module_name, idx): """ Construct an identifier for a given entry of a module. Args: module_name (str): name of the module the entry belongs to. idx (tuple): Index of the entry. Returns: str: Identifier to use for display. """ # save the user from defining 1d entries via tuples if len(idx) == 1: m_idx = idx[0] else: m_idx = idx mod_settings = self.currentDataset["modules"] info = mod_settings.get(module_name, {}).get("output_info", None) if info: if m_idx in info: return ".".join([module_name, info[m_idx]["Name"]]) return ".".join([module_name] + [str(i) for i in idx]) def _get_index_from_suffix(self, module_name, suffix): info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) idx = next((i for i in info if info[i]["Name"] == suffix), None) return idx def _get_units(self, entry): """ Return the unit that corresponds to a given entry. If no information is available, None is returned. Args: entry (str): Name of the entry. This can either be "Model.a.b" where a and b are numbers or if information is available "Model.Signal" where signal is the name of that part. Returns: """ args = entry.split(".") module_name = args.pop(0) info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) if info is None: return None if len(args) == 1: try: idx = int(args[0]) except ValueError: idx = next((i for i in info if info[i]["Name"] == args[0]), None) else: idx = (int(a) for a in args) return info[idx]["Unit"] def create_plot(self, item): """ Creates a plot widget based on the given item. If a plot for this item is already open no new plot is created but the existing one is raised up again. Args: item(Qt.ListItem): Item to plot. """ title = str(item.text()) if title in self.non_plotting_docks: self._logger.error("Title '{}' not allowed for a plot window since" "it would shadow on of the reserved " "names".format(title)) # check if plot has already been opened if title in self.area.findAll()[1]: self.area.docks[title].raiseDock() return # collect data data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] unit = self._get_units(title) if "." in title: name = title.split(".")[1] else: name = title # create plot widget widget = pg.PlotWidget(title=title) widget.showGrid(True, True) widget.plot(x=t, y=data) widget.getPlotItem().getAxis("bottom").setLabel(text="Time", units="s") widget.getPlotItem().getAxis("left").setLabel(text=name, units=unit) # add a time line time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # create dock container and add it to dock area dock = pg.dockarea.Dock(title, closable=True) dock.addWidget(widget) self.area.addDock(dock, "above", self.plotDockPlaceholder) def _get_data_by_name(self, name): tmp = name.split(".") module_name = tmp[0] if len(tmp) == 1: data = np.array(self.currentDataset["results"][module_name]) elif len(tmp) == 2: try: idx = int(tmp[1]) except ValueError: idx = self._get_index_from_suffix(module_name, tmp[1]) finally: data = self.currentDataset["results"][module_name][..., idx] elif len(tmp) == 3: idx = int(tmp[1]) der = int(tmp[2]) data = self.currentDataset["results"][module_name][..., idx, der] else: raise ValueError("Format not supported") return data def _update_time_cursors(self): """ Update the time lines of all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.InfiniteLine): item.setValue(self.playbackTime) def _update_plots(self): """ Update the data in all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue if not self.dataList.findItems(dock.name(), Qt.MatchExactly): # no data for this plot -> remove it dock.close() continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.PlotDataItem): x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) item.setData(x=x_data, y=y_data) @pyqtSlot(QModelIndex) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info("launching postprocessor") self.statusBar().showMessage("launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render() def show_info(self): icon_lic = open(get_resource("license.txt"), "r").read() text = "This application was build using PyMoskito ver. {} .<br />" \ "PyMoskito is free software distributed under GPLv3. <br />" \ "It is developed by members of the " \ "<a href=\'https://tu-dresden.de/ing/elektrotechnik/rst'>" \ "Institute of Control Theory</a>" \ " at the <a href=\'https://tu-dresden.de'>" \ "Dresden University of Technology</a>. <br />" \ "".format(pkg_resources.require("PyMoskito")[0].version) \ + "<br />" + icon_lic box = QMessageBox.about(self, "PyMoskito", text) def show_online_docs(self): webbrowser.open("https://pymoskito.readthedocs.org") def closeEvent(self, QCloseEvent): self._logger.info("Close Event received, shutting down.") logging.getLogger().removeHandler(self.textLogger) super().closeEvent(QCloseEvent)
class outlineBasics(QAbstractItemView): def __init__(self, parent=None): self._indexesToOpen = None self.menuCustomIcons = None def getSelection(self): sel = [] for i in self.selectedIndexes(): if i.column() != 0: continue if not i in sel: sel.append(i) return sel def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self.menu = self.makePopupMenu() self.menu.popup(event.globalPos()) # We don't call QAbstractItemView.mouseReleaseEvent because # outlineBasics is never subclassed alone. So the others views # (outlineView, corkView, treeView) that subclass outlineBasics # call their respective mother class. def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Get index under cursor pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) # Get index's title if mouseIndex.isValid(): title = mouseIndex.internalPointer().title() elif self.rootIndex().parent().isValid(): # mouseIndex is the background of an item, so we check the parent mouseIndex = self.rootIndex().parent() title = mouseIndex.internalPointer().title() else: title = qApp.translate("outlineBasics", "Root") if len(title) > 25: title = title[:25] + "…" # Open Item action self.actOpen = QAction( QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open {}".format(title)), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) # Open item(s) in new tab if mouseIndex in sel and len(sel) > 1: actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format( len(sel)) self._indexesToOpen = sel else: actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title) self._indexesToOpen = [mouseIndex] self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu) self.actNewTab.triggered.connect(self.openItemsInNewTabs) menu.addAction(self.actNewTab) menu.addSeparator() # Add text / folder self.actAddFolder = QAction( QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New &Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New &Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) menu.addSeparator() # Copy, cut, paste, duplicate self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "C&ut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "&Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "&Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) # Rename / duplicate / remove items self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "&Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "&Rename"), menu) self.actRename.triggered.connect(self.rename) menu.addAction(self.actRename) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [ qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor") ]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction( mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons if self.menuCustomIcons: menu.addMenu(self.menuCustomIcons) else: self.menuCustomIcons = QMenu( qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("background: transparent; border: none;") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet( "background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) if len(sel) > 1: self.actRename.setEnabled(False) return menu def openItem(self): #idx = self.currentIndex() idx = self._indexesToOpen[0] from manuskript.functions import MW MW.openIndex(idx) def openItemsInNewTabs(self): from manuskript.functions import MW MW.openIndexes(self._indexesToOpen) def rename(self): if len(self.getSelection()) == 1: index = self.currentIndex() self.edit(index) elif len(self.getSelection()) > 1: # FIXME: add smart rename pass def addFolder(self): self.addItem("folder") def addText(self): self.addItem("text") def addItem(self, _type="folder"): if len(self.selectedIndexes()) == 0: parent = self.rootIndex() else: parent = self.currentIndex() if _type == "text": _type = settings.defaultTextType item = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=_type) self.model().appendItem(item, parent) def copy(self): mimeData = self.model().mimeData( self.selectionModel().selectedIndexes()) qApp.clipboard().setMimeData(mimeData) def paste(self, mimeData=None): """ Paste item from mimeData to selected item. If mimeData is not given, it is taken from clipboard. If not item selected, paste into root. """ index = self.currentIndex() if len(self.getSelection()) == 0: index = self.rootIndex() if not mimeData: mimeData = qApp.clipboard().mimeData() self.model().dropMimeData(mimeData, Qt.CopyAction, -1, 0, index) def cut(self): self.copy() self.delete() def delete(self): """ Shows a warning, and then deletes currently selected indexes. """ if not settings.dontShowDeleteWarning: msg = QMessageBox( QMessageBox.Warning, qApp.translate("outlineBasics", "About to remove"), qApp.translate( "outlineBasics", "<p><b>You're about to delete {} item(s).</b></p><p>Are you sure?</p>" ).format(len(self.getSelection())), QMessageBox.Yes | QMessageBox.Cancel) chk = QCheckBox("&Don't show this warning in the future.") msg.setCheckBox(chk) ret = msg.exec() if ret == QMessageBox.Cancel: return if chk.isChecked(): settings.dontShowDeleteWarning = True self.model().removeIndexes(self.getSelection()) def duplicate(self): """ Duplicates item(s), while preserving clipboard content. """ mimeData = self.model().mimeData( self.selectionModel().selectedIndexes()) self.paste(mimeData) def move(self, delta=1): """ Move selected items up or down. """ # we store selected indexes currentID = self.model().ID(self.currentIndex()) selIDs = [self.model().ID(i) for i in self.selectedIndexes()] # Block signals self.blockSignals(True) self.selectionModel().blockSignals(True) # Move each index individually for idx in self.selectedIndexes(): self.moveIndex(idx, delta) # Done the hardcore way, so inform views self.model().layoutChanged.emit() # restore selection selIdx = [self.model().getIndexByID(ID) for ID in selIDs] sm = self.selectionModel() sm.clear() [sm.select(idx, sm.Select) for idx in selIdx] sm.setCurrentIndex(self.model().getIndexByID(currentID), sm.Select) #self.setSmsgBoxelectionModel(sm) # Unblock signals self.blockSignals(False) self.selectionModel().blockSignals(False) def moveIndex(self, index, delta=1): """ Move the item represented by index. +1 means down, -1 means up. """ if not index.isValid(): return if index.parent().isValid(): parentItem = index.parent().internalPointer() else: parentItem = index.model().rootItem parentItem.childItems.insert(index.row() + delta, parentItem.childItems.pop(index.row())) parentItem.updateWordCount(emit=False) def moveUp(self): self.move(-1) def moveDown(self): self.move(+1) def splitDialog(self): """ Opens a dialog to split selected items. Call context: if at least one index is selected. Folder or text. """ indexes = self.getSelection() if len(indexes) == 0: # No selection, we use parent indexes = [self.rootIndex()] splitDialog(self, indexes) def merge(self): """ Merges selected items together. Call context: Multiple selection, same parent. """ # Get selection indexes = self.getSelection() # Get items items = [i.internalPointer() for i in indexes if i.isValid()] # Remove folders items = [i for i in items if not i.isFolder()] # Check that we have at least 2 items if len(items) < 2: statusMessage(qApp.translate( "outlineBasics", "Select at least two items. Folders are ignored."), importance=2) return # Check that all share the same parent p = items[0].parent() for i in items: if i.parent() != p: statusMessage(qApp.translate( "outlineBasics", "All items must be on the same level (share the same parent)." ), importance=2) return # Sort items by row items = sorted(items, key=lambda i: i.row()) items[0].mergeWith(items[1:]) def setPOV(self, POV): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.POV), str(POV)) def setStatus(self, status): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.status), str(status)) def setLabel(self, label): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.label), str(label)) def setCustomIcon(self, customIcon): for i in self.getSelection(): item = i.internalPointer() item.setCustomIcon(customIcon) def setCustomIconFromItem(self, item): icon = item.data(Qt.UserRole) self.setCustomIcon(icon) self.menu.close() def filterLstIcons(self, text): for l in self.lstIcons.findItems("", Qt.MatchContains): l.setHidden(not text in l.data(Qt.UserRole))
class GuiWordList(QDialog): def __init__(self, theParent, theProject): QDialog.__init__(self, theParent) logger.debug("Initialising GuiWordList ...") self.setObjectName("GuiWordList") self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme self.theProject = theProject self.optState = theProject.optState self.setWindowTitle(self.tr("Project Word List")) mS = self.mainConf.pxInt(250) wW = self.mainConf.pxInt(320) wH = self.mainConf.pxInt(340) self.setMinimumWidth(mS) self.setMinimumHeight(mS) self.resize( self.mainConf.pxInt( self.optState.getInt("GuiWordList", "winWidth", wW)), self.mainConf.pxInt( self.optState.getInt("GuiWordList", "winHeight", wH))) # Main Widgets # ============ self.headLabel = QLabel("<b>%s</b>" % self.tr("Project Word List")) self.listBox = QListWidget() self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop) self.listBox.setSortingEnabled(True) self.newEntry = QLineEdit() self.addButton = QPushButton(self.theTheme.getIcon("add"), "") self.addButton.setToolTip(self.tr("Add new entry")) self.addButton.clicked.connect(self._doAdd) self.delButton = QPushButton(self.theTheme.getIcon("remove"), "") self.delButton.setToolTip(self.tr("Delete selected entry")) self.delButton.clicked.connect(self._doDelete) self.editBox = QHBoxLayout() self.editBox.addWidget(self.newEntry, 1) self.editBox.addWidget(self.addButton, 0) self.editBox.addWidget(self.delButton, 0) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) # Assemble # ======== self.outerBox = QVBoxLayout() self.outerBox.addWidget(self.headLabel) self.outerBox.addSpacing(self.mainConf.pxInt(8)) self.outerBox.addWidget(self.listBox, 1) self.outerBox.addLayout(self.editBox, 0) self.outerBox.addSpacing(self.mainConf.pxInt(12)) self.outerBox.addWidget(self.buttonBox, 0) self.setLayout(self.outerBox) self._loadWordList() logger.debug("GuiWordList initialisation complete") return ## # Slots ## def _doAdd(self): """Add a new word to the word list. """ newWord = self.newEntry.text().strip() if newWord == "": self.theParent.makeAlert(self.tr("Cannot add a blank word."), nwAlert.ERROR) return False if self.listBox.findItems(newWord, Qt.MatchExactly): self.theParent.makeAlert( self.tr("The word '{0}' is already in the word list.").format( newWord), nwAlert.ERROR) return False self.listBox.addItem(newWord) self.newEntry.setText("") return True def _doDelete(self): """Delete the selected item. """ selItem = self.listBox.selectedItems() if selItem: self.listBox.takeItem(self.listBox.row(selItem[0])) return def _doSave(self): """Save the new word list and close. """ self._saveGuiSettings() dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT) tmpFile = dctFile + "~" try: with open(tmpFile, mode="w", encoding="utf8") as outFile: for i in range(self.listBox.count()): outFile.write(self.listBox.item(i).text() + "\n") except Exception: logger.error("Could not save new word list") nw.logException() self.reject() return False if os.path.isfile(dctFile): os.unlink(dctFile) os.rename(tmpFile, dctFile) self.accept() return True def _doClose(self): """Close without saving the word list. """ self._saveGuiSettings() self.reject() return ## # Internal Functions ## def _loadWordList(self): """Load the project's word list, if it exists. """ self.listBox.clear() wordList = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT) if not os.path.isfile(wordList): logger.debug("No project dictionary file found") return False with open(wordList, mode="r", encoding="utf8") as inFile: for inLine in inFile: theWord = inLine.strip() if len(theWord) == 0: continue self.listBox.addItem(theWord) return True def _saveGuiSettings(self): """Save GUI settings. """ winWidth = self.mainConf.rpxInt(self.width()) winHeight = self.mainConf.rpxInt(self.height()) self.optState.setValue("GuiWordList", "winWidth", winWidth) self.optState.setValue("GuiWordList", "winHeight", winHeight) return
class desktopChooser(QDialog): dblClicked = pyqtSignal("PyQt_PyObject") def __init__(self, parent): super(desktopChooser, self).__init__(parent) self.parent = parent self.menu = App2Menu.app2menu() self.setWindowTitle(_("Launcher select")) self.setModal(False) self.desktopList = QListWidget() self.desktopList.setSortingEnabled(True) self.desktopList.setDragEnabled(True) self.desktopList.setAcceptDrops(True) self.desktopList.setSpacing(3) self.desktopList.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.desktopList.itemDoubleClicked.connect(self._dblClick) self.desktopList.itemPressed.connect(self._loadMime) self.data = {} self._renderGui() def _renderGui(self): def searchList(): items = self.desktopList.findItems(inp_search.text(), Qt.MatchFlag.MatchContains) if items: self.desktopList.scrollToItem(items[0]) self.desktopList.setCurrentItem(items[0]) box = QVBoxLayout() inp_search = QLineEdit() inp_search.setPlaceholderText(_("Search")) inp_search.textChanged.connect(searchList) completer = QCompleter() completer.setCaseSensitivity(Qt.CaseInsensitive) model = QtGui.QStandardItemModel() #Load available desktops categories = self.menu.get_categories() for category in categories: desktops = self.menu.get_apps_from_category(category) for desktop in desktops.keys(): desktopInfo = self.menu.get_desktop_info( "%s/%s" % (self.menu.desktoppath, desktop)) if desktopInfo.get("NoDisplay", False): continue listWidget = QListWidgetItem() desktopLayout = QGridLayout() ficon = desktopInfo.get("Icon", "shell") icon = QtGui.QIcon.fromTheme(ficon) name = desktopInfo.get("Name", "shell") model.appendRow(QtGui.QStandardItem(name)) comment = desktopInfo.get("Comment", "shell") listWidget.setIcon(icon) listWidget.setText(name) self.desktopList.addItem(listWidget) self.data[self.desktopList.count() - 1] = { 'path': "%s/%s" % (self.menu.desktoppath, desktop), 'icon': icon } completer.setModel(model) inp_search.setCompleter(completer) box.addWidget(inp_search) box.addWidget(self.desktopList) self.setLayout(box) def _dblClick(self): listWidget = self.desktopList.currentRow() path = self.data[listWidget] self.dblClicked.emit(path) def _loadMime(self): listWidget = self.desktopList.currentRow() mimedata = QMimeData() mimedata.setText(self.data[listWidget]['path']) drag = QtGui.QDrag(self) drag.setMimeData(mimedata) pixmap = self.data[listWidget]['icon'].pixmap(QSize( BTN_SIZE, BTN_SIZE)) drag.setPixmap(pixmap) dropAction = drag.exec_(Qt.MoveAction) #def mousePressEvent def dragMoveEvent(self, e): e.accept() #def dragEnterEvent def dragEnterEvent(self, e): e.accept()
class outlineBasics(QAbstractItemView): def __init__(self, parent=None): self._indexesToOpen = None self.menuCustomIcons = None def getSelection(self): sel = [] for i in self.selectedIndexes(): if i.column() != 0: continue if not i in sel: sel.append(i) return sel def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self.menu = self.makePopupMenu() self.menu.popup(event.globalPos()) # We don't call QAbstractItemView.mouseReleaseEvent because # outlineBasics is never subclassed alone. So the others views # (outlineView, corkView, treeView) that subclass outlineBasics # call their respective mother class. def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Get index under cursor pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) # Get index's title if mouseIndex.isValid(): title = mouseIndex.internalPointer().title() elif self.rootIndex().parent().isValid(): # mouseIndex is the background of an item, so we check the parent mouseIndex = self.rootIndex().parent() title = mouseIndex.internalPointer().title() else: title = qApp.translate("outlineBasics", "Root") if len(title) > 25: title = title[:25] + "…" # Open Item action self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open {}".format(title)), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) # Open item(s) in new tab if mouseIndex in sel and len(sel) > 1: actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel)) self._indexesToOpen = sel else: actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title) self._indexesToOpen = [mouseIndex] self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu) self.actNewTab.triggered.connect(self.openItemsInNewTabs) menu.addAction(self.actNewTab) menu.addSeparator() # Add text / folder self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New &Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New &Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) menu.addSeparator() # Copy, cut, paste, duplicate self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "C&ut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "&Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "&Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) # Rename / duplicate / remove items self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "&Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "&Rename"), menu) self.actRename.triggered.connect(self.rename) menu.addAction(self.actRename) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor")]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction(mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons if self.menuCustomIcons: menu.addMenu(self.menuCustomIcons) else: self.menuCustomIcons = QMenu(qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("QLineEdit { background: transparent; border: none; }") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet("background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) if len(sel) > 1: self.actRename.setEnabled(False) return menu def openItem(self): #idx = self.currentIndex() idx = self._indexesToOpen[0] from manuskript.functions import MW MW.openIndex(idx) def openItemsInNewTabs(self): from manuskript.functions import MW MW.openIndexes(self._indexesToOpen) def rename(self): if len(self.getSelection()) == 1: index = self.currentIndex() self.edit(index) elif len(self.getSelection()) > 1: # FIXME: add smart rename pass def addFolder(self): self.addItem("folder") def addText(self): self.addItem("text") def addItem(self, _type="folder"): if len(self.selectedIndexes()) == 0: parent = self.rootIndex() else: parent = self.currentIndex() if _type == "text": _type = settings.defaultTextType item = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=_type) self.model().appendItem(item, parent) def copy(self): mimeData = self.model().mimeData(self.selectionModel().selectedIndexes()) qApp.clipboard().setMimeData(mimeData) def paste(self, mimeData=None): """ Paste item from mimeData to selected item. If mimeData is not given, it is taken from clipboard. If not item selected, paste into root. """ index = self.currentIndex() if len(self.getSelection()) == 0: index = self.rootIndex() if not mimeData: mimeData = qApp.clipboard().mimeData() self.model().dropMimeData(mimeData, Qt.CopyAction, -1, 0, index) def cut(self): self.copy() self.delete() def delete(self): """ Shows a warning, and then deletes currently selected indexes. """ if not settings.dontShowDeleteWarning: msg = QMessageBox(QMessageBox.Warning, qApp.translate("outlineBasics", "About to remove"), qApp.translate("outlineBasics", "<p><b>You're about to delete {} item(s).</b></p><p>Are you sure?</p>" ).format(len(self.getSelection())), QMessageBox.Yes | QMessageBox.Cancel) chk = QCheckBox("&Don't show this warning in the future.") msg.setCheckBox(chk) ret = msg.exec() if ret == QMessageBox.Cancel: return if chk.isChecked(): settings.dontShowDeleteWarning = True self.model().removeIndexes(self.getSelection()) def duplicate(self): """ Duplicates item(s), while preserving clipboard content. """ mimeData = self.model().mimeData(self.selectionModel().selectedIndexes()) self.paste(mimeData) def move(self, delta=1): """ Move selected items up or down. """ # we store selected indexes currentID = self.model().ID(self.currentIndex()) selIDs = [self.model().ID(i) for i in self.selectedIndexes()] # Block signals self.blockSignals(True) self.selectionModel().blockSignals(True) # Move each index individually for idx in self.selectedIndexes(): self.moveIndex(idx, delta) # Done the hardcore way, so inform views self.model().layoutChanged.emit() # restore selection selIdx = [self.model().getIndexByID(ID) for ID in selIDs] sm = self.selectionModel() sm.clear() [sm.select(idx, sm.Select) for idx in selIdx] sm.setCurrentIndex(self.model().getIndexByID(currentID), sm.Select) #self.setSmsgBoxelectionModel(sm) # Unblock signals self.blockSignals(False) self.selectionModel().blockSignals(False) def moveIndex(self, index, delta=1): """ Move the item represented by index. +1 means down, -1 means up. """ if not index.isValid(): return if index.parent().isValid(): parentItem = index.parent().internalPointer() else: parentItem = index.model().rootItem parentItem.childItems.insert(index.row() + delta, parentItem.childItems.pop(index.row())) parentItem.updateWordCount(emit=False) def moveUp(self): self.move(-1) def moveDown(self): self.move(+1) def splitDialog(self): """ Opens a dialog to split selected items. Call context: if at least one index is selected. Folder or text. """ indexes = self.getSelection() if len(indexes) == 0: # No selection, we use parent indexes = [self.rootIndex()] splitDialog(self, indexes) def merge(self): """ Merges selected items together. Call context: Multiple selection, same parent. """ # Get selection indexes = self.getSelection() # Get items items = [i.internalPointer() for i in indexes if i.isValid()] # Remove folders items = [i for i in items if not i.isFolder()] # Check that we have at least 2 items if len(items) < 2: statusMessage(qApp.translate("outlineBasics", "Select at least two items. Folders are ignored."), importance=2) return # Check that all share the same parent p = items[0].parent() for i in items: if i.parent() != p: statusMessage(qApp.translate("outlineBasics", "All items must be on the same level (share the same parent)."), importance=2) return # Sort items by row items = sorted(items, key=lambda i: i.row()) items[0].mergeWith(items[1:]) def setPOV(self, POV): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.POV), str(POV)) def setStatus(self, status): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.status), str(status)) def setLabel(self, label): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.label), str(label)) def setCustomIcon(self, customIcon): for i in self.getSelection(): item = i.internalPointer() item.setCustomIcon(customIcon) def setCustomIconFromItem(self, item): icon = item.data(Qt.UserRole) self.setCustomIcon(icon) self.menu.close() def filterLstIcons(self, text): for l in self.lstIcons.findItems("", Qt.MatchContains): l.setHidden(not text in l.data(Qt.UserRole))
class ToolBar(QWidget): mouseEntered = pyqtSignal() mouseLeft = pyqtSignal() toolChanged = pyqtSignal(str) primaryInkChanged = pyqtSignal(str) secondaryInkChanged = pyqtSignal(str) def __init__(self): super(ToolBar, self).__init__() self.setAttribute(Qt.WA_StaticContents) self.setAttribute(Qt.WA_NoSystemBackground) self.setFont(ResourcesCache.get("BigFont")) self._registeredTools = {} self._registeredInks = {} self._toolSlots = [] self._inkSlots = [] self._currentActiveToolSlot = None self._previousActiveToolSlot = None self._currentEditedInkSlot = None self._previousEditedInkSlot = None self._editMode = False self._backgroundColor = QColor(40, 40, 40) self._toolLabelColor = QColor(112, 231, 255) self._layout = QVBoxLayout() self._layout.setAlignment(Qt.AlignTop) self._layout.setContentsMargins(4, 4, 4, 4) top_layout = QHBoxLayout() self._layout.addLayout(top_layout) self._toolsLayout = QHBoxLayout() self._inksLayout = QHBoxLayout() self._toolsLayout.setContentsMargins(0, 0, 0, 0) self._toolsLayout.setAlignment(Qt.AlignLeft) self._inksLayout.setContentsMargins(0, 0, 0, 0) self._inksLayout.setAlignment(Qt.AlignRight) top_layout.addLayout(self._toolsLayout) top_layout.addLayout(self._inksLayout) self._toolsButtonGroup = QButtonGroup() self._toolsButtonGroup.buttonClicked.connect( self._on_tool_slot_triggered) self._inksButtonGroup = QButtonGroup() self._inksButtonGroup.setExclusive(False) self._inksButtonGroup.buttonClicked.connect( self._on_ink_slot_triggered) self.setLayout(self._layout) self._toolbarSubPanel = None self._toolsListWidget = None self._toolsOptionsPanel = None self._init_edit_panel() self._add_ink_slot(0) self._add_ink_slot(1) self.resize(0, 50) # ------------------------------------------------------------------------- def get_tool_by_name(self, name): return self._registeredTools[name] def register_tool(self, tool, is_default=None): if tool.name not in self._registeredTools: self._registeredTools[tool.name] = tool self._toolsListWidget.addItem(tool.name) if is_default is True: self._toolsListWidget.setCurrentRow(0) self._build_tool_options_pane(tool) if len(self._toolSlots) < 4: slot_index = self._add_tool_slot(is_default) self._assign_tool_to_slot(tool, slot_index) def register_ink(self, ink, slot): if not ink.name in self._registeredInks: self._registeredInks[ink.name] = ink self._inksListWidget.addItem(ink.name) self._build_ink_options_pane(ink) if self._inkSlots[slot]['id'] is None: self._assign_ink_to_slot(ink, slot) def switch_tool_slot(self, slot): self._previousActiveToolSlot = self._currentActiveToolSlot self._currentActiveToolSlot = slot if self._currentActiveToolSlot == self._previousActiveToolSlot: return tool_name = self._toolSlots[slot]['id'] self._toolSlots[slot]['button'].setChecked(True) self.toolChanged.emit(tool_name) self._select_tool_on_list(tool_name) # ------------------------------------------------------------------------- def _go_back_to_last_tool(self): self.switch_tool_slot(self._previousActiveToolSlot) def _add_tool_slot(self, selected=None): slot_button = QPushButton() slot_button.setCheckable(True) index = len(self._toolSlots) if selected is not None and selected is True: slot_button.setChecked(True) slot = {'id': None, 'button': slot_button} if selected: self._currentActiveToolSlot = index self._toolSlots.append(slot) self._toolsButtonGroup.addButton(slot_button, index) self._toolsLayout.addWidget(slot_button) return index def _add_ink_slot(self, slot_number): slot_button = QPushButton() slot_button.setFont(self.font()) slot_button.setStyleSheet( "border-color: rgb(56,56,56); background-color: rgb(17,17," "17); font-size: 12pt;") index = len(self._inkSlots) if slot_number == 0: icon = QIcon() icon.addPixmap(QPixmap(":/icons/ico_mouse_button1"), QIcon.Normal, QIcon.Off) slot_button.setIcon(icon) slot_button.setIconSize(QSize(18, 23)) elif slot_number == 1: icon = QIcon() icon.addPixmap(QPixmap(":/icons/ico_mouse_button2"), QIcon.Normal, QIcon.Off) slot_button.setIcon(icon) slot_button.setIconSize(QSize(18, 23)) slot = {'id': None, 'button': slot_button} self._inkSlots.append(slot) self._inksButtonGroup.addButton(slot_button) self._inksButtonGroup.setId(slot_button, index) self._inksLayout.addWidget(slot_button) return index def _assign_tool_to_slot(self, tool, slot): if slot < 0 or slot > len(self._toolSlots) - 1: raise Exception( '[ToolBar] > _assignToolToSlot : invalid slot parameter') self._toolSlots[slot]['id'] = tool.name icon = tool.icon if icon is not None: tool_button = self._toolSlots[slot]['button'] tool_button.setIcon(tool.icon) tool_button.setIconSize(QSize(24, 24)) def _assign_ink_to_slot(self, ink, slot): if slot != 0 and slot != 1: raise Exception( '[ToolBar] > _assignInkToSlot : invalid slot parameter') ink_name = ink.name self._inkSlots[slot]['id'] = ink_name self._inkSlots[slot]['button'].setText(ink_name) if slot == 0: self.primaryInkChanged.emit(ink_name) elif slot == 1: self.secondaryInkChanged.emit(ink_name) def _init_edit_panel(self): self._toolbarSubPanel = QStackedWidget() # 1. Initialize Tools Control Panel ----------------------------------- self._toolsListWidget = QListWidget() self._toolsListWidget.currentRowChanged.connect( lambda v: self._toolsOptionsPanel.setCurrentIndex(v)) self._toolsListWidget.setMaximumSize(QSize(150, 200)) self._toolsListWidget.itemClicked.connect( self._on_tool_list_item_clicked) # Tools Subpanel ------------------------------------------------------ tools_control_panel = QWidget() tools_control_panel_layout = QHBoxLayout() tools_control_panel.setLayout(tools_control_panel_layout) tools_control_panel_layout.setAlignment(Qt.AlignLeft) # Tools List ---------------------------------------------------------- tools_list_sublayout = QVBoxLayout() tools_list_sublayout.setAlignment(Qt.AlignTop) tools_list_sublayout.setContentsMargins(0, 0, 0, 0) tools_list_sublayout.addWidget(QLabel("Tools")) tools_list_sublayout.addWidget(self._toolsListWidget) tools_control_panel_layout.addLayout(tools_list_sublayout) # Tools Options ------------------------------------------------------- tools_options_sublayout = QVBoxLayout() tools_options_sublayout.setAlignment(Qt.AlignTop) tools_control_panel_layout.addLayout(tools_options_sublayout) self._toolsOptionsPanel = QStackedWidget() tools_options_sublayout.addWidget(QLabel("Tools Options")) tools_options_sublayout.addWidget(self._toolsOptionsPanel) self._toolbarSubPanel.addWidget(tools_control_panel) # 2. Initialize Inks Control Panel ------------------------------------ self._inksListWidget = QListWidget() self._inksListWidget.currentRowChanged.connect( lambda v: self._inksOptionsPanel.setCurrentIndex(v)) self._inksListWidget.setMaximumSize(QSize(150, 200)) self._inksListWidget.itemClicked.connect( self._on_ink_list_item_clicked) # Inks Subpanel ------------------------------------------------------- inks_control_panel = QWidget() inks_control_panel_layout = QHBoxLayout() inks_control_panel.setLayout(inks_control_panel_layout) inks_control_panel_layout.setAlignment(Qt.AlignLeft) # Inks List ----------------------------------------------------------- inks_list_sublayout = QVBoxLayout() inks_list_sublayout.setAlignment(Qt.AlignTop) inks_list_sublayout.setContentsMargins(0, 0, 0, 0) inks_list_sublayout.addWidget(QLabel("Inks")) inks_list_sublayout.addWidget(self._inksListWidget) inks_control_panel_layout.addLayout(inks_list_sublayout) # Inks Options -------------------------------------------------------- inks_options_sublayout = QVBoxLayout() inks_options_sublayout.setAlignment(Qt.AlignTop) inks_control_panel_layout.addLayout(inks_options_sublayout) self._inksOptionsPanel = QStackedWidget() inks_options_sublayout.addWidget(QLabel("Ink Options")) inks_options_sublayout.addWidget(self._inksOptionsPanel) self._toolbarSubPanel.addWidget(inks_control_panel) # --------------------------------------------------------------------- self._layout.addWidget(self._toolbarSubPanel) self._toolbarSubPanel.setVisible(False) def _build_tool_options_pane(self, tool): pane = QWidget() pane_layout = QVBoxLayout() pane_layout.setAlignment(Qt.AlignTop) pane.setLayout(pane_layout) for prop in tool.properties.values(): field_layout = QHBoxLayout() field_layout.addWidget(QLabel(prop.description)) prop_widget = prop.build_property_widget() field_layout.addWidget(prop_widget) pane_layout.addLayout(field_layout) self._toolsOptionsPanel.addWidget(pane) def _build_ink_options_pane(self, ink): pane = QWidget() pane_layout = QVBoxLayout() pane_layout.setAlignment(Qt.AlignTop) pane.setLayout(pane_layout) for prop in ink.properties.values(): field_layout = QHBoxLayout() field_layout.addWidget(QLabel(prop.description)) prop_widget = prop.build_property_widget() field_layout.addWidget(prop_widget) pane_layout.addLayout(field_layout) self._inksOptionsPanel.addWidget(pane) def _select_tool_on_list(self, tool_name): tool_list_item = \ self._toolsListWidget.findItems(tool_name, Qt.MatchExactly)[0] if tool_list_item is not None: self._toolsListWidget.setCurrentItem(tool_list_item) def _select_ink_on_list(self, ink_name): ink_list_item = \ self._inksListWidget.findItems(ink_name, Qt.MatchExactly)[0] if ink_list_item is not None: self._inksListWidget.setCurrentItem(ink_list_item) def _toggle_edit_mode(self): if not self._editMode: self._show_sub_panel() else: self._hide_sub_panel() self.update() def _show_sub_panel(self): self._editMode = True self.resize(self.width(), 300) self._toolbarSubPanel.setVisible(True) def _hide_sub_panel(self): self._editMode = False self.resize(self.width(), 50) self._toolbarSubPanel.setVisible(False) self._finish_ink_edit_mode() def _finish_ink_edit_mode(self): if self._currentEditedInkSlot is not None: self._inksButtonGroup.button(self._currentEditedInkSlot). \ setStyleSheet("border-color: rgb(56,56,56);") self._currentEditedInkSlot = None self._previousEditedInkSlot = None self._inksListWidget.setCurrentRow(0) self._toolbarSubPanel.setCurrentIndex(0) # ------------------------------------------------------------------------- def mousePressEvent(self, e): self._toggle_edit_mode() e.accept() def wheelEvent(self, e): e.accept() def enterEvent(self, e): self.mouseEntered.emit() self.setCursor(Qt.PointingHandCursor) def leaveEvent(self, e): self.mouseLeft.emit() def _on_tool_slot_triggered(self): self._toolbarSubPanel.setCurrentIndex(0) triggered_slot = self._toolsButtonGroup.checkedId() if self._currentEditedInkSlot is not None: self._finish_ink_edit_mode() self.switch_tool_slot(triggered_slot) self.update() def _on_ink_slot_triggered(self, slot_button): if not self._editMode: self._show_sub_panel() triggered_slot_id = self._inksButtonGroup.id(slot_button) if triggered_slot_id != self._currentEditedInkSlot: self._previousEditedInkSlot = self._currentEditedInkSlot self._currentEditedInkSlot = triggered_slot_id if self._previousEditedInkSlot is not None: self._inksButtonGroup. \ button(self._previousEditedInkSlot). \ setStyleSheet("border-color: rgb(56,56,56);") slot_button.setStyleSheet("border-color: rgb(255,0,0);") self._toolbarSubPanel.setCurrentIndex(1) ink_name = self._inkSlots[triggered_slot_id]['id'] self._select_ink_on_list(ink_name) if triggered_slot_id == 0: self.primaryInkChanged.emit(ink_name) elif triggered_slot_id == 1: self.secondaryInkChanged.emit(ink_name) else: self._hide_sub_panel() def _on_tool_list_item_clicked(self, new_item): new_item_name = new_item.text() self._assign_tool_to_slot(self.get_tool_by_name(new_item_name), self._currentActiveToolSlot) self.toolChanged.emit(new_item_name) self._toolbarSubPanel.update() def _on_ink_list_item_clicked(self, item): item_name = item.text() ink = self._registeredInks[item_name] if ink is not None: self._assign_ink_to_slot(ink, self._currentEditedInkSlot)
class FontDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.__font_db = QFontDatabase() self.setWindowTitle(_("Select font")) layout = QVBoxLayout() content_layout = QHBoxLayout() font_list_layout = QVBoxLayout() font_list_layout.addWidget(QLabel(_("Font family:"))) self.__font_search = UpDownPassingLineEdit() self.__font_search.textEdited.connect(self.__family_search_edited) font_list_layout.addWidget(self.__font_search) self.__font_list = QListWidget() self.__font_list.setFocusPolicy(Qt.NoFocus) for font in sorted(self.__font_db.families()): if self.__font_db.isSmoothlyScalable(font): self.__font_list.addItem(font) self.__font_list.currentTextChanged.connect(self.__family_changed) self.__font_search.destination_widget = self.__font_list font_list_layout.addWidget(self.__font_list) content_layout.addLayout(font_list_layout) style_layout = QVBoxLayout() style_layout.setAlignment(Qt.AlignCenter) self.__bold_widget = self.__create_style_button( "B", _("Bold"), QKeySequence.Bold, FontStyle.bold) style_layout.addWidget(self.__bold_widget) self.__italic_widget = self.__create_style_button( "I", _("Italic"), QKeySequence.Italic, FontStyle.italic) style_layout.addWidget(self.__italic_widget) self.__underline_widget = self.__create_style_button( "U", _("Underline"), QKeySequence.Underline, FontStyle.underline) style_layout.addWidget(self.__underline_widget) self.__strike_widget = self.__create_style_button( "S", _("Strike Out"), STRIKE_OUT, FontStyle.strike) style_layout.addWidget(self.__strike_widget) content_layout.addLayout(style_layout) size_layout = QVBoxLayout() size_layout.setAlignment(Qt.AlignHCenter) size_layout.addWidget(QLabel(_("Size (px):"))) self.__size_edit = UpDownPassingLineEdit() self.__size_edit.textEdited.connect(self.__size_edited) self.__size_edit.setValidator(QIntValidator(5, 60)) size_layout.addWidget(self.__size_edit) self.__size_edit.setSizePolicy( QSizePolicy.Preferred, self.__size_edit.sizePolicy().verticalPolicy()) self.__size_slider = QSlider(Qt.Vertical) self.__size_slider.setFocusPolicy(Qt.NoFocus) self.__size_slider.setRange(5, 60) self.__size_slider.setTickInterval(5) self.__size_slider.setTickPosition(QSlider.TicksRight) self.__size_slider.valueChanged.connect(self.__size_changed) self.__size_edit.destination_widget = self.__size_slider size_layout.addWidget(self.__size_slider) content_layout.addLayout(size_layout) layout.addLayout(content_layout) self.__example = QLineEdit("AaBbYyZz") self.__example.setFixedHeight(80) layout.addWidget(self.__example) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.button(QDialogButtonBox.Ok).setText(_("Ok")) button_box.button(QDialogButtonBox.Cancel).setText(_("Cancel")) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) self.setLayout(layout) self.__font = Fonts.default def __create_style_button(self, text, tooltip, keys, font_style): def toggled(value): self.__font = self.__font.change(font_style, value) self.__refresh_example_font() shortcut = QKeySequence(keys) widget = QPushButton(text) widget.setShortcut(shortcut) widget.setToolTip("{0} ({1})".format(tooltip, shortcut.toString())) font = widget.font() if font_style == FontStyle.bold: font.setBold(True) elif font_style == FontStyle.italic: font.setItalic(True) elif font_style == FontStyle.underline: font.setUnderline(True) elif font_style == FontStyle.strike: font.setStrikeOut(True) widget.setFont(font) widget.setCheckable(True) widget.toggled.connect(toggled) return widget @property def ufl_font(self): return self.__font @ufl_font.setter def ufl_font(self, value): self.__font = value for item in self.__font_list.findItems(value.family, Qt.MatchExactly): self.__font_list.setCurrentItem(item) break else: self.__font_list.clearSelection() self.__font_search.setText(value.family) self.__bold_widget.setChecked(FontStyle.bold in value.style) self.__italic_widget.setChecked(FontStyle.italic in value.style) self.__underline_widget.setChecked(FontStyle.underline in value.style) self.__strike_widget.setChecked(FontStyle.strike in value.style) self.__size_slider.setValue(value.size) self.__size_edit.setText(str(value.size)) self.__refresh_example_font() def __refresh_example_font(self): qfont = QFont(self.__font.family) qfont.setPixelSize(self.__font.size) qfont.setBold(FontStyle.bold in self.__font.style) qfont.setItalic(FontStyle.italic in self.__font.style) qfont.setStrikeOut(FontStyle.strike in self.__font.style) qfont.setUnderline(FontStyle.underline in self.__font.style) self.__example.setFont(qfont) def __family_search_edited(self, name): selected_text = self.__font_list.currentItem().text() if selected_text.startswith(name): return for item in self.__font_list.findItems(name, Qt.MatchStartsWith): self.__font_list.setCurrentItem(item) break def __family_changed(self, value): if self.__font.family != value: if not self.__font_search.hasFocus(): self.__font_search.setText(value) self.__font = self.__font.change_family(value) self.__refresh_example_font() def __size_edited(self, value): if not value or int(value) < 5: return int_value = int(value) if self.__font.size != int_value: self.__font = self.__font.change_size(int_value) self.__size_slider.setValue(int_value) self.__refresh_example_font() def __size_changed(self, value): if self.__font.size != value: if self.__size_edit.text() != str(value): self.__size_edit.setText(str(value)) self.__size_edit.selectAll() self.__font = self.__font.change_size(value) self.__refresh_example_font()
def _set_list_item_from_text(lst: qtw.QListWidget, text: str): lst.setCurrentItem(lst.findItems(text, Qt.MatchExactly)[0])
class QSyllabusView(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) start_label = QLabel("Начало", self) self.start_edit = QDateEdit(calendarPopup=True) finish_label = QLabel("Конец", self) self.finish_edit = QDateEdit(calendarPopup=True) studyings_label = QLabel("Расписание", self) self.studyings_list = QListWidget(self) layout = QVBoxLayout() layout.addWidget(start_label) layout.addWidget(self.start_edit) layout.addWidget(finish_label) layout.addWidget(self.finish_edit) layout.addWidget(studyings_label) layout.addWidget(self.studyings_list) self.ok_btn = QPushButton('OK', parent=self) self.ok_btn.clicked.connect(self.ok) self.ok_cancel = QPushButton('Cancel', parent=self) self.ok_cancel.clicked.connect(self.cancel) btn_layout = QHBoxLayout() btn_layout.addWidget(self.ok_btn) btn_layout.addWidget(self.ok_cancel) layout.addLayout(btn_layout) layout.addStretch(0) self.setLayout(layout) self.presenter = SyllabusPresenter(self) def set_studying_items(self, items: str): for i in items: item = QListWidgetItem(i) item.setCheckState(Qt.Unchecked) self.studyings_list.addItem(item) def set_studyings(self, studyings): if not studyings: return for i in studyings: self.studyings_list.findItems(i, Qt.MatchFixedString)[0].setData( Qt.CheckStateRole, Qt.Checked) def get_studyings(self): return [ self.studyings_list.item(i).text() for i in range(self.studyings_list.count()) if self.studyings_list.item(i).checkState() == Qt.Checked ] def get_start(self): return self.start_edit.date().toPyDate() def set_start(self, value): if value is not None: self.start_edit.setDate(value) def get_finish(self): return self.finish_edit.date().toPyDate() def set_finish(self, value): if value is not None: self.finish_edit.setDate(value) def ok(self): self.presenter.ok() def cancel(self): self.presenter.cancel() def get_presenter(self): return self.presenter
class QStudyingView(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.time_label = QLabel("Время начала", self) self.time_edit = QTimeEdit() self.date_label = QLabel("Дата", parent=self) self.date_edit = QDateEdit(calendarPopup=True) self.subjects_label = QLabel("Темы", self) self.subjects_list = QListWidget(self) layout = QVBoxLayout() layout.addWidget(self.time_label) layout.addWidget(self.time_edit) layout.addWidget(self.date_label) layout.addWidget(self.date_edit) layout.addWidget(self.subjects_label) layout.addWidget(self.subjects_list) self.ok_btn = QPushButton('OK', parent=self) self.ok_btn.clicked.connect(self.ok) self.ok_cancel = QPushButton('Cancel', parent=self) self.ok_cancel.clicked.connect(self.cancel) btn_layout = QHBoxLayout() btn_layout.addWidget(self.ok_btn) btn_layout.addWidget(self.ok_cancel) layout.addLayout(btn_layout) layout.addStretch(0) self.setLayout(layout) self.presenter = StudyingPresenter(self) def set_subject_items(self, items: str): for i in items: item = QListWidgetItem(i) item.setCheckState(Qt.Unchecked) self.subjects_list.addItem(item) def set_subjects(self, subjects): if not subjects: return for i in subjects: self.subjects_list.findItems(i, Qt.MatchFixedString)[0].setData( Qt.CheckStateRole, Qt.Checked) def get_subjects(self): return [ self.subjects_list.item(i).text() for i in range(self.subjects_list.count()) if self.subjects_list.item(i).checkState() == Qt.Checked ] def get_time(self): return self.time_edit.time().toPyTime() def set_time(self, value): if value is not None: self.time_edit.setTime(value) def get_date(self): return self.date_edit.date().toPyDate() def set_date(self, value): if value is not None: self.date_edit.setTime(value) def ok(self): self.presenter.ok() def cancel(self): self.presenter.cancel() def get_presenter(self): return self.presenter
class SimulationGui(QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name runSimulation = pyqtSignal() stopSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal(bool) def __init__(self): # constructor of the base class QMainWindow.__init__(self) QCoreApplication.setOrganizationName("RST") QCoreApplication.setOrganizationDomain("https://tu-dresden.de/rst") QCoreApplication.setApplicationVersion( pkg_resources.require("PyMoskito")[0].version) QCoreApplication.setApplicationName(globals()["__package__"]) # load settings self._settings = QSettings() self._read_settings() # initialize logger self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.stopSimulation.connect(self.sim.stop_simulation) self.sim.simulation_finalized.connect(self.new_simulation_data) self.currentDataset = None self.interpolator = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties icon_size = QSize(25, 25) self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.animationDock = pg.dockarea.Dock("Animation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDockPlaceholder = pg.dockarea.Dock("Placeholder") # arrange docks self.area.addDock(self.animationDock, "right") self.area.addDock(self.regimeDock, "left", self.animationDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDockPlaceholder, "bottom", self.animationDock) self.area.addDock(self.logDock, "bottom", self.dataDock) self.non_plotting_docks = list(self.area.findAll()[1].keys()) # add widgets to the docks self.propertyDock.addWidget(self.targetView) if not vtk_available: self._logger.error( "loading vtk failed with:{}".format(vtk_error_msg)) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first visualizer self._logger.info("loading visualizer '{}'".format( available_vis[0][1])) self.animationLayout = QVBoxLayout() if issubclass(available_vis[0][0], MplVisualizer): self.animationWidget = QWidget() self.visualizer = available_vis[0][0](self.animationWidget, self.animationLayout) self.animationDock.addWidget(self.animationWidget) elif issubclass(available_vis[0][0], VtkVisualizer): if vtk_available: # vtk window self.animationFrame = QFrame() self.vtkWidget = QVTKRenderWindowInteractor( self.animationFrame) self.animationLayout.addWidget(self.vtkWidget) self.animationFrame.setLayout(self.animationLayout) self.animationDock.addWidget(self.animationFrame) self.vtk_renderer = vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer( self.vtk_renderer) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self._logger.warning("visualizer depends on vtk which is " "not available on this system!") elif available_vis: raise NotImplementedError else: self.visualizer = None # regime window self.regime_list = QListWidget(self) self.regime_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" self.actDeleteRegimes = QAction(self.regime_list) self.actDeleteRegimes.setText("&Delete Selected Regimes") # TODO shortcut works always, not only with focus on the regime list # self.actDeleteRegimes.setShortcutContext(Qt.WindowShortcut) self.actDeleteRegimes.setShortcut(QKeySequence(Qt.Key_Delete)) self.actDeleteRegimes.triggered.connect(self.remove_regime_items) self.actSave = QAction(self) self.actSave.setText('Save Results As') self.actSave.setIcon(QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.setShortcut(QKeySequence.Save) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QAction(self) self.actLoadRegimes.setText("Load Regimes from File") self.actLoadRegimes.setIcon(QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.setShortcut(QKeySequence.Open) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExitOnBatchCompletion = QAction(self) self.actExitOnBatchCompletion.setText("&Exit On Batch Completion") self.actExitOnBatchCompletion.setCheckable(True) self.actExitOnBatchCompletion.setChecked( self._settings.value("control/exit_on_batch_completion") == "True") self.actExitOnBatchCompletion.changed.connect( self.update_exit_on_batch_completion_setting) # regime management self.runningBatch = False self._current_regime_index = None self._current_regime_name = None self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # data window self.dataList = QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulateCurrent = QAction(self) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.setShortcut(QKeySequence("F5")) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actSimulateAll = QAction(self) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.setShortcut(QKeySequence("F6")) self.actSimulateAll.setDisabled(True) self.actSimulateAll.triggered.connect(self.start_regime_execution) # actions for animation control self.actAutoPlay = QAction(self) self.actAutoPlay.setText("&Autoplay Simulation") self.actAutoPlay.setCheckable(True) self.actAutoPlay.setChecked( self._settings.value("control/autoplay_animation") == "True") self.actAutoPlay.changed.connect(self.update_autoplay_setting) self.actPlayPause = QAction(self) self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.setShortcut(QKeySequence(Qt.Key_Space)) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.actSlow = QAction(self) self.actSlow.setText("Slowest") self.actSlow.setIcon(QIcon(get_resource("slow.png"))) self.actSlow.setDisabled(False) self.actSlow.triggered.connect(self.set_slowest_playback_speed) self.actFast = QAction(self) self.actFast.setText("Fastest") self.actFast.setIcon(QIcon(get_resource("fast.png"))) self.actFast.setDisabled(False) self.actFast.triggered.connect(self.set_fastest_playback_speed) self.speedControl = QSlider(Qt.Horizontal, self) self.speedControl.setMaximumSize(200, 25) self.speedControl.setTickPosition(QSlider.TicksBothSides) self.speedControl.setDisabled(False) self.speedControl.setMinimum(0) self.speedControl.setMaximum(12) self.speedControl.setValue(6) self.speedControl.setTickInterval(6) self.speedControl.setSingleStep(2) self.speedControl.setPageStep(3) self.speedControl.valueChanged.connect(self.update_playback_speed) self.timeSlider = QSlider(Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = .0 self.playbackGain = 1 self.currentStepSize = .0 self.currentEndTime = .0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actResetCamera = QAction(self) self.actResetCamera.setText("Reset Camera") self.actResetCamera.setIcon(QIcon(get_resource("reset_camera.png"))) self.actResetCamera.setDisabled(True) if available_vis: self.actResetCamera.setEnabled(self.visualizer.can_reset_view) self.actResetCamera.triggered.connect(self.reset_camera_clicked) # postprocessing self.actPostprocessing = QAction(self) self.actPostprocessing.setText("Launch Postprocessor") self.actPostprocessing.setIcon(QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.actPostprocessing.setShortcut(QKeySequence("F7")) self.postprocessor = None # toolbar self.toolbarSim = QToolBar("Simulation") self.toolbarSim.setContextMenuPolicy(Qt.PreventContextMenu) self.toolbarSim.setMovable(False) self.toolbarSim.setIconSize(icon_size) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulateCurrent) self.toolbarSim.addAction(self.actSimulateAll) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSlow) self.toolbarSim.addWidget(self.speedControl) self.toolbarSim.addAction(self.actFast) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.actResetCamera) self.postprocessor = None # log dock self.logBox = QPlainTextEdit(self) self.logBox.setReadOnly(True) self.logDock.addWidget(self.logBox) # init logger for logging box self.textLogger = PlainTextLogger(logging.INFO) self.textLogger.set_target_cb(self.logBox.appendPlainText) logging.getLogger().addHandler(self.textLogger) # menu bar fileMenu = self.menuBar().addMenu("&File") fileMenu.addAction(self.actLoadRegimes) fileMenu.addAction(self.actSave) fileMenu.addAction("&Quit", self.close) editMenu = self.menuBar().addMenu("&Edit") editMenu.addAction(self.actDeleteRegimes) simMenu = self.menuBar().addMenu("&Simulation") simMenu.addAction(self.actSimulateCurrent) simMenu.addAction(self.actSimulateAll) simMenu.addAction(self.actExitOnBatchCompletion) simMenu.addAction(self.actPostprocessing) animMenu = self.menuBar().addMenu("&Animation") animMenu.addAction(self.actPlayPause) animMenu.addAction("&Increase Playback Speed", self.increment_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Plus)) animMenu.addAction("&Decrease Playback Speed", self.decrement_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Minus)) animMenu.addAction("&Reset Playback Speed", self.reset_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_0)) animMenu.addAction(self.actAutoPlay) animMenu.addAction(self.actResetCamera) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("&Online Documentation", self.show_online_docs) helpMenu.addAction("&About", self.show_info) # status bar self.status = QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) self._logger.info("Simulation GUI is up and running.") def _read_settings(self): # add default settings if none are present if not self._settings.contains("path/simulation_results"): self._settings.setValue( "path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) if not self._settings.contains("path/postprocessing_results"): self._settings.setValue( "path/postprocessing_results", os.path.join(os.path.curdir, "results", "postprocessing")) if not self._settings.contains("path/metaprocessing_results"): self._settings.setValue( "path/metaprocessing_results", os.path.join(os.path.curdir, "results", "metaprocessing")) if not self._settings.contains("control/autoplay_animation"): self._settings.setValue("control/autoplay_animation", "False") if not self._settings.contains("control/exit_on_batch_completion"): self._settings.setValue("control/exit_on_batch_completion", "False") def _write_settings(self): """ Store the application state. """ pass @pyqtSlot() def update_autoplay_setting(self): self._settings.setValue("control/autoplay_animation", str(self.actAutoPlay.isChecked())) @pyqtSlot() def update_exit_on_batch_completion_setting(self, state=None): if state is None: state = self.actExitOnBatchCompletion.isChecked() self._settings.setValue("control/exit_on_batch_completion", str(state)) def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() @pyqtSlot() def play_animation(self): """ play the animation """ self._logger.debug("Starting Playback") # if we are at the end, start from the beginning if self.playbackTime == self.currentEndTime: self.timeSlider.setValue(0) self.actPlayPause.setText("Pause Animation") self.actPlayPause.setIcon(QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) @pyqtSlot() def pause_animation(self): """ pause the animation """ self._logger.debug("Pausing Playback") self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) def stop_animation(self): """ Stop the animation if it is running and reset the playback time. """ self._logger.debug("Stopping Playback") if self.actPlayPause.text() == "Pause Animation": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.timeSlider.setValue(0) @pyqtSlot() def start_simulation(self): """ start the simulation and disable start button """ if self._current_regime_index is None: regime_name = "" else: regime_name = str( self.regime_list.item(self._current_regime_index).text()) self.statusLabel.setText("simulating {}".format(regime_name)) self._logger.info("Simulating: {}".format(regime_name)) self.actSimulateCurrent.setIcon( QIcon(get_resource("stop_simulation.png"))) self.actSimulateCurrent.setText("Abort &Simulation") self.actSimulateCurrent.triggered.disconnect(self.start_simulation) self.actSimulateCurrent.triggered.connect(self.stop_simulation) if not self.runningBatch: self.actSimulateAll.setDisabled(True) self.guiProgress = QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() @pyqtSlot() def stop_simulation(self): self.stopSimulation.emit() def export_simulation_data(self, ok): """ Query the user for a custom name and export the current simulation results. :param ok: unused parameter from QAction.triggered() Signal """ self._save_data() def _save_data(self, file_path=None): """ Save the current simulation results. If *fie_name* is given, the result will be saved to the specified location, making automated exporting easier. Args: file_path(str): Absolute path of the target file. If `None` the use will be asked for a storage location. """ regime_name = self._regimes[self._current_regime_index]["Name"] if file_path is None: # get default path path = self._settings.value("path/simulation_results") # create canonic file name suggestion = self._simfile_name(regime_name) else: path = os.path.dirname(file_path) suggestion = os.path.basename(file_path) # check if path exists otherwise create it if not os.path.isdir(path): box = QMessageBox() box.setText("Export Folder does not exist yet.") box.setInformativeText("Do you want to create it? \n" "{}".format(os.path.abspath(path))) box.setStandardButtons(QMessageBox.Ok | QMessageBox.No) box.setDefaultButton(QMessageBox.Ok) ret = box.exec_() if ret == QMessageBox.Ok: os.makedirs(path) else: path = os.path.abspath(os.path.curdir) file_path = None # If no path was given, present the default and let the user choose if file_path is None: dialog = QFileDialog(self) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.AnyFile) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Results (*.pmr)") dialog.selectFile(suggestion) if dialog.exec_(): file_path = dialog.selectedFiles()[0] else: self._logger.warning("Export Aborted") return -1 # ask whether this should act as new default path = os.path.abspath(os.path.dirname(file_path)) if path != self._settings.value("path/simulation_results"): box = QMessageBox() box.setText("Use this path as new default?") box.setInformativeText("{}".format(path)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.Yes) ret = box.exec_() if ret == QMessageBox.Yes: self._settings.setValue("path/simulation_results", path) self.currentDataset.update({"regime name": regime_name}) with open(file_path, "wb") as f: pickle.dump(self.currentDataset, f, protocol=4) self.statusLabel.setText("results saved to {}".format(file_path)) self._logger.info("results saved to {}".format(file_path)) def _simfile_name(self, regime_name): """ Create a canonical name for a simulation result file """ suggestion = (time.strftime("%Y%m%d-%H%M%S") + "_" + regime_name + ".pmr") return suggestion def load_regime_dialog(self): regime_path = os.path.join(os.curdir) dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setDirectory(regime_path) dialog.setNameFilter("Simulation Regime files (*.sreg)") if dialog.exec_(): file = dialog.selectedFiles()[0] self.load_regimes_from_file(file) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info("loading regime file: {0}".format( self.regime_file_name)) with open(file_name.encode(), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actSimulateAll.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage( "loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format( reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) @pyqtSlot(QListWidgetItem) def regime_dclicked(self, item): """ Apply the selected regime to the current target. """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ Apply the regime given by `regime_name` und update the regime index. Returns: bool: `True` if successful, `False` if errors occurred. """ # get regime idx try: idx = list(map(itemgetter("Name"), self._regimes)).index(regime_name) except ValueError as e: self._logger.error( "apply_regime_by_name(): Error no regime called " "'{0}'".format(regime_name)) return False # apply return self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): """ Apply the given regime. Args: index(int): Index of the regime in the `RegimeList` . Returns: bool: `True` if successful, `False` if errors occurred. """ if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return False reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self._current_regime_name = reg_name return self.sim.set_regime(self._regimes[index]) @pyqtSlot() def start_regime_execution(self): """ Simulate all regimes in the regime list. """ self.actSimulateAll.setText("Stop Simulating &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("stop_batch.png"))) self.actSimulateAll.triggered.disconnect(self.start_regime_execution) self.actSimulateAll.triggered.connect(self.stop_regime_excecution) self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ Execute the next regime in the regime batch. """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit(True) return suc = self._apply_regime_by_idx(self._current_regime_index + 1) if not suc: self.finishedRegimeBatch.emit(False) return self.start_simulation() @pyqtSlot() def stop_regime_excecution(self): """ Stop the batch process. """ self.stopSimulation.emit() self.finishedRegimeBatch.emit(False) def regime_batch_finished(self, status): self.runningBatch = False self.actSimulateAll.setDisabled(False) self.actSave.setDisabled(True) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.triggered.disconnect(self.stop_regime_excecution) self.actSimulateAll.triggered.connect(self.start_regime_execution) if status: self.statusLabel.setText("All regimes have been simulated") self._logger.info("All Regimes have been simulated") else: self._logger.error("Batch simulation has been aborted") if self._settings.value("control/exit_on_batch_completion") == "True": self._logger.info("Shutting down SimulationGUI") self.close() @pyqtSlot(str, dict, name="new_simulation_data") def new_simulation_data(self, status, data): """ Slot to be called when the simulation interface has completed the current job and new data is available. Args: status (str): Status of the simulation, either - `finished` : Simulation has been finished successfully or - `failed` : Simulation has failed. data (dict): Dictionary, holding the simulation data. """ self._logger.info("Simulation {}".format(status)) self.statusLabel.setText("Simulation {}".format(status)) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.triggered.disconnect(self.stop_simulation) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actPlayPause.setDisabled(False) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedControl.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect( self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.stop_animation() self.currentDataset = data if data: self._read_results() self._update_data_list() self._update_plots() if self._settings.value("control/autoplay_animation") == "True": self.actPlayPause.trigger() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] file_name = self._simfile_name(regime_name) self._save_data( os.path.join(self._settings.value("path/simulation_results"), file_name)) self.regimeFinished.emit() else: self.actSimulateAll.setDisabled(False) def _read_results(self): state = self.currentDataset["results"]["Solver"] self.interpolator = interp1d(self.currentDataset["results"]["time"], state, axis=0, bounds_error=False, fill_value=(state[0], state[-1])) self.currentStepSize = 1.0 / self.currentDataset["simulation"][ "measure rate"] self.currentEndTime = self.currentDataset["simulation"]["end time"] self.validData = True def increment_playback_speed(self): self.speedControl.setValue(self.speedControl.value() + self.speedControl.singleStep()) def decrement_playback_speed(self): self.speedControl.setValue(self.speedControl.value() - self.speedControl.singleStep()) def reset_playback_speed(self): self.speedControl.setValue( (self.speedControl.maximum() - self.speedControl.minimum()) / 2) def set_slowest_playback_speed(self): self.speedControl.setValue(self.speedControl.minimum()) def set_fastest_playback_speed(self): self.speedControl.setValue(self.speedControl.maximum()) def update_playback_speed(self, val): """ adjust playback time to slider value :param val: """ maximum = self.speedControl.maximum() self.playbackGain = 10**(3.0 * (val - maximum / 2) / maximum) @pyqtSlot() def increment_playback_time(self): """ go one time step forward in playback """ if self.playbackTime == self.currentEndTime: self.pause_animation() return increment = self.playbackGain * self.playbackTimeout / 1000 self.playbackTime = min(self.currentEndTime, self.playbackTime + increment) pos = int(self.playbackTime / self.currentEndTime * self.timeSliderRange) self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value( ) / self.timeSliderRange * self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText("current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursors() # update state of rendering if self.visualizer: state = self.interpolator(self.playbackTime) self.visualizer.update_scene(state) if isinstance(self.visualizer, MplVisualizer): pass elif isinstance(self.visualizer, VtkVisualizer): self.vtkWidget.GetRenderWindow().Render() def _update_data_list(self): self.dataList.clear() for module_name, results in self.currentDataset["results"].items(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, module_name) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, ))) elif len(results.shape) == 3: for col in range(results.shape[1]): for der in range(results.shape[2]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, der))) def _build_entry_name(self, module_name, idx): """ Construct an identifier for a given entry of a module. Args: module_name (str): name of the module the entry belongs to. idx (tuple): Index of the entry. Returns: str: Identifier to use for display. """ # save the user from defining 1d entries via tuples if len(idx) == 1: m_idx = idx[0] else: m_idx = idx mod_settings = self.currentDataset["modules"] info = mod_settings.get(module_name, {}).get("output_info", None) if info: if m_idx in info: return ".".join([module_name, info[m_idx]["Name"]]) return ".".join([module_name] + [str(i) for i in idx]) def _get_index_from_suffix(self, module_name, suffix): info = self.currentDataset["modules"].get(module_name, {}).get("output_info", None) idx = next((i for i in info if info[i]["Name"] == suffix), None) return idx def _get_units(self, entry): """ Return the unit that corresponds to a given entry. If no information is available, None is returned. Args: entry (str): Name of the entry. This can either be "Model.a.b" where a and b are numbers or if information is available "Model.Signal" where signal is the name of that part. Returns: """ args = entry.split(".") module_name = args.pop(0) info = self.currentDataset["modules"].get(module_name, {}).get("output_info", None) if info is None: return None if len(args) == 1: try: idx = int(args[0]) except ValueError: idx = next((i for i in info if info[i]["Name"] == args[0]), None) else: idx = (int(a) for a in args) return info[idx]["Unit"] def create_plot(self, item): """ Creates a plot widget based on the given item. If a plot for this item is already open no new plot is created but the existing one is raised up again. Args: item(Qt.ListItem): Item to plot. """ title = str(item.text()) if title in self.non_plotting_docks: self._logger.error("Title '{}' not allowed for a plot window since" "it would shadow on of the reserved " "names".format(title)) # check if plot has already been opened if title in self.area.findAll()[1]: self.area.docks[title].raiseDock() return # collect data data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] unit = self._get_units(title) if "." in title: name = title.split(".")[1] else: name = title # create plot widget widget = pg.PlotWidget(title=title) widget.showGrid(True, True) widget.plot(x=t, y=data) widget.getPlotItem().getAxis("bottom").setLabel(text="Time", units="s") widget.getPlotItem().getAxis("left").setLabel(text=name, units=unit) # add a time line time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # create dock container and add it to dock area dock = pg.dockarea.Dock(title, closable=True) dock.addWidget(widget) self.area.addDock(dock, "above", self.plotDockPlaceholder) def _get_data_by_name(self, name): tmp = name.split(".") module_name = tmp[0] if len(tmp) == 1: data = np.array(self.currentDataset["results"][module_name]) elif len(tmp) == 2: try: idx = int(tmp[1]) except ValueError: idx = self._get_index_from_suffix(module_name, tmp[1]) finally: data = self.currentDataset["results"][module_name][..., idx] elif len(tmp) == 3: idx = int(tmp[1]) der = int(tmp[2]) data = self.currentDataset["results"][module_name][..., idx, der] else: raise ValueError("Format not supported") return data def _update_time_cursors(self): """ Update the time lines of all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.InfiniteLine): item.setValue(self.playbackTime) def _update_plots(self): """ Update the data in all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue if not self.dataList.findItems(dock.name(), Qt.MatchExactly): # no data for this plot -> remove it dock.close() continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.PlotDataItem): x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) item.setData(x=x_data, y=y_data) @pyqtSlot(QModelIndex) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info("launching postprocessor") self.statusBar().showMessage("launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render() def show_info(self): icon_lic = open(get_resource("license.txt"), "r").read() text = "This application was build using PyMoskito ver. {} .<br />" \ "PyMoskito is free software distributed under GPLv3. <br />" \ "It is developed by members of the " \ "<a href=\'https://tu-dresden.de/ing/elektrotechnik/rst'>" \ "Institute of Control Theory</a>" \ " at the <a href=\'https://tu-dresden.de'>" \ "Dresden University of Technology</a>. <br />" \ "".format(pkg_resources.require("PyMoskito")[0].version) \ + "<br />" + icon_lic box = QMessageBox.about(self, "PyMoskito", text) def show_online_docs(self): webbrowser.open("https://pymoskito.readthedocs.org") def closeEvent(self, QCloseEvent): self._logger.info("Close Event received, shutting down.") logging.getLogger().removeHandler(self.textLogger) super().closeEvent(QCloseEvent)
class EditorGeneral(QWidget): """EditorGeneral widget class.""" def __init__(self, parent): super(EditorGeneral, self).__init__() self._preferences, vbox = parent, QVBoxLayout(self) self.original_style = copy.copy(resources.CUSTOM_SCHEME) self.current_scheme, self._modified_editors = 'default', [] self._font = settings.FONT groupBoxMini = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_MINIMAP) groupBoxTypo = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_TYPOGRAPHY) groupBoxScheme = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_SCHEME) #Minimap formMini = QGridLayout(groupBoxMini) formMini.setContentsMargins(5, 15, 5, 5) self._checkShowMinimap = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_ENABLE_MINIMAP) self._spinMaxOpacity = QSpinBox() self._spinMaxOpacity.setRange(0, 100) self._spinMaxOpacity.setSuffix("% Max.") self._spinMinOpacity = QSpinBox() self._spinMinOpacity.setRange(0, 100) self._spinMinOpacity.setSuffix("% Min.") self._spinSize = QSpinBox() self._spinSize.setMaximum(100) self._spinSize.setMinimum(0) self._spinSize.setSuffix( translations.TR_PREFERENCES_EDITOR_GENERAL_AREA_MINIMAP) formMini.addWidget(self._checkShowMinimap, 0, 1) formMini.addWidget(QLabel( translations.TR_PREFERENCES_EDITOR_GENERAL_SIZE_MINIMAP), 1, 0, Qt.AlignRight) formMini.addWidget(self._spinSize, 1, 1) formMini.addWidget(QLabel( translations.TR_PREFERENCES_EDITOR_GENERAL_OPACITY), 2, 0, Qt.AlignRight) formMini.addWidget(self._spinMinOpacity, 2, 1) formMini.addWidget(self._spinMaxOpacity, 2, 2) #Typo gridTypo = QGridLayout(groupBoxTypo) gridTypo.setContentsMargins(5, 15, 5, 5) self._btnEditorFont = QPushButton('') gridTypo.addWidget(QLabel( translations.TR_PREFERENCES_EDITOR_GENERAL_EDITOR_FONT), 0, 0, Qt.AlignRight) gridTypo.addWidget(self._btnEditorFont, 0, 1) #Scheme vboxScheme = QVBoxLayout(groupBoxScheme) vboxScheme.setContentsMargins(5, 15, 5, 5) self._listScheme = QListWidget() vboxScheme.addWidget(self._listScheme) hbox = QHBoxLayout() btnDownload = QPushButton( translations.TR_PREFERENCES_EDITOR_DOWNLOAD_SCHEME) btnDownload.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnDownload.clicked['bool'].connect(self._open_schemes_manager) hbox.addWidget(btnDownload) btnAdd = QPushButton(QIcon(":img/add"), translations.TR_EDITOR_CREATE_SCHEME) btnAdd.setIconSize(QSize(16, 16)) btnAdd.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnAdd.clicked['bool'].connect(self._open_schemes_designer) btnRemove = QPushButton(QIcon(":img/delete"), translations.TR_EDITOR_REMOVE_SCHEME) btnRemove.setIconSize(QSize(16, 16)) btnRemove.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnRemove.clicked['bool'].connect(self._remove_scheme) hbox.addSpacerItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) hbox.addWidget(btnAdd) hbox.addWidget(btnRemove) vboxScheme.addLayout(hbox) vbox.addWidget(groupBoxMini) vbox.addWidget(groupBoxTypo) vbox.addWidget(groupBoxScheme) #Settings qsettings = IDE.ninja_settings() qsettings.beginGroup('preferences') qsettings.beginGroup('editor') self._checkShowMinimap.setChecked(settings.SHOW_MINIMAP) if settings.IS_MAC_OS: self._spinMinOpacity.setValue(100) self._spinMaxOpacity.setValue(100) self._spinMinOpacity.setDisabled(True) self._spinMaxOpacity.setDisabled(True) else: self._spinMinOpacity.setValue(settings.MINIMAP_MIN_OPACITY * 100) self._spinMaxOpacity.setValue(settings.MINIMAP_MAX_OPACITY * 100) self._spinSize.setValue(settings.SIZE_PROPORTION * 100) btnText = ', '.join(self._font.toString().split(',')[0:2]) self._btnEditorFont.setText(btnText) self._listScheme.clear() self._listScheme.addItem('default') self._schemes = json_manager.load_editor_skins() for item in self._schemes: self._listScheme.addItem(item) items = self._listScheme.findItems( qsettings.value('scheme', defaultValue='', type='QString'), Qt.MatchExactly) if items: self._listScheme.setCurrentItem(items[0]) else: self._listScheme.setCurrentRow(0) qsettings.endGroup() qsettings.endGroup() #Signals self._btnEditorFont.clicked['bool'].connect(self._load_editor_font) self._listScheme.itemSelectionChanged.connect(self._preview_style) self._preferences.savePreferences.connect(self.save) def _open_schemes_manager(self): ninjaide = IDE.getInstance() ninjaide.show_schemes() # refresh schemes def _open_schemes_designer(self): name = self._listScheme.currentItem().text() scheme = self._schemes.get(name, resources.COLOR_SCHEME) designer = preferences_editor_scheme_designer.EditorSchemeDesigner( scheme, self) designer.exec_() if designer.saved: scheme_name = designer.line_name.text() scheme = designer.original_style self._schemes[scheme_name] = scheme result = self._listScheme.findItems(scheme_name, Qt.MatchExactly) if not result: self._listScheme.addItem(scheme_name) def _remove_scheme(self): name = self._listScheme.currentItem().text() fileName = ('{0}.color'.format( file_manager.create_path(resources.EDITOR_SKINS, name))) file_manager.delete_file(fileName) item = self._listScheme.takeItem(self._listScheme.currentRow()) del item def hideEvent(self, event): super(EditorGeneral, self).hideEvent(event) resources.CUSTOM_SCHEME = self.original_style for editorWidget in self._modified_editors: try: editorWidget.restyle(editorWidget.lang) except RuntimeError: print('the editor has been removed') def _preview_style(self): scheme = self._listScheme.currentItem().text() if scheme == self.current_scheme: return main_container = IDE.get_service('main_container') if not main_container: return editorWidget = main_container.get_current_editor() if editorWidget is not None: resources.CUSTOM_SCHEME = self._schemes.get( scheme, resources.COLOR_SCHEME) editorWidget.restyle(editorWidget.lang) self._modified_editors.append(editorWidget) self.current_scheme = scheme def _load_editor_font(self): try: font, ok = QFontDialog.getFont(self._font, self) if ok: self._font = font btnText = ', '.join(self._font.toString().split(',')[0:2]) self._btnEditorFont.setText(btnText) except: QMessageBox.warning( self, translations.TR_PREFERENCES_EDITOR_GENERAL_FONT_MESSAGE_TITLE, translations.TR_PREFERENCES_EDITOR_GENERAL_FONT_MESSAGE_BODY) def save(self): qsettings = IDE.ninja_settings() settings.FONT = self._font qsettings.setValue('preferences/editor/font', settings.FONT) settings.SHOW_MINIMAP = self._checkShowMinimap.isChecked() settings.MINIMAP_MAX_OPACITY = self._spinMaxOpacity.value() / 100.0 settings.MINIMAP_MIN_OPACITY = self._spinMinOpacity.value() / 100.0 settings.SIZE_PROPORTION = self._spinSize.value() / 100.0 qsettings.setValue('preferences/editor/minimapMaxOpacity', settings.MINIMAP_MAX_OPACITY) qsettings.setValue('preferences/editor/minimapMinOpacity', settings.MINIMAP_MIN_OPACITY) qsettings.setValue('preferences/editor/minimapSizeProportion', settings.SIZE_PROPORTION) qsettings.setValue('preferences/editor/minimapShow', settings.SHOW_MINIMAP) scheme = self._listScheme.currentItem().text() resources.CUSTOM_SCHEME = self._schemes.get(scheme, resources.COLOR_SCHEME) qsettings.setValue('preferences/editor/scheme', scheme)
class EditorGeneral(QWidget): """EditorGeneral widget class.""" def __init__(self, parent): super(EditorGeneral, self).__init__() self._preferences, vbox = parent, QVBoxLayout(self) self.original_style = copy.copy(resources.CUSTOM_SCHEME) self.current_scheme, self._modified_editors = 'default', [] self._font = settings.FONT groupBoxMini = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_MINIMAP) groupBoxDocMap = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_DOCMAP) groupBoxTypo = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_TYPOGRAPHY) groupBoxScheme = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_SCHEME) boxMiniAndDocMap = QHBoxLayout() # Minimap formMini = QGridLayout(groupBoxMini) formMini.setContentsMargins(5, 15, 5, 5) self._checkShowMinimap = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_ENABLE_MINIMAP) self._spinMaxOpacity = QSpinBox() self._spinMaxOpacity.setRange(0, 100) self._spinMaxOpacity.setSuffix("% Max.") self._spinMinOpacity = QSpinBox() self._spinMinOpacity.setRange(0, 100) self._spinMinOpacity.setSuffix("% Min.") self._spinSize = QSpinBox() self._spinSize.setMaximum(100) self._spinSize.setMinimum(0) self._spinSize.setSuffix( translations.TR_PREFERENCES_EDITOR_GENERAL_AREA_MINIMAP) formMini.addWidget(self._checkShowMinimap, 0, 1) formMini.addWidget( QLabel(translations.TR_PREFERENCES_EDITOR_GENERAL_SIZE_MINIMAP), 1, 0, Qt.AlignRight) formMini.addWidget(self._spinSize, 1, 1) formMini.addWidget( QLabel(translations.TR_PREFERENCES_EDITOR_GENERAL_OPACITY), 2, 0, Qt.AlignRight) formMini.addWidget(self._spinMinOpacity, 2, 1) formMini.addWidget(self._spinMaxOpacity, 2, 2) boxMiniAndDocMap.addWidget(groupBoxMini) # Document Map formDocMap = QGridLayout(groupBoxDocMap) self._checkShowDocMap = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_ENABLE_DOCMAP) self._checkShowSlider = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_DOCMAP_SLIDER) self._checkOriginalScroll = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_ORIGINAL_SCROLLBAR) self._checkCurrentLine = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_DOCMAP_CURRENT_LINE) self._checkSearchLines = QCheckBox( translations.TR_PREFERENCES_EDITOR_GENERAL_DOCMAP_SEARCH_LINES) self._spinWidth = QSpinBox() self._spinWidth.setRange(5, 40) formDocMap.addWidget(self._checkShowDocMap, 0, 0) formDocMap.addWidget(self._checkShowSlider, 0, 1) formDocMap.addWidget(self._checkOriginalScroll, 0, 2) formDocMap.addWidget(self._checkCurrentLine, 1, 0) formDocMap.addWidget(self._checkSearchLines, 1, 1) formDocMap.addWidget( QLabel(translations.TR_PREFERENCES_EDITOR_GENERAL_DOCMAP_WIDTH), 2, 0) formDocMap.addWidget(self._spinWidth, 2, 1) boxMiniAndDocMap.addWidget(groupBoxDocMap) # Typo gridTypo = QGridLayout(groupBoxTypo) gridTypo.setContentsMargins(5, 15, 5, 5) self._btnEditorFont = QPushButton('') gridTypo.addWidget( QLabel(translations.TR_PREFERENCES_EDITOR_GENERAL_EDITOR_FONT), 0, 0, Qt.AlignRight) gridTypo.addWidget(self._btnEditorFont, 0, 1) #Scheme vboxScheme = QVBoxLayout(groupBoxScheme) vboxScheme.setContentsMargins(5, 15, 5, 5) self._listScheme = QListWidget() vboxScheme.addWidget(self._listScheme) hbox = QHBoxLayout() btnDownload = QPushButton( translations.TR_PREFERENCES_EDITOR_DOWNLOAD_SCHEME) btnDownload.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnDownload.clicked['bool'].connect(self._open_schemes_manager) hbox.addWidget(btnDownload) btnAdd = QPushButton(QIcon(":img/add"), translations.TR_EDITOR_CREATE_SCHEME) btnAdd.setIconSize(QSize(16, 16)) btnAdd.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnAdd.clicked['bool'].connect(self._open_schemes_designer) btnRemove = QPushButton(QIcon(":img/delete"), translations.TR_EDITOR_REMOVE_SCHEME) btnRemove.setIconSize(QSize(16, 16)) btnRemove.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btnRemove.clicked['bool'].connect(self._remove_scheme) hbox.addSpacerItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) hbox.addWidget(btnAdd) hbox.addWidget(btnRemove) vboxScheme.addLayout(hbox) vbox.addLayout(boxMiniAndDocMap) vbox.addWidget(groupBoxTypo) vbox.addWidget(groupBoxScheme) #Settings qsettings = IDE.ninja_settings() qsettings.beginGroup('preferences') qsettings.beginGroup('editor') self._checkShowMinimap.setChecked(settings.SHOW_MINIMAP) if settings.IS_MAC_OS: self._spinMinOpacity.setValue(100) self._spinMaxOpacity.setValue(100) self._spinMinOpacity.setDisabled(True) self._spinMaxOpacity.setDisabled(True) else: self._spinMinOpacity.setValue(settings.MINIMAP_MIN_OPACITY * 100) self._spinMaxOpacity.setValue(settings.MINIMAP_MAX_OPACITY * 100) self._checkShowDocMap.setChecked(settings.SHOW_DOCMAP) self._checkShowSlider.setChecked(settings.DOCMAP_SLIDER) self._checkOriginalScroll.setChecked(settings.EDITOR_SCROLLBAR) self._checkCurrentLine.setChecked(settings.DOCMAP_CURRENT_LINE) self._checkSearchLines.setChecked(settings.DOCMAP_SEARCH_LINES) self._spinWidth.setValue(settings.DOCMAP_WIDTH) self._spinSize.setValue(settings.SIZE_PROPORTION * 100) btnText = ', '.join(self._font.toString().split(',')[0:2]) self._btnEditorFont.setText(btnText) self._listScheme.clear() self._listScheme.addItem('default') self._schemes = json_manager.load_editor_skins() for item in self._schemes: self._listScheme.addItem(item) items = self._listScheme.findItems( qsettings.value('scheme', defaultValue='', type='QString'), Qt.MatchExactly) if items: self._listScheme.setCurrentItem(items[0]) else: self._listScheme.setCurrentRow(0) qsettings.endGroup() qsettings.endGroup() #Signals self._btnEditorFont.clicked['bool'].connect(self._load_editor_font) self._listScheme.itemSelectionChanged.connect(self._preview_style) self._preferences.savePreferences.connect(self.save) def _open_schemes_manager(self): ninjaide = IDE.getInstance() ninjaide.show_schemes() # refresh schemes def _open_schemes_designer(self): name = self._listScheme.currentItem().text() scheme = self._schemes.get(name, resources.COLOR_SCHEME) designer = preferences_editor_scheme_designer.EditorSchemeDesigner( scheme, self) designer.exec_() if designer.saved: scheme_name = designer.line_name.text() scheme = designer.original_style self._schemes[scheme_name] = scheme result = self._listScheme.findItems(scheme_name, Qt.MatchExactly) if not result: self._listScheme.addItem(scheme_name) def _remove_scheme(self): name = self._listScheme.currentItem().text() fileName = ('{0}.color'.format( file_manager.create_path(resources.EDITOR_SKINS, name))) file_manager.delete_file(fileName) item = self._listScheme.takeItem(self._listScheme.currentRow()) del item def hideEvent(self, event): super(EditorGeneral, self).hideEvent(event) resources.CUSTOM_SCHEME = self.original_style for editorWidget in self._modified_editors: try: editorWidget.restyle(editorWidget.lang) except RuntimeError: print('the editor has been removed') def _preview_style(self): scheme = self._listScheme.currentItem().text() if scheme == self.current_scheme: return main_container = IDE.get_service('main_container') if not main_container: return editorWidget = main_container.get_current_editor() if editorWidget is not None: resources.CUSTOM_SCHEME = self._schemes.get( scheme, resources.COLOR_SCHEME) editorWidget.restyle(editorWidget.lang) self._modified_editors.append(editorWidget) self.current_scheme = scheme def _load_editor_font(self): try: font, ok = QFontDialog.getFont(self._font, self) if ok: self._font = font btnText = ', '.join(self._font.toString().split(',')[0:2]) self._btnEditorFont.setText(btnText) except: QMessageBox.warning( self, translations.TR_PREFERENCES_EDITOR_GENERAL_FONT_MESSAGE_TITLE, translations.TR_PREFERENCES_EDITOR_GENERAL_FONT_MESSAGE_BODY) def save(self): qsettings = IDE.ninja_settings() settings.FONT = self._font qsettings.setValue('preferences/editor/font', settings.FONT) settings.SHOW_MINIMAP = self._checkShowMinimap.isChecked() settings.MINIMAP_MAX_OPACITY = self._spinMaxOpacity.value() / 100.0 settings.MINIMAP_MIN_OPACITY = self._spinMinOpacity.value() / 100.0 settings.SIZE_PROPORTION = self._spinSize.value() / 100.0 qsettings.setValue('preferences/editor/minimapMaxOpacity', settings.MINIMAP_MAX_OPACITY) qsettings.setValue('preferences/editor/minimapMinOpacity', settings.MINIMAP_MIN_OPACITY) qsettings.setValue('preferences/editor/minimapSizeProportion', settings.SIZE_PROPORTION) qsettings.setValue('preferences/editor/minimapShow', settings.SHOW_MINIMAP) settings.SHOW_DOCMAP = self._checkShowDocMap.isChecked() settings.DOCMAP_SLIDER = self._checkShowSlider.isChecked() settings.EDITOR_SCROLLBAR = self._checkOriginalScroll.isChecked() settings.DOCMAP_CURRENT_LINE = self._checkCurrentLine.isChecked() settings.DOCMAP_SEARCH_LINES = self._checkSearchLines.isChecked() settings.DOCMAP_WIDTH = self._spinWidth.value() qsettings.setValue('preferences/editor/docmapShow', settings.SHOW_DOCMAP) qsettings.setValue('preferences/editor/docmapSlider', settings.DOCMAP_SLIDER) qsettings.setValue('preferences/editor/editorScrollBar', settings.EDITOR_SCROLLBAR) qsettings.setValue('preferences/editor/docmapCurrentLine', settings.DOCMAP_CURRENT_LINE) qsettings.setValue('preferences/editor/docmapSearchLines', settings.DOCMAP_SEARCH_LINES) qsettings.setValue('preferences/editor/docmapWidth', settings.DOCMAP_WIDTH) scheme = self._listScheme.currentItem().text() resources.CUSTOM_SCHEME = self._schemes.get(scheme, resources.COLOR_SCHEME) qsettings.setValue('preferences/editor/scheme', scheme)
class TagsPage(QWizardPage): def __init__(self, parent=None): super().__init__(parent) self.files = set() self.old_files = set() self.new_common_tags = set() self.recently_deleted_tags = set() self.button_max_width = 50 self.box_mini_width = 225 self.create_files_box() self.create_common_tags_box() self.create_recommended_tags_box() self.create_all_tags_box() self.file_info_box = FileInfoBox() self._init_ui_() def _init_ui_(self) -> None: self.setTitle("Tags") self.setButtonText(QWizard.NextButton, "Preview") layout = QHBoxLayout() layout.addWidget(self.filesBox) layout.addWidget(self.file_info_box) tags_layout = QVBoxLayout() tags_layout.addWidget(self.commonTagsBox) tags_layout.addWidget(self.recommendTagsBox) layout.addLayout(tags_layout) layout.addWidget(self.allTagsBox) self.setLayout(layout) def initializePage(self): if self.old_files == self.files: return else: self.old_files = self.files self.fileList.clear() self.tagList.clear() self.recommendTagsList.clear() self.new_common_tags.clear() self.allTagsList.clear() for file in self.files: item = QListWidgetItem(self.fileList) item.setText(file) # item.setFlags(Qt.NoItemFlags) tag_set_list: List[set] = [] for file in self.files: if Biji.get_biji_json_path(file).exists(): biji = Biji.from_file(file) tag_set_list.append(set(biji.tags)) else: tag_set_list.append(set()) if tag_set_list: self.common_tags = tag_set_list[0].intersection(*tag_set_list[1:]) all_tags = tag_set_list[0].union(*tag_set_list[1:]) else: self.common_tags = set() all_tags = set() if self.common_tags: for tag in self.common_tags: item = QListWidgetItem(self.tagList) item.setText(tag) item.setCheckState(Qt.Unchecked) item.setFlags(item.flags() | Qt.ItemIsEditable) recommend_tags = all_tags - self.common_tags if recommend_tags: for tag in recommend_tags: item = QListWidgetItem(self.recommendTagsList) item.setText(tag) recently_deleted_tags = \ self.recently_deleted_tags - recommend_tags - self.common_tags self.recommendTagsList.insertItems(0, recently_deleted_tags) for row in BijiDatabase.get_all_tags_desc(): item = QListWidgetItem(self.allTagsList) item.setText(row['tag']) self.fileList.setCurrentRow(0) def validatePage(self): self.update_new_common_tags() return True def cleanupPage(self): self.update_new_common_tags() new_added_tags = self.new_common_tags - self.common_tags self.recently_deleted_tags |= new_added_tags def update_new_common_tags(self) -> None: self.new_common_tags.clear() for i in range(self.tagList.count()): self.new_common_tags.add(self.tagList.item(i).text().strip()) def create_files_box(self) -> None: self.filesBox = QGroupBox("Files") self.filesBox.setMinimumWidth(self.box_mini_width) layout = QVBoxLayout() self.fileList = QListWidget() self.fileList.currentItemChanged.connect(self.update_file_info) layout.addWidget(self.fileList) self.filesBox.setLayout(layout) def create_common_tags_box(self) -> None: self.commonTagsBox = QGroupBox("Common Tags") self.commonTagsBox.setMinimumWidth(self.box_mini_width) layout = QVBoxLayout() buttons_layout = QHBoxLayout() all_tags_button = QPushButton("All") all_tags_button.setMaximumWidth(self.button_max_width) all_tags_button.clicked.connect(lambda: check_all(self.tagList)) delete_tags_button = QPushButton("Del") delete_tags_button.setMaximumWidth(self.button_max_width) delete_tags_button.clicked.connect(self.delete_tags) delete_tags_button.setToolTip("Delete checked items from the list") add_tags_button = QPushButton("Add") add_tags_button.setMaximumWidth(self.button_max_width) add_tags_button.clicked.connect(self.add_tags) buttons_layout.addWidget(all_tags_button) buttons_layout.addWidget(delete_tags_button) buttons_layout.addStretch() buttons_layout.addWidget(add_tags_button) self.tagList = QListWidget() layout.addLayout(buttons_layout) layout.addWidget(self.tagList) self.commonTagsBox.setLayout(layout) def create_recommended_tags_box(self) -> None: self.recommendTagsBox = QGroupBox("Recommended Tags") self.recommendTagsBox.setMinimumWidth(self.box_mini_width) layout = QVBoxLayout() self.recommendTagsList = QListWidget() layout.addWidget(self.recommendTagsList) self.recommendTagsBox.setLayout(layout) self.recommendTagsList.doubleClicked.connect(self.move_to_common_tags) def create_all_tags_box(self) -> None: self.allTagsBox = QGroupBox("All Tags") self.allTagsBox.setMinimumWidth(self.box_mini_width) layout = QVBoxLayout() self.allTagsList = QListWidget() layout.addWidget(self.allTagsList) self.allTagsBox.setLayout(layout) self.allTagsList.doubleClicked.connect(self.copy_to_common_tags) def delete_tags(self) -> None: deleted_items = delete_from_list(self.tagList) self.recently_deleted_tags |= deleted_items for item in deleted_items: if not self.recommendTagsList.findItems(item, Qt.MatchExactly): self.recommendTagsList.insertItem(0, item) def add_tags(self) -> None: tag, ok = QInputDialog.getText(self, "New tag", "New tag:") tag = tag.strip() if not ok or len(tag) == 0: return if self.tagList.findItems(tag, Qt.MatchFixedString | Qt.MatchCaseSensitive): QMessageBox.information(self, "Duplicated.", "The tag is already in the list.\n", QMessageBox.Close) return item = QListWidgetItem(self.tagList) item.setText(tag) item.setCheckState(Qt.Unchecked) item.setFlags(item.flags() | Qt.ItemIsEditable) def copy_to_common_tags(self, index: QModelIndex) -> None: if not self.tagList.findItems( index.data(), Qt.MatchFixedString | Qt.MatchCaseSensitive): item = QListWidgetItem(self.tagList) item.setText(index.data()) item.setCheckState(Qt.Unchecked) item.setFlags(item.flags() | Qt.ItemIsEditable) def move_to_common_tags(self, index: QModelIndex) -> None: self.copy_to_common_tags(index) self.recommendTagsList.takeItem(index.row()) def update_file_info(self, current: QListWidgetItem) -> None: self.file_info_box.update_file_info(current)
class StartWindow(QMainWindow): """ Main entrance of the whole process. """ def __init__(self): super().__init__() self.root_dir = os.getcwd() self.save_dir = os.getcwd() self.script_dir = "/home/silasi/ANTs/Scripts" # self.script_dir = os.getcwd() self.widget_main = QWidget() self.layout_main = QGridLayout() self.widget_main.setLayout(self.layout_main) self.allList = QListWidget() self.analyseList = QListWidget() self.btnAdd = QPushButton(">>") self.btnAdd.clicked.connect(self.add) self.buttonGroupWrite = QButtonGroup(self.widget_main) self.radio_write = QRadioButton("Write a sumarry") self.radio_show = QRadioButton("Show results visually") self.buttonGroupWrite.addButton(self.radio_write) self.buttonGroupWrite.addButton(self.radio_show) self.radio_write.setChecked(True) self.buttonGroupAutoSeg = QButtonGroup(self.widget_main) self.radio_auto = QRadioButton("Automatically detect microspheres") self.radio_man = QRadioButton(" Count microspheres manually") self.radio_auto.setChecked(True) self.labelnote = QLabel( "*make sure images are saved in the correct directory path (i.e. images/brain1/raw/)" ) self.labelnote2 = QLabel( "*Select which analysis step you wish to perform") self.labelnote3 = QLabel("*Select the root directory") self.labelRootDir = QLabel( "Select root directory containing serial brain images. (i.e. images/)" ) self.btnRootDir = QPushButton('Select') self.btnRootDir.clicked.connect(self.select_rootDir) self.lineRootDir = QLabel(self.root_dir) self.labelSaveDir = QLabel( "Select directory to save results (different from root directory)") self.btnSaveDir = QPushButton('Select') self.btnSaveDir.clicked.connect(self.select_saveDir) self.lineSaveDir = QLabel(self.save_dir) self.labelScriptDir = QLabel("Select ANTs script directory") self.btnScriptDir = QPushButton('Select') self.btnScriptDir.clicked.connect(self.select_scriptDir) self.lineScriptDir = QLabel(self.script_dir) self.btnRun = QPushButton("Analyse (Will not response for a while)") self.btnRun.clicked.connect(self.analyse) self.thickness_label = QLabel(self) self.thickness_label.setText( ' Section thickness:' ) self.thickness_combo = QComboBox(self) self.thickness_combo.addItem("25") self.thickness_combo.addItem("50") self.thickness_combo.addItem("100") self.layout_main.addWidget(self.labelnote, 0, 0) self.layout_main.addWidget(self.labelRootDir, 1, 0) self.layout_main.addWidget(self.lineRootDir, 2, 0) self.layout_main.addWidget(self.btnRootDir, 2, 1) self.layout_main.addWidget(self.labelSaveDir, 3, 0) self.layout_main.addWidget(self.lineSaveDir, 4, 0) self.layout_main.addWidget(self.btnSaveDir, 4, 1) self.layout_main.addWidget(self.labelScriptDir, 5, 0) self.layout_main.addWidget(self.lineScriptDir, 6, 0) self.layout_main.addWidget(self.btnScriptDir, 6, 1) self.layout_main.addWidget(self.labelnote2, 7, 0) self.layout_main.addWidget(self.radio_show, 8, 0) self.layout_main.addWidget(self.radio_write, 8, 1) self.layout_main.addWidget(self.radio_auto, 9, 0) self.layout_main.addWidget(self.radio_man, 9, 1) self.layout_main.addWidget(self.labelnote3, 10, 0) self.layout_main.addWidget(self.allList, 11, 0) self.layout_main.addWidget(self.btnAdd, 11, 1) self.layout_main.addWidget(self.analyseList, 11, 2) self.layout_main.addWidget(self.thickness_label, 12, 0) self.layout_main.addWidget(self.thickness_combo, 12, 1) self.layout_main.addWidget(self.btnRun, 12, 2) self.setCentralWidget(self.widget_main) self.update_list() self.show() def update_list(self): for item in os.listdir(self.root_dir): self.allList.addItem(os.path.join(self.root_dir, item)) def add(self): for item in self.allList.selectedItems(): if len(self.analyseList.findItems(item.text(), Qt.MatchExactly)) == 0: self.analyseList.addItem(item.text()) def choosePath(self): root = Tk() root.withdraw() result = askdirectory( initialdir="/", title="Select root directory containing all cage folders") return result def select_rootDir(self): self.root_dir = self.choosePath() if type(self.root_dir) is str: self.lineRootDir.setText(self.root_dir) self.allList.clear() self.analyseList.clear() self.update_list() def select_saveDir(self): self.save_dir = self.choosePath() if type(self.save_dir) is str: self.lineSaveDir.setText(self.save_dir) def select_scriptDir(self): self.script_dir = self.choosePath() if type(self.script_dir) is str: self.lineScriptDir.setText(self.script_dir) def analyse(self): self.thickness = int(str(self.thickness_combo.currentText())) d, okPressed = QInputDialog.getDouble( self, "How strict you would like to calculate the edge?", "Value(between 0 and 1):", 0., 0., 1., 2) if okPressed: strict = float(d) else: strict = 0. for brain_dir in [ str(self.analyseList.item(i).text()) for i in range(self.analyseList.count()) ]: run_one_brain(brain_dir, self.save_dir, True, True, self.script_dir, True, self.radio_write.isChecked(), self.radio_show.isChecked(), False, False, self.radio_auto.isChecked(), section_thickness=self.thickness, strict=strict)
class MyMainWindow(QMainWindow): def __init__(self): super(MyMainWindow, self).__init__() self.setWindowTitle('pyrodump') self.setMinimumSize(800, 640) self.setup_menubar() self.vlayout = QVBoxLayout() self.interface = None # type: str self.btn_interface = QPushButton('No interface') self.btn_interface.pressed.connect(self.on_select_interface) self.hlayout1 = QHBoxLayout() self.vlayout.addLayout(self.hlayout1) self.hlayout1.addWidget(self.btn_interface) self.label_interface_type = QLabel("Type: unknown") self.hlayout1.addWidget(self.label_interface_type) self.vlayout.addWidget(QHLine()) hlayout2 = QHBoxLayout() self.vlayout.addLayout(hlayout2) vlayout2 = QVBoxLayout() hlayout2.addLayout(vlayout2) vlayout2.addWidget(QLabel("Access Points")) self.ap_list = QListWidget() # type: QListWidget self.ap_list.itemSelectionChanged.connect( self.ap_list_item_selection_changed) vlayout2.addWidget(self.ap_list) vlayout3 = QVBoxLayout() hlayout2.addLayout(vlayout3) vlayout3.addWidget(QLabel("Clients on selected AP")) self.client_list = QListWidget() self.client_list.itemSelectionChanged.connect( self.client_list_item_selection_changed) vlayout3.addWidget(self.client_list) self.label_ap_details = QLabel() self.display_ap_details() self.vlayout.addWidget(self.label_ap_details) window = QWidget() window.setLayout(self.vlayout) self.setCentralWidget(window) self.proc_start_monitor_mode = None # type: QProcess self.proc_stop_monitor_mode = None # type: QProcess self.proc_airodump = None # type: QProcess self.proc_deauth = None # type: QProcess self.proc_deauth_args = [] # save arguments for auto respawn self.proc_deauth2 = None # type: QProcess self.airodump_analyzer = None # type: AirodumpAnalyzer self.timer = None # type: QTimer self.tempdir = tempfile.TemporaryDirectory() self.auto_select_interface() def display_ap_details(self, essid="(None)", bssid="(None}", channel="(None)", power="(None)", num_beacons="(None)"): self.label_ap_details.setText( "ESSID: {}, BSSID: {}\nChannel: {}, Power: {}, # Beacons: {}". format(essid, bssid, channel, power, num_beacons)) def set_interface(self, iface): self.interface = iface if iface is None: self.btn_interface.setText("None") else: self.btn_interface.setText(iface) iface_type = get_wifi_interface_type(iface) if iface_type: self.label_interface_type.setText( "Type: {}".format(iface_type)) else: self.label_interface_type.setText("Type: unknown") def auto_select_interface(self): ifaces = get_wifi_interfaces() if not ifaces: self.set_interface(None) return # try to find one with monitor mode enabled for iface in ifaces: if get_wifi_interface_type(iface) == "monitor": self.set_interface(iface) # fallback self.set_interface(ifaces[0]) def on_select_interface(self): ifaces = get_wifi_interfaces() if not ifaces: QErrorDialog("No WiFi interfaces found.").exec_() return iface, ok = QInputDialog.getItem(self, "Select WiFi interface", "Select WiFi interface", ifaces, 0, False) self.set_interface(iface) def check_interface_ready(self): if get_wifi_interface_type(self.interface) != "monitor": QErrorDialog( "A monitor mode interface must be started or selected first." ).exec_() return False return True def start_airodump(self): print("Starting airodump...") if self.proc_airodump: self.stop_airodump() if not self.check_interface_ready(): return cmd = "airodump-ng" cmd_args = [ self.interface, "-w", os.path.join(self.tempdir.name, "airodump"), "--write-interval", "1", "-o", "csv", "--band", "ag" ] self.proc_airodump = QProcess() self.proc_airodump.start(cmd, cmd_args) self.monitor_mode_menu.setDisabled(True) self.timer = QTimer() self.timer.timeout.connect(self.update_airodump_display) self.timer.start(1000) def stop_airodump(self): print("Stopping airodump...") if self.timer: self.timer.stop() if self.airodump_analyzer: self.airodump_analyzer.running = False if self.proc_airodump: self.proc_airodump.kill() self.proc_airodump = None self.monitor_mode_menu.setDisabled(False) def start_deauth_client(self): if self.proc_airodump or not self.client_list.selectedItems(): QErrorDialog("How did this happen?").exec_() return if not self.check_interface_ready(): return m = re.compile("^(.*) \(").search( self.client_list.selectedItems()[0].text()) if not m: QErrorDialog("Could not parse client MAC").exec_() return client_mac = m.group(1) ap_bssid = None ap_channel = None for bssid, ap_data in self.airodump_analyzer.access_points.items(): if client_mac in ap_data["clients"]: ap_bssid = bssid ap_channel = ap_data["channel"] break if not ap_bssid: QErrorDialog( "Could not find AP BSSID of client with MAC {}".format( client_mac)).exec_() return print("-- Trying deauth of client with MAC {} from BSSID {}...".format( client_mac, ap_bssid)) self.monitor_mode_menu.setDisabled(True) self.airodump_menu.setDisabled(True) # start airodump-ng on correct channel self.proc_deauth2 = QProcess() self.proc_deauth2.start( "airodump-ng", [self.interface, "--band", "ag", "-c", ap_channel]) self.proc_deauth = QProcess() # hack with stdbuf so that stdout/stderr is not buffered self.proc_deauth_args = [ "-i0", "-o0", "-e0", "aireplay-ng", "--deauth=0", "-c", client_mac, "-a", ap_bssid, self.interface ] self.proc_deauth.finished.connect(self.deauth_proc_finished) self.proc_deauth.readyReadStandardOutput.connect( self.deauth_proc_print_output) self.proc_deauth.setProcessChannelMode(QProcess.MergedChannels) self.proc_deauth.start("stdbuf", self.proc_deauth_args, QProcess.Unbuffered | QProcess.ReadWrite) def deauth_proc_print_output(self): if self.proc_deauth: print(self.proc_deauth.readAll().data().decode("utf-8"), flush=True) def deauth_proc_finished(self): if self.proc_deauth: print("-- aireplay-ng finished by itself -> restart") self.proc_deauth.start("stdbuf", self.proc_deauth_args, QProcess.Unbuffered | QProcess.ReadWrite) def stop_deauth_client(self): print("Stopping deauth...") self.proc_deauth2 = None self.proc_deauth = None self.monitor_mode_menu.setDisabled(False) self.airodump_menu.setDisabled(False) def update_airodump_display(self): if not self.airodump_analyzer or not self.airodump_analyzer.running: if not self.proc_airodump: print( "! Airodump analyzer not running. Cannot update display.") return else: # try to create analyzer object match_files = self.tempdir.name + "/airodump-*.csv" candidate_files = glob.glob(match_files) if not candidate_files: QErrorDialog( "Could not find airodump output at location \"{}\". Stopping Airodump." .format(match_files)).exec_() # TODO only stop on 3rd try self.stop_airodump() return file = sorted(candidate_files)[-1] # select most recent one self.airodump_analyzer = AirodumpAnalyzer(file) self.airodump_analyzer.update() # remember selection - kind of ugly if self.ap_list.selectedItems(): selected_item = self.ap_list.selectedItems()[0].text() else: selected_item = None self.ap_list.clear() last_channel = None for ap, ap_data in sorted(self.airodump_analyzer.access_points.items(), key=lambda kv: int(kv[1]["channel"])): if ap_data["channel"] != last_channel: last_channel = ap_data["channel"] self.ap_list.addItem("-- Channel {} --".format(last_channel)) self.ap_list.addItem("{} (BSSID: {})".format( ap_data["essid"], ap_data["bssid"])) # for ap, ap_data in self.airodump_analyzer.access_points.items(): # self.ap_list.addItem("{} (BSSID: {})".format(ap_data["essid"], ap_data["bssid"])) if selected_item: newItemsMatching = self.ap_list.findItems(selected_item, QtCore.Qt.MatchExactly) if newItemsMatching: newItemsMatching[0].setSelected(True) self.ap_list.setCurrentItem(newItemsMatching[0]) def ap_list_item_selection_changed(self): self.display_ap_details() if not self.ap_list.selectedItems(): # unselected item self.client_list.clear() return selected_bssid_match = re.compile("\(BSSID: (.*)\)$").search( self.ap_list.selectedItems()[0].text()) if not selected_bssid_match: self.client_list.clear() return selected_bssid = selected_bssid_match.group(1) if not selected_bssid in self.airodump_analyzer.access_points: self.client_list.clear() return ap = self.airodump_analyzer.access_points[selected_bssid] # shorthand self.display_ap_details(essid=ap["essid"], bssid=ap["bssid"], channel=ap["channel"], power=ap["power"], num_beacons=ap["num_beacons"]) self.client_list.clear() for client_mac, client_data in ap["clients"].items(): s = "{} (packets: {}, power: {})".format( client_data["mac"], client_data["num_packets"], client_data["power"]) self.client_list.addItem(s) def client_list_item_selection_changed(self): self.client_menu.setDisabled(True) if self.client_list.selectedItems() and not self.proc_airodump: self.client_menu.setDisabled(False) def start_monitor_mode(self): if self.interface is None: QErrorDialog( "No interface selected.\nPlease select one first.").exec_() return print("Trying to start monitor mode on interface {}...".format( self.interface)) cmd = "airmon-ng" cmd_args = ["start", self.interface] self.proc_start_monitor_mode = QProcess(self) self.proc_start_monitor_mode.finished.connect( self.on_start_monitor_mode_cb) self.proc_start_monitor_mode.start(cmd, cmd_args) self.menubar.setDisabled(True) def stop_monitor_mode(self): if self.interface is None: QErrorDialog( "No interface selected.\nPlease select one first.").exec_() return print("Trying to stop monitor mode on interface {}...".format( self.interface)) cmd = "airmon-ng" cmd_args = ["stop", self.interface] self.proc_stop_monitor_mode = QProcess(self) self.proc_stop_monitor_mode.finished.connect( self.on_stop_monitor_mode_cb) self.proc_stop_monitor_mode.start(cmd, cmd_args) self.menubar.setDisabled(True) def on_start_monitor_mode_cb(self, exitCode, exitStatus): self.menubar.setDisabled(False) stdout = self.proc_start_monitor_mode.readAllStandardOutput().data( ).decode("utf-8") self.proc_start_monitor_mode = None expr1 = \ re.compile("mac80211 monitor mode [a-z]* enabled for \[phy[0-9]*\][a-z0-9]* on \[phy[0-9]*\]([a-z0-9]*)") match = expr1.search(stdout) if not match: QErrorDialog( "Not sure if monitor mode interface was successfully created. Please select manually." ).exec_() self.auto_select_interface() return iface = match.group(1) print("Enabled monitor mode for interface {}".format(iface)) self.set_interface(iface) def on_stop_monitor_mode_cb(self, exitCode, exitStatus): self.menubar.setDisabled(False) stdout = self.proc_stop_monitor_mode.readAllStandardOutput().data( ).decode("utf-8") self.proc_stop_monitor_mode = None self.auto_select_interface() def setup_menubar(self): self.menubar = self.menuBar() self.monitor_mode_menu = self.menubar.addMenu("Monitor mode") self.monitor_mode_menu.addAction("Start").triggered.connect( self.start_monitor_mode) self.monitor_mode_menu.addAction("Stop").triggered.connect( self.stop_monitor_mode) self.airodump_menu = self.menubar.addMenu("Airodump") self.airodump_menu.addAction("Start").triggered.connect( self.start_airodump) self.airodump_menu.addAction("Stop").triggered.connect( self.stop_airodump) self.client_menu = self.menubar.addMenu("Client") self.client_menu.setDisabled(True) self.client_menu.addAction("Start deauth").triggered.connect( self.start_deauth_client) self.client_menu.addAction("Stop deauth").triggered.connect( self.stop_deauth_client)
class VectorDatabaseFrame(GenericFrame): def __init__(self, parent): super().__init__(QHBoxLayout(), 'Vector Database', parent) self.vectors = None self.vector_info = VectorInformationFrame() self.list = QListWidget() self.dialog = None self.buttons = QToolBar('Buttons') self.init_list() self.init_frame() self.list.itemClicked.connect(self.unsaved_dialog) self.vector_info.save_changes.clicked.connect( lambda: self.list.currentItem().setText( self.vector_info.currentVector.data.get("Name"))) def init_frame(self): self.setFrameShape(QFrame.StyledPanel) self.init_buttons() self.layout.addWidget(self.list) self.layout.addWidget(self.vector_info) self.layout.addWidget(self.buttons) self.setMinimumSize(600, 400) def init_buttons(self): self.buttons.setOrientation(Qt.Vertical) self.buttons.setMovable(False) self.buttons.setStyleSheet(""" QToolBar { spacing: 6px; padding: 3px; } """) # Buttons after this are set to the right side spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.buttons.addWidget(spacer) b1 = QPushButton('Add Vector') self.buttons.addWidget(b1) b1.clicked.connect(self.add_vector) b2 = QPushButton('Delete Vector') self.buttons.addWidget(b2) b2.clicked.connect(self.delete_vector_dialog) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.buttons.addWidget(spacer) b3 = QPushButton('Close') self.buttons.addWidget(b3) b3.clicked.connect(self.check_status) def init_list(self): self.vectors = get_vector_list() if self.vectors: for vector in self.vectors: self.list.addItem(vector.get("Name")) self.list.setCurrentItem(self.list.itemAt(0, 0)) self.vector_info.update_frame(self.list.currentItem()) def add_vector(self): v = Vector() self.vectors.append(v.data) self.list.addItem(self.vectors[-1].get("Name")) self.list.setCurrentItem(self.list.item(self.list.count() - 1)) self.vector_info.update_frame(self.list.currentItem()) def delete_vector_dialog(self): self.dialog = GenericWindow(QGridLayout()) dialog_delete = QPushButton("Delete") dialog_cancel = QPushButton("Cancel") self.dialog.layout.addWidget(QLabel("Delete current vector?"), 0, 1) self.dialog.layout.addWidget(dialog_delete, 1, 0) self.dialog.layout.addWidget(dialog_cancel, 1, 2) dialog_delete.clicked.connect(self.dialog_confirm_delete) dialog_cancel.clicked.connect(self.dialog_cancel) self.dialog.show() def unsaved_dialog(self, vector, closeParent=None): if self.vector_info.save_changes.isEnabled(): self.dialog = GenericWindow(QGridLayout(), self) dialog_confirm = QPushButton("Continue") dialog_cancel = QPushButton("Cancel") self.dialog.layout.addWidget( QLabel( "Changes to current vector are pending.\nContinue without saving?" ), 0, 1) self.dialog.layout.addWidget(dialog_confirm, 1, 0) self.dialog.layout.addWidget(dialog_cancel, 1, 2) if closeParent is not None: dialog_confirm.clicked.connect(self.dialog_confirm_exit) else: dialog_confirm.clicked.connect(self.dialog_confirm_change) dialog_cancel.clicked.connect(self.dialog_cancel) self.dialog.show() else: self.vector_info.update_frame(vector) def dialog_confirm_exit(self): self.dialog.close() self.parentWidget().close() def dialog_confirm_change(self): self.vector_info.update_frame(self.list.currentItem()) self.dialog.close() def dialog_confirm_delete(self): del_object("Name", self.list.takeItem(self.list.currentRow()).text(), "Vector") if self.list.count() > 0: self.list.setCurrentItem(self.list.itemAt(0, 0)) self.vector_info.update_frame(self.list.currentItem()) else: self.vector_info.reset_frame() self.dialog.close() def dialog_cancel(self): self.list.setCurrentItem( self.list.findItems( self.vector_info.currentVector.data.get("Name"), Qt.MatchExactly)[0]) self.dialog.close() def check_status(self): if not self.vector_info.save_changes.isEnabled(): self.parentWidget().close() else: self.unsaved_dialog(self.vector_info.currentVector, True)
class ActorWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) mainLayout = QHBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) self._listWidget = QListWidget() self._listWidget.itemDoubleClicked.connect(self.onActorNameDblClicked) mainLayout.addWidget(self._listWidget) right = QVBoxLayout() right.setAlignment(Qt.AlignTop) self._newItem = QLineEdit() self._add = QPushButton('Add') self._add.clicked.connect(self.onAddClicked) self._remove = QPushButton('Remove') self._remove.clicked.connect(self.onRemoveClicked) right.addWidget(self._newItem) right.addWidget(self._add) right.addWidget(self._remove) mainLayout.addLayout(right) self.setLayout(mainLayout) def onActorNameDblClicked(self, item): self._newItem.setText(item.text()) def onAddClicked(self): text = self._newItem.text().strip() if not len(text): return items = self._listWidget.findItems(text, Qt.MatchExactly) if (len(items) > 0): print('already exists actor name!') return self._listWidget.addItem(text) self._newItem.clear() QApplication.postEvent( self, QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier)) def onRemoveClicked(self): if not self._listWidget.currentItem(): return item = self._listWidget.currentItem() #print(item.text()) self._listWidget.takeItem(self._listWidget.row(item)) QApplication.postEvent( self, QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier)) def getValue(self): vals = [] for i in range(self._listWidget.count()): vals.append(self._listWidget.item(i).text()) return ';'.join(vals) def setValue(self, value): self._listWidget.clear() if not value: return self._listWidget.insertItems(0, value.split(';')) Value = pyqtProperty(str, getValue, setValue)
class DockElementPlotter(DockElement): selected = None registerKeySignal = pyqtSignal(str, str, str) __settings = {} def __init__(self, mainWindow, title): DockElement.__init__(self, mainWindow, 'Plotter') self.name = title self.setWindowTitle(title) self.setupUi() def setupUi(self): self.resize(500, 300) self.settingsWidget = QWidget() self.settingsWidget.setMinimumSize(900, 150) # Create the main widget as a container widget. self.mainWidget = QWidget(self) self.setWidget(self.mainWidget) mainLayout = QVBoxLayout(self.mainWidget) # Create 'Settings' dropdown button. self.settingsButton = QToolButton(self) self.settingsButton.setText("Settings") # Remove the menu indicator by applying a global style. self.settingsButton.setObjectName("noIndicator") # And replace it with a down arrow icon instead (which looks better). self.settingsButton.setArrowType(QtCore.Qt.DownArrow) self.settingsButton.setToolButtonStyle( QtCore.Qt.ToolButtonTextBesideIcon) # Set the popup that opens on button click. self.settingsButton.setPopupMode(QToolButton.InstantPopup) action = QWidgetAction(self.settingsButton) action.setDefaultWidget(self.settingsWidget) self.settingsButton.addAction(action) # Create a horizontal layout for the settings button. buttonLayout = QHBoxLayout() buttonLayout.addWidget(self.settingsButton) buttonLayout.addStretch() mainLayout.addLayout(buttonLayout) # Create the plotter widget. self.plotter = Qt5Plotter() self.plotter.setPlotterGUI(self) self.plotter.setMinimumSize(300, 200) self.plotter.plotLinear() mainLayout.addWidget(self.plotter) # Create the settings dialog. self.setupSettingsDialogUi() # Set keys that are plotted by default self.defaultKeys = {"TEST": ["loss"], "TRAIN": ["loss"]} def setupSettingsDialogUi(self): self.settingsDialogLayout = QGridLayout() # DropDown for plotAgainstTime or Iterations self.dbTimeIter = QComboBox() self.dbTimeIter.addItem("Iterations") self.dbTimeIter.addItem("Time") self.dbTimeIter.setFixedWidth(130) self.dbTimeIter.currentIndexChanged.connect(self.plotAgainstTimeIter) self.settingsDialogLayout.addWidget(self.dbTimeIter, 1, 3) # DropDown for plotting scale linear or logarithmic self.dbLinLoga = QComboBox() self.dbLinLoga.addItem("Linear") self.dbLinLoga.addItem("Logarithmic") self.dbLinLoga.setFixedWidth(130) self.dbLinLoga.currentIndexChanged.connect(self.plotLinLoga) self.settingsDialogLayout.addWidget(self.dbLinLoga, 3, 3) # Dynamic checkboxes # Key lists from parser that define the table rows for train and test self.test_dict_list = [] self.train_dict_list = [] # Lists for checkboxes for train and test self.test_cb = [] self.train_cb = [] # Text labels for better display testLabel = QLabel("Test keys:") trainLabel = QLabel("Train keys:") timeIterLabel = QLabel("Plot against:") LinLogaLabel = QLabel("Plot scale:") self.settingsDialogLayout.addWidget(testLabel, 0, 4) self.settingsDialogLayout.addWidget(trainLabel, 2, 4) self.settingsDialogLayout.addWidget(timeIterLabel, 0, 3) self.settingsDialogLayout.addWidget(LinLogaLabel, 2, 3) # create default Checkboxes self.createCheckboxes(self.test_dict_list, self.train_dict_list) # Button select Logfiles self.btn1 = QPushButton( QtGui.QIcon(QApplication.style().standardIcon( QStyle.SP_DialogOpenButton)), "") self.btn1.setFixedWidth(40) self.btn1.clicked.connect(self.getFiles) self.settingsDialogLayout.addWidget(self.btn1, 3, 0) # Button remove item self.btn2 = QPushButton( QtGui.QIcon(QApplication.style().standardIcon( QStyle.SP_TrashIcon)), "") self.btn2.setFixedWidth(40) self.btn2.clicked.connect(self.removeItem) self.settingsDialogLayout.addWidget(self.btn2, 3, 1) # Button CSV export self.btn3 = QPushButton("Export as CSV") self.btn3.setFixedWidth(110) self.btn3.clicked.connect(self.exportCSV) self.settingsDialogLayout.addWidget(self.btn3, 3, 2) # Create Logfile list self.leftlist = QListWidget() self.leftlist.setFixedSize(200, 100) self.Stack = QStackedWidget(self) self.settingsDialogLayout.addWidget(self.Stack, 0, 0, 3, 3) self.settingsDialogLayout.addWidget(self.leftlist, 0, 0, 3, 3) self.leftlist.currentRowChanged.connect(self.display) self.leftlist.currentItemChanged.connect(self.selectedLogItem) self.settingsWidget.setLayout(self.settingsDialogLayout) # Signal handling self.registerKeySignal.connect(self.registerKey) def btnstatetest(self, checkbox): """ Plot the selected checkbox for Test-Data """ if self.selected: self.plotter.showMetricTest(self.selected, checkbox.text(), checkbox.isChecked()) self.displayPlot() self.__settings["checkBoxes"][self.selected]["TEST"][ checkbox.text()] = checkbox.isChecked() def btnstatetrain(self, checkbox): """ Plot the selected checkbox for Train-Data """ if self.selected: self.plotter.showMetricTrain(self.selected, checkbox.text(), checkbox.isChecked()) self.displayPlot() self.__settings["checkBoxes"][self.selected]["TRAIN"][ checkbox.text()] = checkbox.isChecked() def displayPlot(self): """ Update the plotter. """ if self.selected: self.plotter.plot() self.plotter.show() self.createNecessaryDocks() def createNecessaryDocks(self): """ Create the necessary docks if they don't exist. """ if self.__settings is None: self.__settings = {} if "checkBoxes" not in self.__settings: self.__settings["checkBoxes"] = {} if self.selected not in self.__settings["checkBoxes"] or \ isinstance(self.__settings["checkBoxes"][self.selected], list): self.__settings["checkBoxes"][self.selected] = { "TEST": {}, "TRAIN": {} } def existsKeySelection(self): """ Returns true if there is a selection by the user of keys to be plotted """ if self.__settings is None: self.__settings = {} if "checkBoxes" not in self.__settings: self.__settings["checkBoxes"] = {} if self.selected not in self.__settings["checkBoxes"]: return False return True def createCheckboxes(self, test_list, train_list): """ Dynamically creates checkboxes, which are in the lists: test_list and train_list """ self.test_dict_list = test_list self.train_dict_list = train_list # As long as there are elements in TEST list: create Checkboxes if self.test_dict_list is not None: for i in range(len(self.test_dict_list)): self.test_cb.append(QCheckBox(self.test_dict_list[i], self)) self.settingsDialogLayout.addWidget(self.test_cb[i], 1, 4 + i) self.test_cb[i].stateChanged.connect( lambda checked, i=i: self.btnstatetest(self.test_cb[i])) if self.existsKeySelection() and self.test_cb[i].text() in \ self.__settings["checkBoxes"][self.selected]["TEST"]: # Compatibility for older projects try: self.test_cb[i].setChecked( self.__settings["checkBoxes"][self.selected] ["TEST"].get(self.test_cb[i].text(), True)) except AttributeError: self.test_cb[i].setChecked(True) else: self.test_cb[i].setChecked( self.test_cb[i].text() in self.defaultKeys["TEST"]) self.btnstatetest(self.test_cb[i]) # As long as there are elements in TRAIN list: create Checkboxes if self.train_dict_list is not None: for i in range(len(self.train_dict_list)): self.train_cb.append(QCheckBox(self.train_dict_list[i], self)) self.settingsDialogLayout.addWidget(self.train_cb[i], 3, 4 + i) self.train_cb[i].stateChanged.connect( lambda checked, i=i: self.btnstatetrain(self.train_cb[i])) if self.existsKeySelection() and self.train_cb[i].text() in \ self.__settings["checkBoxes"][self.selected]["TRAIN"]: # Compatibility for older projects try: self.train_cb[i].setChecked( self.__settings["checkBoxes"][self.selected] ["TRAIN"].get(self.train_cb[i].text(), True)) except AttributeError: self.train_cb[i].setChecked(True) else: self.train_cb[i].setChecked( self.train_cb[i].text() in self.defaultKeys["TRAIN"]) self.btnstatetrain(self.train_cb[i]) self.settingsWidget.setLayout(self.settingsDialogLayout) def display(self, i): self.Stack.setCurrentIndex(i) def loadFiles(self, filenames): """ Loads every log file in the list of filenames and adds them to the list of logs. """ for i in range(len(filenames)): f = filenames[i] if not os.path.exists(f): Log.error("External log not found: " + f, Log.getCallerId("plotter")) else: parser = Parser(open(f, 'r'), OrderedDict(), Log.getCallerId(str(f))) head, tail = os.path.split(str(f)) logId = "external_" + tail logName = "[ext] " + tail self.putLog(logId, parser, logName=logName) parser.parseLog() # This is for saving the loaded logs in the project file # (create the necessary docks if they don't exist) if self.__settings is None: self.__settings = {"logFiles": {logId: f}} elif "logFiles" not in self.__settings: self.__settings["logFiles"] = {logId: f} else: self.__settings["logFiles"][logId] = f def getFiles(self): """ Dialog for adding different data files to list for plotting """ dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFiles) if dlg.exec_(): self.loadFiles(dlg.selectedFiles()) self.selectLast() def plotAgainstTimeIter(self): if self.dbTimeIter.currentText() == "Time": self.plotter.plotAgainstTime() self.__settings["againstTime"] = "Time" self.plotter.plot() else: if self.dbTimeIter.currentText() == "Iterations": self.plotter.plotAgainstIterations() self.__settings["againstTime"] = "Iterations" self.plotter.plot() def plotLinLoga(self): if self.dbLinLoga.currentText() == "Linear": self.plotter.plotLinear() self.__settings["logarithmic"] = "Linear" self.plotter.plot() else: if self.dbLinLoga.currentText() == "Logarithmic": self.plotter.plotLogarithmic() self.__settings["logarithmic"] = "Logarithmic" self.plotter.plot() def putLog(self, logId, parser, plotOnUpdate=False, logName=None): """ Add a log (i.e. parser) to the list of shown logs. If a log with the log name already exists primes are added to the name until it is unique. """ for i in range(self.leftlist.count()): if logId == self.leftlist.item(i).data(Qt.UserRole): return if not logName: logName = logId self.test_dict_list = [] self.train_dict_list = [] while self.leftlist.findItems(logName, Qt.MatchExactly): logName += "'" self.plotter.putLog(logId, logName, parser, plotOnUpdate) logItem = QListWidgetItem(logName) v = QVariant(logId) logItem.setData(Qt.UserRole, v) self.leftlist.insertItem(self.leftlist.count(), logItem) self.selectLast() def removeLog(self, logId, logName): try: self.__settings["checkBoxes"].pop(logId) except KeyError: pass self.plotter.removeLog(logId) item = self.leftlist.findItems(logName, Qt.MatchExactly) if item: row = self.leftlist.row(item[0]) self.leftlist.takeItem(row) self.test_dict_list = [] self.train_dict_list = [] self.selectLast() self.plotter.plot() self.plotter.show() def registerKey(self, logId, phase, key): """ Registers the key given by the signal. This is only of interest if the key belongs to the selected log file. A new checkbox is then created for that key. """ if self.leftlist.currentItem(): if str(self.leftlist.currentItem().data(Qt.UserRole)) == logId: if phase == "TEST": self.test_dict_list.append(key) if phase == "TRAIN": self.train_dict_list.append(key) self.updateCheckboxes(self.test_dict_list, self.train_dict_list) def removeItem(self): """ Removes the selected item from the list and also from plot. """ item = self.leftlist.takeItem(self.leftlist.currentRow()) if item is not None: self.removeLog(item.data(Qt.UserRole), item.text()) self.displayPlot() self.selectLast() if (self.__settings is not None and "logFiles" in self.__settings and str(item.data( Qt.UserRole)) in self.__settings["logFiles"]): del self.__settings["logFiles"][str(item.data(Qt.UserRole))] def selectLast(self): if len(self.leftlist) > 0: self.leftlist.setCurrentItem( self.leftlist.item(len(self.leftlist) - 1)) self.selectedLogItem(self.leftlist.item(len(self.leftlist) - 1)) else: self.test_dict_list = [] self.train_dict_list = [] self.updateCheckboxes(self.test_dict_list, self.train_dict_list) def selectedLogItem(self, item, update=True): """ Memorize which logfile is selected, block signals, and asks plotter if the checkbox is selected or not. """ if item: if update: self.updateCheckboxes( list( self.plotter.getParser(str(item.data( Qt.UserRole))).getKeys('TEST')), list( self.plotter.getParser(str(item.data( Qt.UserRole))).getKeys('TRAIN'))) self.selected = str(str(item.data(Qt.UserRole))) self.tickBoxes() self.__settings["selectedIndex"] = self.leftlist.row(item) def updateCheckboxes(self, test_list, train_list): """ Clear all checkboxes and clear the layer and create new checkboxes and add them to the layer again """ for i in range(len(self.test_cb)): self.settingsDialogLayout.removeWidget(self.test_cb[i]) self.test_cb[i].deleteLater() self.test_cb[i] = None for i in range(len(self.train_cb)): self.settingsDialogLayout.removeWidget(self.train_cb[i]) self.train_cb[i].deleteLater() self.train_cb[i] = None self.test_cb = [] self.train_cb = [] # create new Checkboxes with new Keys self.createCheckboxes(test_list, train_list) self.tickBoxes() def tickBoxes(self): """ Whether a plot is shown is stored in the plotter object. This method ticks the checkboxes of the keys whose plot is shown in the plotter. """ for i in range(len(self.test_cb)): self.test_cb[i].blockSignals(True) self.test_cb[i].setChecked( self.plotter.isMetricTestShown(self.selected, self.test_cb[i].text())) self.test_cb[i].blockSignals(False) for i in range(len(self.train_cb)): self.train_cb[i].blockSignals(True) self.train_cb[i].setChecked( self.plotter.isMetricTrainShown(self.selected, self.train_cb[i].text())) self.train_cb[i].blockSignals(False) def setProject(self, project): """ This sets the project object. The plotter settings have the following structure: "plotter": { "checkBoxes": { "logName1": { "TEST": { "accuracy": true, "loss": false }, "TRAIN": { "LearningRate": true, "loss": false } }, "logName2": { ... }, ... }, "selectedIndex": 0, "againstTime": "Iterations", "logarithmic": "Logarithmic" } """ if "plotter" not in project.getSettings(): project.getSettings()["plotter"] = self.__settings else: self.__settings = project.getSettings()["plotter"] selectedIndex = -1 if "selectedIndex" in self.__settings: selectedIndex = self.__settings["selectedIndex"] if "logFiles" in self.__settings: self.loadFiles(list(self.__settings["logFiles"].values())) if "checkBoxes" in self.__settings: for name in self.__settings["checkBoxes"]: for key in self.__settings["checkBoxes"][name]["TEST"]: # Compatibility for older projects try: self.plotter.showMetricTest( name, key, self.__settings["checkBoxes"][name] ["TEST"].get(key)) except AttributeError: self.plotter.showMetricTest(name, key, True) for key in self.__settings["checkBoxes"][name]["TRAIN"]: # Compatibility for older projects try: self.plotter.showMetricTrain( name, key, self.__settings["checkBoxes"][name] ["TRAIN"].get(key)) except AttributeError: self.plotter.showMetricTrain(name, key, True) if "againstTime" in self.__settings: index = self.dbTimeIter.findText(self.__settings["againstTime"], Qt.MatchFixedString) if index >= 0: self.dbTimeIter.setCurrentIndex(index) if "logarithmic" in self.__settings: index = self.dbLinLoga.findText(self.__settings["logarithmic"], Qt.MatchFixedString) if index >= 0: self.dbLinLoga.setCurrentIndex(index) #for sid in project.getSessions(): # session = project.getSession(sid) # self.putLog(str(session.getLogId()), session.getParser(), True, str(session.getLogFileName(True))) self.plotter.plot() if selectedIndex >= 0: self.leftlist.setCurrentItem(self.leftlist.item(selectedIndex)) self.selectedLogItem(self.leftlist.item(selectedIndex), False) else: self.selectLast() # Delete plot if session is deleted project.deleteSession.connect(lambda sid: self.removeLog( str(project.getSession(sid).getLogId()), str(project.getSession(sid).getLogFileName(True)))) project.deleteSession.connect( lambda sid: project.deletePlotterSettings( str(project.getSession(sid).getLogId()))) project.resetSession.connect(lambda sid: self.removeLog( str(project.getSession(sid).getLogId()), str(project.getSession(sid).getLogFileName(True)))) project.resetSession.connect(lambda sid: project.deletePlotterSettings( str(project.getSession(sid).getLogId()))) def exportCSV(self): """ Opens a file dialog and the plotter saves the shown plots to the selected file. The file dialog only appears if there is at least one plot. """ if self.plotter.numGraphs() == 0: QMessageBox.question(self, 'PyQt5 message', "Nothing selected!", QMessageBox.Ok, QMessageBox.Ok) else: path = str( QFileDialog.getSaveFileName( filter="CSV table (*.csv) ;; Text file (*.txt)")[0]) if path: if not (path.endswith(".csv") or path.endswith(".txt")): path = path + ".csv" self.plotter.exportCSVToFile(path)
class BotManagerView(QWidget): def __init__(self, parent, botlist): super(QWidget, self).__init__(parent) self.innerLayout = QVBoxLayout() self.groupBox = QGroupBox('Saved Bots') self.layout = QVBoxLayout(self) self.botListView = QListWidget(self) self.botListView.setWindowTitle('Your saved bots') self.botListView.itemSelectionChanged.connect(self.loadBot) self.botListView.itemChanged.connect(self.getSelectedBots) self.removeButton = QPushButton('Remove') self.removeButton.setEnabled(False) self.compareButton = QPushButton('Select 2 Bots to Compare') self.compareButton.setEnabled(False) self.botManager = BotManager(self) self.parent = parent self.botList = {} self.compareList = dict() self._setup(botlist) self.error_msg = QLabel() self.error_msg.setWordWrap(True) self.error_msg.setAlignment(QtCore.Qt.AlignLeft) self.innerLayout.addWidget(self.botListView) self.innerLayout.addWidget(self.removeButton) self.innerLayout.addWidget(self.compareButton) self.innerLayout.addWidget(self.error_msg) self.groupBox.setLayout(self.innerLayout) self.layout.addWidget(self.groupBox) self.setLayout(self.layout) self.windows = [] def _setup(self, botlist): for b in botlist: self.botList[b] = b item = QListWidgetItem(b) item.setCheckState(False) item.setText(b) self.botListView.addItem(item) self.removeButton.clicked.connect(self.removeButtonPressed) self.compareButton.clicked.connect(self.compareButtonPressed) def refresh(self): self.botListView.clear() self.compareList.clear() self.compareButton.setText('Select 2 Bots to Compare') self.compareButton.setEnabled(False) self.removeButton.setEnabled(False) for b in self.botList: item = QListWidgetItem(b) item.setCheckState(False) item.setText(b) self.botListView.addItem(item) def loadBot(self): if self.botListView.currentItem() is None: return alias = self.botListView.currentItem().text() self.parent.loadSavedBot(alias) def addBot(self, alias): self.botList[alias] = alias self.refresh() def getSelected(self): item = self.botListView.currentItem() if item is not None: return item.text() return '' def removeButtonPressed(self): count = len(self.compareList) result = QMessageBox.question( self, "Delete Bots", "Are you sure you want to delete " + str(count) + " bots?", QMessageBox.Yes, QMessageBox.No) if result == QMessageBox.Yes: self._removeManyBots() def _removeSingleBot(self): item = self.botListView.currentItem() if item is not None: alias = item.text() self.parent.deleteBot(alias) self.botList.pop(alias) self.refresh() def _removeManyBots(self): for item in self.compareList: self.parent.deleteBot(item) self.botList.pop(item) self.refresh() def setSelection(self, name): item = self.botListView.findItems(name, Qt.MatchExactly) if len(item) > 0: self.botListView.setCurrentItem(item[0]) def buttonPressed(self): result = QMessageBox.question( self, "Delete Bot", "Are you sure you want to delete this bot?", QMessageBox.Yes, QMessageBox.No) if result == QMessageBox.Yes: self._removeSingleBot() def getSelectedBots(self, item): alias = item.text() if alias not in self.compareList: self.compareList[alias] = alias elif alias in self.compareList: del self.compareList[alias] if len(self.compareList) == 2: self.compareButton.setText('Compare Bots') self.compareButton.setEnabled(True) else: self.compareButton.setText('Select 2 Bots to Compare') self.compareButton.setEnabled(False) if len(self.compareList) > 0: self.removeButton.setEnabled(True) else: self.removeButton.setEnabled(False) def displayMessage(self, msg, colour='black'): self.error_msg.setText(msg) self.error_msg.setStyleSheet('color: ' + colour) def removeMessage(self): self.error_msg.setText('') self.error_msg.setStyleSheet('color: black') def compareButtonPressed(self): self.removeMessage() if len(self.compareList) != 2: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setText( "Invalid number of bots selected - Please select 2 bots") msgBox.setWindowTitle("Compare Bots") msgBox.setStandardButtons(QMessageBox.Ok) msgBox.exec() return startDate, endDate, coin, ok = ComparisonInputDialog.getUserInput(self) if ok: bot1 = list(self.compareList.keys())[0] bot2 = list(self.compareList.keys())[1] self.botOneObject = self.botManager.loadBot(bot1) self.botTwoObject = self.botManager.loadBot(bot2) self.botOneObject.changeParam('Start Trading Date', startDate) self.botOneObject.changeParam('End Trading Date', endDate) self.botTwoObject.changeParam('Start Trading Date', startDate) self.botTwoObject.changeParam('End Trading Date', endDate) if not self.checkBot(self.botOneObject): return if not self.checkBot(self.botTwoObject): return try: self.data = fetchData.getData(exchange="Gemini", resolution="day", coin=coin) buySellDataOne = self.botOneObject.processHistoricalData( self.data) buySellDataTwo = self.botTwoObject.processHistoricalData( self.data) window = CompareWindow(self.data, bot1, bot2, buySellDataOne, buySellDataTwo, coin, self.botManager) self.windows.append(window) window.show() except IndexError as IE: self.displayMessage('Please select an appropriate date range', 'red') def checkBot(self, bot): try: bot.checkParameters() return True except exceptions.InvalidStartEndDates as NE: self.displayMessage( 'Invalid Input - Please give appropriate start and end dates', 'red') except exceptions.InvalidMovingAvgs as II: self.displayMessage( 'Invalid Input - Please give appropriate moving day averages', 'red') except exceptions.InvalidDays as ID: self.displayMessage( 'Invalid input - Please give a positive number of consecutive days', 'red') except exceptions.InvalidIntervals as IV: self.displayMessage( 'Invalid input - Please give appropriate interval values', 'red') except exceptions.InvalidThresholds as IT: self.displayMessage( 'Invalid Input - Please give appropriate threshold values', 'red') return False
class outlineBasics(QAbstractItemView): def __init__(self, parent=None): pass def getSelection(self): sel = [] for i in self.selectedIndexes(): if i.column() != 0: continue if not i in sel: sel.append(i) return sel def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self.menu = self.makePopupMenu() self.menu.popup(event.globalPos()) else: QAbstractItemView.mouseReleaseEvent(self, event) def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Add / remove items self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open Item"), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) menu.addSeparator() # Add / remove items self.actAddFolder = QAction( QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) menu.addSeparator() # Copy, cut, paste self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "Cut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [ qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor") ]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction( mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons self.menuCustomIcons = QMenu( qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("background: transparent; border: none;") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet( "background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \ or not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) > 0 and index.isValid( ) and not index.internalPointer().isFolder(): self.actAddFolder.setEnabled(False) self.actAddText.setEnabled(False) if len(sel) == 0: self.actOpen.setEnabled(False) self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) return menu def openItem(self): idx = self.currentIndex() from manuskript.functions import MW MW.openIndex(idx) def addFolder(self): self.addItem("folder") def addText(self): self.addItem("text") def addItem(self, _type="folder"): if len(self.selectedIndexes()) == 0: parent = self.rootIndex() else: parent = self.currentIndex() if _type == "text": _type = settings.defaultTextType item = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=_type) self.model().appendItem(item, parent) def copy(self): mimeData = self.model().mimeData( self.selectionModel().selectedIndexes()) qApp.clipboard().setMimeData(mimeData) def paste(self): index = self.currentIndex() if len(self.getSelection()) == 0: index = self.rootIndex() data = qApp.clipboard().mimeData() self.model().dropMimeData(data, Qt.CopyAction, -1, 0, index) def cut(self): self.copy() self.delete() def delete(self): self.model().removeIndexes(self.getSelection()) def setPOV(self, POV): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.POV.value), str(POV)) def setStatus(self, status): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.status.value), str(status)) def setLabel(self, label): for i in self.getSelection(): self.model().setData(i.sibling(i.row(), Outline.label.value), str(label)) def setCustomIcon(self, customIcon): for i in self.getSelection(): item = i.internalPointer() item.setCustomIcon(customIcon) def setCustomIconFromItem(self, item): icon = item.data(Qt.UserRole) self.setCustomIcon(icon) self.menu.close() def filterLstIcons(self, text): for l in self.lstIcons.findItems("", Qt.MatchContains): l.setHidden(not text in l.data(Qt.UserRole))
class SelectionPanel(QWidget): autobuild = QtCore.pyqtSignal() def __init__(self, settings): super().__init__() self.settings = settings # Initializing GUI elements self.domains_list = QComboBox(self) self.search = QLineEdit(self) self.functions_list = QListWidget(self) self.autobuild_button = QPushButton('Autobuild') # Preparing elements by giving initial values, etc self.setMinimumHeight(500) self.search.setPlaceholderText('Search...') # Setting all widgets in their places layout = QVBoxLayout() layout.addWidget(self.domains_list) layout.addWidget(self.search) layout.addWidget(self.functions_list) layout.addWidget(self.autobuild_button) self.setLayout(layout) self.domains_list.activated[str].connect(self.on_select_domain) self.search.textEdited.connect(self.on_search) self.autobuild_button.clicked.connect(self.on_autobuild) self.settings.package_changed.connect(self.init_selection_panel) self.settings.tl_changed.connect(self.refresh) def init_selection_panel(self): if not self.settings.package.broken: self.functions_dict = copy.deepcopy( self.settings.package.functions) self.search.setEnabled(True) self.refresh() else: self.search.setEnabled(False) self.reset() def refresh(self): self.domains_type = (self.settings.package.type if not self.settings.tl_group.isChecked() else utils.THREADING_LAYER) domains_list = self.functions_dict[self.domains_type].keys() self.set_widget_items(self.domains_list, domains_list) self.on_select_domain() def on_select_domain(self): self.current_domain = self.domains_list.currentText() self.on_search(self.search.text()) def on_search(self, search_request): self.set_widget_items(self.functions_list, [ entry for entry in self.functions_dict[self.domains_type] [self.current_domain] if search_request.upper() in entry.upper() ]) def on_autobuild(self): self.autobuild.emit() def set_widget_items(self, widget, items): """ Adds items to widget :param widget: widget :param items: list of strings """ widget.clear() widget.addItems(items) def reset(self): self.domains_list.clear() self.functions_list.clear() def add_function(self, function): """ Adds new function to required list :param function: name if function """ domain_type, domain, index = self.find_function(function) self.functions_dict[domain_type][domain].insert(index, function) if domain == self.current_domain: self.functions_list.insertItem(index, QListWidgetItem(function)) self.on_search(self.search.text()) def remove_function(self, function): """ Removes function from left list """ domain_type, domain, index = self.find_function(function) self.functions_dict[domain_type][domain].remove(function) if self.current_domain == domain: item = self.functions_list.findItems(function, QtCore.Qt.MatchExactly) if item: self.functions_list.takeItem(self.functions_list.row(item[0])) def find_function(self, function_name): previous_domain = '' initial_functions_dict = self.settings.package.functions for domain_type, domain, function in utils.walk_dict( initial_functions_dict): if domain != previous_domain: index = 0 if function_name == function: return domain_type, domain, index elif function in self.functions_dict[domain_type][domain]: previous_domain = domain index = self.functions_dict[domain_type][domain].index( function) + 1
class DownloadedPage(QWidget): send_trigger = pyqtSignal(dict) def __init__(self, parent=None): super(DownloadedPage, self).__init__(parent) self.data_path = '../../../data/downloaded.json' self.rm_file_check = True self.downloaded_list_widget = QListWidget(self) self.downloaded_list_widget.setStyleSheet("QListWidget{border:none;}") self.downloaded_list_widget.setSpacing(2) self.downloaded_list_widget.setGeometry(QRect(10, 20, 950, 800)) self.init_ui() def init_ui(self): downloaded_dict_list = self.load_downloaded_dict_list() if not downloaded_dict_list: return for downloaded_dict in downloaded_dict_list: self.add_downloaded_item_to_widget(downloaded_dict) def add_downloaded_item(self, downloaded_dict): downloaded_dict['date'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) self.add_downloaded_item_to_widget(downloaded_dict) self.add_downloaded_item_to_data(downloaded_dict) def add_downloaded_item_to_widget(self, downloaded_dict): item = QListWidgetItem() item.setText(downloaded_dict['id']) item.setSizeHint(QSize(608, 86)) self.downloaded_list_widget.addItem(item) self.downloaded_list_widget.setItemWidget( item, self.create_downloaded_item(downloaded_dict)) def add_downloaded_item_to_data(self, downloaded_dict): downloaded_dict_list = self.load_downloaded_dict_list() downloaded_dict_list.append(downloaded_dict) self.write_data(downloaded_dict_list) def load_downloaded_dict_list(self): if not os.path.exists(self.data_path): os.makedirs('../../../data', exist_ok=True) return [] with open(self.data_path, 'r', encoding='utf-8') as f: downloaded_dict_list = json.load(f) return downloaded_dict_list def create_downloaded_item(self, downloaded_dict): book_name = downloaded_dict['file_name'] item_widget = QWidget() item_widget.setObjectName(f'{downloaded_dict["id"]}') hbox_layout = QHBoxLayout() item_label = QLabel() pixmap, push = self.get_icon_by_file_type(book_name) item_label.setPixmap(pixmap) hbox_layout.addWidget(item_label) vlayout = QVBoxLayout() file_name_label = QLabel(book_name) vlayout.addWidget(file_name_label) h_in_v_item_layout = QHBoxLayout() h_in_v_item_layout.addWidget(QLabel(downloaded_dict['size']), Qt.AlignRight) downloaded_date_label = QLabel() downloaded_date_label.setObjectName(f'{downloaded_dict["id"]}_date') downloaded_date_label.setText(f'{downloaded_dict["date"]}') h_in_v_item_layout.addWidget(downloaded_date_label, Qt.AlignRight) vlayout.addLayout(h_in_v_item_layout) hbox_layout.addLayout(vlayout) file_path = f'../../../downloads/{book_name}' if os.path.exists(file_path): del_btn = QPushButton() del_btn.setIcon(QIcon('delete.png')) del_btn.setStyleSheet("QPushButton{border:none}") del_btn.clicked.connect( lambda: self.remove_task(downloaded_dict["id"], file_path)) if push: send_btn = QPushButton() send_btn.setIcon(QIcon('send.png')) send_btn.setStyleSheet("QPushButton{border:none}") downloaded_dict['file_path'] = file_path send_btn.clicked.connect( lambda: self.send_to_kindle(downloaded_dict)) hbox_layout.addWidget(send_btn) hbox_layout.addWidget(del_btn) else: download_btn = QPushButton() download_btn.setIcon(QIcon('download_btn.png')) download_btn.setStyleSheet("QPushButton{border:none}") download_btn.clicked.connect(lambda: self.regain(downloaded_dict)) hbox_layout.addWidget(download_btn) item_widget.setLayout(hbox_layout) item_widget_style_sheet = f'#{downloaded_dict["id"]}{{background-color:rgb(241,231,230);color:rgb(210,10,10)}}' item_widget.setStyleSheet(item_widget_style_sheet) return item_widget def remove_task(self, id, file_path): if self.remove_file_or_not(): if self.rm_file_check: self.remove_file(file_path) self.remove_task_from_list_widget(id) self.remove_task_from_data(id) def remove_file_or_not(self): rm_file_checkbox = QCheckBox('同时删除源文件') rm_file_checkbox.setChecked(True) rm_file_checkbox.stateChanged.connect(self.checkbox_check) message_box = QMessageBox() message_box.setIcon(QMessageBox.Warning) message_box.setText("您确定要删除此任务吗?") sure = message_box.addButton('确定', QMessageBox.AcceptRole) cancel = message_box.addButton('取消', QMessageBox.RejectRole) message_box.setDefaultButton(sure) message_box.setCheckBox(rm_file_checkbox) reply = message_box.exec() if reply == QMessageBox.AcceptRole: return True return False def checkbox_check(self, state): check = self.sender() if state == Qt.Unchecked: self.rm_file_check = False else: self.rm_file_check = True def remove_task_from_data(self, id): downloaded_dict_list = self.load_downloaded_dict_list() for downloaded_dict in downloaded_dict_list: if id in downloaded_dict['id']: downloaded_dict_list.remove(downloaded_dict) self.write_data(downloaded_dict_list) def write_data(self, downloaded_dict_list): with open(self.data_path, 'w') as f: json.dump(downloaded_dict_list, f) def remove_task_from_list_widget(self, id): item = self.downloaded_list_widget.findItems(id, Qt.MatchExactly)[0] self.downloaded_list_widget.takeItem( self.downloaded_list_widget.row(item)) def remove_file(self, file_path): if os.path.exists(file_path): os.remove(file_path) def send_to_kindle(self, downloaded_dict): if not check_send_config(): reply = QMessageBox.question(self, '配置提示', '推送配置未完成,不能推送书籍,需要现在配置吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: config_dialog = ConfigDialog(self) config_dialog.show() return downloaded_dict['status'] = "0" self.send_trigger.emit(downloaded_dict) QMessageBox.information(self, '推送通知', '正在推送...') self.send_work = SendWorker(downloaded_dict) self.send_work.send_trigger.connect(self.call_back_send) self.send_work.start() def regain(self, downloaded_dict): pass def get_file_extension(self, file_name): return file_name.strip().split(".")[-1] def get_icon_by_file_type(self, file_name): extension = self.get_file_extension(file_name) if extension in ['rar', 'zip', '7z']: return QPixmap('zip.png'), False elif extension == 'mobi': return QPixmap('book.png'), True elif extension == 'pdf': return QPixmap('pdf.png'), True else: return QPixmap('file.png'), False def get_status_msg(self, status_code): status_dict = {"1": "推送成功", "-1": "推送失败", "0": "正在推送"} if str(status_code) in status_dict: return status_dict[str(status_code)] else: return '未知状态' def call_back_send(self, send_dict): QMessageBox.information(self, '推送完成', self.get_status_msg(send_dict['status'])) self.send_trigger.emit(send_dict)
class DownloadingPage(QWidget): def __init__(self, parent=None): super(DownloadingPage, self).__init__(parent) self.download_worker_dict = {} self.data_path = os.path.join(data, 'downloading.json') self.downloaing_list_widget = QListWidget(self) self.downloaing_list_widget.setStyleSheet("QListWidget{border:none;}") self.downloaing_list_widget.setSpacing(2) self.downloaing_list_widget.setGeometry(QRect(10, 20, 950, 800)) self.init_ui() def check_download(self, downloading_dict): downloading_dict_list = self.load_downloading_dict_list() if not downloading_dict_list: return True for downloading_dict in downloading_dict_list: if downloading_dict['url'].find("lanjuhua"): return False def init_ui(self): downloading_dict_list = self.load_downloading_dict_list() if not downloading_dict_list: return for downloading_dict in downloading_dict_list: self.add_downloading_item_to_widget(downloading_dict) def add_downloading_item(self, downloading_dict, worker=None): self.download_worker_dict[downloading_dict['id']] = worker self.add_downloading_item_to_widget(downloading_dict) self.add_downloading_item_to_data(downloading_dict) def add_downloading_item_to_widget(self, downloading_dict): item = QListWidgetItem() item.setText(downloading_dict['id']) item.setSizeHint(QSize(608, 86)) self.downloaing_list_widget.addItem(item) self.downloaing_list_widget.setItemWidget( item, self.create_downloading_item(downloading_dict)) def add_downloading_item_to_data(self, downloading_dict): downloading_dict_list = self.load_downloading_dict_list() downloading_dict_list.append(downloading_dict) self.write_data(downloading_dict_list) def load_downloading_dict_list(self): if not os.path.exists(self.data_path): os.makedirs(data, exist_ok=True) return [] with open(self.data_path, 'r', encoding='utf-8') as f: downloading_dict_list = json.load(f) return downloading_dict_list def create_downloading_item(self, downloading_dict): book_name = downloading_dict['file_name'] item_widget = QWidget() item_widget.setObjectName(f'{downloading_dict["id"]}') hbox_layout = QHBoxLayout() item_label = QLabel() item_label.setPixmap(QPixmap(os.path.join(assets, 'book.png'))) hbox_layout.addWidget(item_label) vlayout = QVBoxLayout() file_name_label = QLabel(book_name) download_progress = ProgressBar( minimum=0, maximum=100, textVisible=False, objectName=f'{downloading_dict["id"]}_progress_bar') style_sheet = f''' #{downloading_dict["id"]}_progress_bar {{ min-height: 6px; max-height: 6px; border-radius: 6px; }} #{downloading_dict["id"]}_progress_bar::chunk {{ border-radius: 6px; width:12px; background-color: #D20A0A; }} ''' download_progress.setStyleSheet(style_sheet) vlayout.addWidget(file_name_label) vlayout.addWidget(download_progress) h_in_v_item_layout = QHBoxLayout() h_in_v_item_layout.addWidget(QLabel(downloading_dict['size']), Qt.AlignLeft) percent_label = QLabel() percent_label.setObjectName(f'{downloading_dict["id"]}_percent') percent_label.setText(f'已经下载:0%') h_in_v_item_layout.addWidget(percent_label, Qt.AlignHCenter) speed_label = QLabel() speed_label.setObjectName(f'{downloading_dict["id"]}_speed') speed_label.setText("0 kb/s") h_in_v_item_layout.addWidget(speed_label, Qt.AlignRight) vlayout.addLayout(h_in_v_item_layout) hbox_layout.addLayout(vlayout) del_btn = QPushButton() del_btn.setIcon(QIcon(os.path.join(assets, 'delete.png'))) del_btn.setStyleSheet("QPushButton{border:none}") del_btn.clicked.connect( lambda: self.remove_task(downloading_dict["id"])) hbox_layout.addWidget(del_btn) item_widget.setLayout(hbox_layout) item_widget_style_sheet = f'#{downloading_dict["id"]}{{background-color:rgb(241,231,230);color:rgb(210,10,10)}}' item_widget.setStyleSheet(item_widget_style_sheet) return item_widget def downloading_callback(self, downloading_dict): downloading_dict = list(downloading_dict) def remove_task(self, id): print(f'remove_task {id}...') self.remove_task_form_list_widget(id) self.remove_task_from_data(id) def remove_task_from_data(self, id): downloading_dict_list = self.load_downloading_dict_list() for downloading_dict in downloading_dict_list: if id in downloading_dict['id']: if id in self.download_worker_dict: eval(str(self.download_worker_dict[id]())) downloading_dict_list.remove(downloading_dict) self.write_data(downloading_dict_list) def write_data(self, downloading_dict_list): with open(self.data_path, 'w') as f: json.dump(downloading_dict_list, f) def remove_task_form_list_widget(self, id): item = self.downloaing_list_widget.findItems(id, Qt.MatchExactly)[0] self.downloaing_list_widget.takeItem( self.downloaing_list_widget.row(item)) def update_progress(self, progress_dict): percent_label = self.downloaing_list_widget.findChild( QLabel, f'{progress_dict["id"]}_percent') if percent_label: percent_label.setText(f'已经下载:{progress_dict["progress"]}%') speed_label = self.downloaing_list_widget.findChild( QLabel, f'{progress_dict["id"]}_speed') speed_label.setText(f'{progress_dict["speed"]} kb/s') progress_bar = self.downloaing_list_widget.findChild( ProgressBar, f'{progress_dict["id"]}_progress_bar') progress_bar.setValue(progress_dict["progress"])
class Indexer(QWidget): def __init__(self, wheres_the_fck_receipt: api_interface.WheresTheFckReceipt, parent=None): QWidget.__init__(self, parent=None) self.wheres_the_fck_receipt = wheres_the_fck_receipt self.index_job = None # type: api_interface.IndexJob self.index_job_timer = QTimer() self.index_job_timer.timeout.connect(self.index_job_timer_timeout) # WIDGETS # add dir button self.add_directory = QPushButton('Add Directory') self.add_directory.setEnabled(True) self.add_directory.clicked.connect(self.add_directory_clicked) # locations self.directories = QListWidget() self.directories.itemSelectionChanged.connect(self.directories_selection_changed) self.directories.setSelectionMode(QAbstractItemView.SingleSelection) for dir in self.wheres_the_fck_receipt.get_directories(): self.directories.addItem(dir) # the locations_action_bar self.index = QPushButton('Update') self.index.clicked.connect(self.update_clicked) self.remove_dir = QPushButton('Remove') self.remove_dir.clicked.connect(self.remove_clicked) self.re_index = QPushButton('Re-Index') self.re_index.clicked.connect(self.reindex_clicked) file_list_action_bar_layout = QHBoxLayout() file_list_action_bar_layout.setContentsMargins(0, 0, 0, 0) file_list_action_bar_layout.addWidget(self.index) file_list_action_bar_layout.addWidget(self.remove_dir) file_list_action_bar_layout.addWidget(self.re_index) self.file_list_action_bar_widget = QWidget() self.file_list_action_bar_widget.setLayout(file_list_action_bar_layout) self.file_list_action_bar_widget.setEnabled(False) # index_status_widget self.index_progress = QProgressBar() self.index_progress.setEnabled(False) self.stop_index = QPushButton('Stop Indexing') self.stop_index.setEnabled(False) self.stop_index.clicked.connect(self.stop_index_clicked) index_status_widget_layout = QHBoxLayout() index_status_widget_layout.setContentsMargins(0, 0, 0, 0) index_status_widget_layout.addWidget(self.index_progress) index_status_widget_layout.addWidget(self.stop_index) index_status_widget = QWidget() index_status_widget.setLayout(index_status_widget_layout) # index console self.index_console = QTextEdit() self.index_console.setReadOnly(True) # self.index_console.setEnabled(False) # layout layout = QVBoxLayout() layout.addWidget(QLabel("Indexed Directories:")) layout.addWidget(self.add_directory) layout.addWidget(self.directories) layout.addWidget(self.file_list_action_bar_widget) layout.addWidget(QLabel("Indexer Status:")) layout.addWidget(index_status_widget) layout.addWidget(self.index_console) self.setLayout(layout) def directories_selection_changed(self): list_items = self.directories.selectedItems() self.file_list_action_bar_widget.setEnabled(len(list_items) == 1) def add_directory_clicked(self): settings = QSettings('WheresTheFckReceipt', 'WheresTheFckReceipt') last_directory_added = settings.value("last_directory_added", "") directory = str(QFileDialog.getExistingDirectory(self, "Select Directory", last_directory_added, QFileDialog.ShowDirsOnly)) if directory: settings.setValue("last_directory_added", directory) del settings # get the job if not self.directories.findItems(directory, Qt.MatchExactly): self.directories.addItem(directory) self.index_job = self.wheres_the_fck_receipt.add_directory(directory) self.run_indexer() def run_indexer(self): # manage gui self.add_directory.setEnabled(False) self.directories.setEnabled(False) self.file_list_action_bar_widget.setEnabled(False) self.index_progress.setEnabled(True) self.index_progress.reset() self.stop_index.setEnabled(True) # self.index_console.setEnabled(True) self.index_console.clear() # start job self.stop_index.setEnabled(True) self.index_job.start() self.index_job_timer.start(500) def update_clicked(self): self.index_job = self.wheres_the_fck_receipt.update_directory(self.directories.currentItem().text()) self.run_indexer() def remove_clicked(self): self.add_directory.setEnabled(False) self.directories.setEnabled(False) self.file_list_action_bar_widget.setEnabled(False) progress_updater = ProgressUpdater(self) self.wheres_the_fck_receipt.remove_directory(self.directories.currentItem().text(), progress_updater) if progress_updater.canceled() is False: self.directories.takeItem(self.directories.currentRow()) self.add_directory.setEnabled(True) self.directories.setEnabled(True) self.file_list_action_bar_widget.setEnabled(True) def reindex_clicked(self): self.index_job = self.wheres_the_fck_receipt.reindex_directory(self.directories.currentItem().text()) self.run_indexer() def stop_index_clicked(self): self.index_job.stop() self.indexing_stopped() def indexing_stopped(self): self.index_job_timer.stop() self.add_directory.setEnabled(True) self.directories.setEnabled(True) self.file_list_action_bar_widget.setEnabled(len(self.directories.selectedItems()) > 0) self.index_progress.setEnabled(False) self.stop_index.setEnabled(False) # self.index_console.setEnabled(False) self.index_job = None def index_job_timer_timeout(self): for msg in self.index_job.get_messages(): self.index_console.append(msg) num_files = self.index_job.get_num_files() if num_files and self.index_progress.maximum() != num_files: self.index_progress.setRange(0, num_files) curr_file_idx = self.index_job.get_curr_file_index() if curr_file_idx: self.index_progress.setValue(curr_file_idx) if self.index_job.is_finished(): self.index_progress.setValue(self.index_progress.maximum()) self.indexing_stopped()
class SubwindowMisc(QWidget): """Show subwindow with miscellaneous settings.""" current_tab = -1 def createWindow(self, mainWindow, tab=''): """Create subwindow with miscellaneous settings.""" try: parent = None super().__init__(parent) # self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setWindowIcon( QIcon(scctool.settings.getResFile('settings.png'))) self.setWindowModality(Qt.ApplicationModal) self.mainWindow = mainWindow self.passEvent = False self.controller = mainWindow.controller self.__dataChanged = False self.createButtonGroup() self.createTabs(tab) mainLayout = QVBoxLayout() mainLayout.addWidget(self.tabs) mainLayout.addLayout(self.buttonGroup) self.setLayout(mainLayout) self.resize( QSize(int(mainWindow.size().width() * 0.9), self.sizeHint().height())) relativeChange = QPoint(int(mainWindow.size().width() / 2), int(mainWindow.size().height() / 3))\ - QPoint(int(self.size().width() / 2), int(self.size().height() / 3)) self.move(mainWindow.pos() + relativeChange) self.setWindowTitle(_("Miscellaneous Settings")) except Exception: module_logger.exception("message") def createTabs(self, tab=''): """Create tabs.""" self.tabs = QTabWidget() self.createMapsBox() self.createFavBox() self.createAliasBox() self.createOcrBox() self.createAlphaBox() self.createSC2ClientAPIBox() self.createAligulacTab() self.createCounterTab() # Add tabs self.tabs.addTab(self.mapsBox, _("Map Manager")) self.tabs.addTab(self.favBox, _("Favorites")) self.tabs.addTab(self.aliasBox, _("Alias")) self.tabs.addTab(self.ocrBox, _("OCR")) self.tabs.addTab(self.alphaBox, _("AlphaTL && Ingame Score")) self.tabs.addTab(self.clientapiBox, _("SC2 Client API")) self.tabs.addTab(self.aligulacTab, _("Aligulac")) self.tabs.addTab(self.counterTab, _("Countdown && Ticker")) table = dict() table['mapmanager'] = 0 table['favorites'] = 1 table['alias'] = 2 table['ocr'] = 3 table['alphatl'] = 4 table['sc2clientapi'] = 5 table['aligulac'] = 6 table['counter'] = 7 self.tabs.setCurrentIndex(table.get(tab, SubwindowMisc.current_tab)) self.tabs.currentChanged.connect(self.tabChanged) @classmethod def tabChanged(cls, idx): """Save the current tab index.""" SubwindowMisc.current_tab = idx def changed(self): """Handle changes.""" self.__dataChanged = True def createAlphaBox(self): """Create Alpha QWidget.""" self.alphaBox = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("AlphaTL")) layout = QHBoxLayout() self.cb_trans_banner = QCheckBox( " " + _("Download transparent Banner of the Match")) self.cb_trans_banner.setChecked( scctool.settings.config.parser.getboolean( "SCT", "transparent_match_banner")) self.cb_trans_banner.stateChanged.connect(self.changed) layout.addWidget(self.cb_trans_banner) box.setLayout(layout) mainLayout.addWidget(box) box = QGroupBox(_("Set Ingame Score Task")) layout = QVBoxLayout() self.cb_ctrlx = QCheckBox(" " + _('Automatically press Ctrl+X to apply the' ' correct player order ingame')) self.cb_ctrlx.setToolTip( _("This will ensure that the player of the first team is always" " on the left/top in the ingame Observer UI.")) self.cb_ctrlx.setChecked( scctool.settings.config.parser.getboolean("SCT", "CtrlX")) self.cb_ctrlx.stateChanged.connect(self.changed) layout.addWidget(self.cb_ctrlx) self.cb_ctrln = QCheckBox(" " + _('Automatically press Ctrl+N before' ' OCR to display player names')) self.cb_ctrln.setToolTip( _("This is recommended for Standard and Gawliq Observer UI.")) self.cb_ctrln.setChecked( scctool.settings.config.parser.getboolean("SCT", "CtrlN")) self.cb_ctrln.stateChanged.connect(self.changed) layout.addWidget(self.cb_ctrln) self.cb_ctrlshifts = QCheckBox( " " + _('Automatically press Ctrl+Shift+S to display' ' the ingame score')) self.cb_ctrlshifts.setToolTip( _("Ctrl+Shift+S is needed for the WCS-Gameheart Oberserver" " Overlay, but disables the sound for other overlays.")) self.cb_ctrlshifts.setChecked( scctool.settings.config.parser.getboolean("SCT", "CtrlShiftS")) self.cb_ctrlshifts.stateChanged.connect(self.changed) layout.addWidget(self.cb_ctrlshifts) self.cb_ctrlshiftc = QCheckBox( " " + _('Automatically press Ctrl+Shift+C to toogle the clan tag')) self.cb_ctrlshiftc.setChecked( scctool.settings.config.parser.getboolean("SCT", "CtrlShiftC")) self.cb_ctrlshiftc.stateChanged.connect(self.changed) layout.addWidget(self.cb_ctrlshiftc) container = QHBoxLayout() self.cb_ctrlshiftr = QComboBox() self.cb_ctrlshiftr.addItem("0") self.cb_ctrlshiftr.addItem("1") self.cb_ctrlshiftr.addItem("2") try: self.cb_ctrlshiftr.setCurrentIndex( scctool.settings.config.parser.getint("SCT", "CtrlShiftR")) except Exception: self.cb_ctrlshiftr.setCurrentIndex(0) self.cb_ctrlshiftr.setMaximumWidth(40) self.cb_ctrlshiftr.currentIndexChanged.connect(self.changed) container.addWidget( QLabel( _('Automatically press Ctrl+Shift+R to toogle the race icon ')) ) container.addWidget(self.cb_ctrlshiftr) container.addWidget(QLabel(_(' time(s)'))) layout.addLayout(container) self.cb_blacklist = QCheckBox(" " + _('Activate Blacklist for' ' Ingame Score')) self.cb_blacklist.setChecked( scctool.settings.config.parser.getboolean("SCT", "blacklist_on")) self.cb_blacklist.stateChanged.connect(self.changed) layout.addWidget(self.cb_blacklist) box.setLayout(layout) mainLayout.addWidget(box) box = QGroupBox(_("Blacklist for Ingame Score")) layout = QVBoxLayout() blacklistDesc = _("Enter your SC2 client usernames to deactivate" " automatically setting the ingame score and" " toogling the production tab when you are playing" " yourself. Replays are exempt.") label = QLabel(blacklistDesc) label.setAlignment(Qt.AlignJustify) label.setWordWrap(True) layout.addWidget(label) self.list_blacklist = ListTable(4, scctool.settings.config.getBlacklist()) self.list_blacklist.dataModified.connect(self.changed) self.list_blacklist.setFixedHeight(50) layout.addWidget(self.list_blacklist) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.alphaBox.setLayout(mainLayout) def createFavBox(self): """Create favorites box.""" self.favBox = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("Players")) layout = QHBoxLayout() self.list_favPlayers = ListTable( 4, scctool.settings.config.getMyPlayers()) self.list_favPlayers.dataModified.connect(self.changed) self.list_favPlayers.setFixedHeight(150) layout.addWidget(self.list_favPlayers) box.setLayout(layout) mainLayout.addWidget(box) box = QGroupBox(_("Teams")) layout = QVBoxLayout() self.list_favTeams = ListTable(3, scctool.settings.config.getMyTeams()) self.list_favTeams.dataModified.connect(self.changed) self.list_favTeams.setFixedHeight(100) layout.addWidget(self.list_favTeams) self.cb_swapTeams = QCheckBox( _('Swap my favorite team always to the left')) self.cb_swapTeams.setChecked( scctool.settings.config.parser.getboolean("SCT", "swap_myteam")) self.cb_swapTeams.stateChanged.connect(self.changed) layout.addWidget(self.cb_swapTeams) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.favBox.setLayout(mainLayout) def createAliasBox(self): """Create favorites box.""" self.aliasBox = QWidget() mainLayout = QGridLayout() aliasDesc = _( 'Player, team, and league aliases are replaced by the actual name when' + ' encountered by the match grabber. Additionally, SC2 player' + ' names listed as aliases are replaced in the intros' + ' and used to identify players by the automatic' + ' background tasks "Auto Score Update" and "Set Ingame Score".') label = QLabel(aliasDesc) label.setAlignment(Qt.AlignJustify) label.setWordWrap(True) mainLayout.addWidget(label, 1, 0, 1, 3) box = QGroupBox(_("Player Aliases")) layout = QVBoxLayout() self.list_aliasPlayers = AliasTreeView(self) self.list_aliasPlayers.aliasRemoved.connect( self.controller.aliasManager.removePlayerAlias) layout.addWidget(self.list_aliasPlayers) addButton = QPushButton(_("Add Alias")) addButton.clicked.connect( lambda: self.addAlias(self.list_aliasPlayers, _('Player Name'))) layout.addWidget(addButton) box.setLayout(layout) mainLayout.addWidget(box, 0, 0) box = QGroupBox(_("Team Aliases")) layout = QVBoxLayout() self.list_aliasTeams = AliasTreeView(self) self.list_aliasTeams.aliasRemoved.connect( self.controller.aliasManager.removeTeamAlias) layout.addWidget(self.list_aliasTeams) addButton = QPushButton(_("Add Alias")) addButton.clicked.connect( lambda: self.addAlias(self.list_aliasTeams, _('Team Name'))) layout.addWidget(addButton) box.setLayout(layout) mainLayout.addWidget(box, 0, 1) box = QGroupBox(_("League Aliases")) layout = QVBoxLayout() self.list_aliasLeagues = AliasTreeView(self) self.list_aliasLeagues.aliasRemoved.connect( self.controller.aliasManager.removeLeagueAlias) layout.addWidget(self.list_aliasLeagues) addButton = QPushButton(_("Add Alias")) addButton.clicked.connect( lambda: self.addAlias(self.list_aliasLeagues, _('League Name'))) layout.addWidget(addButton) box.setLayout(layout) mainLayout.addWidget(box, 0, 2) alias_list = self.controller.aliasManager.playerAliasList() for player, aliases in alias_list.items(): self.list_aliasPlayers.insertAliasList(player, aliases) alias_list = self.controller.aliasManager.teamAliasList() for league, aliases in alias_list.items(): self.list_aliasTeams.insertAliasList(league, aliases) alias_list = self.controller.aliasManager.leagueAliasList() for league, aliases in alias_list.items(): self.list_aliasLeagues.insertAliasList(league, aliases) self.aliasBox.setLayout(mainLayout) def addAlias(self, widget, scope, name=""): """Add an alias.""" name, ok = QInputDialog.getText(self, scope, scope + ':', text=name) if not ok: return name = name.strip() alias, ok = QInputDialog.getText(self, _('Alias'), _('Alias of {}').format(name) + ':', text="") alias = alias.strip() if not ok: return try: if widget == self.list_aliasPlayers: self.controller.aliasManager.addPlayerAlias(name, alias) elif widget == self.list_aliasTeams: self.controller.aliasManager.addTeamAlias(name, alias) elif widget == self.list_aliasLeagues: self.controller.aliasManager.addLeagueAlias(name, alias) widget.insertAlias(name, alias, True) except Exception as e: module_logger.exception("message") QMessageBox.critical(self, _("Error"), str(e)) def createSC2ClientAPIBox(self): """Create form for SC2 Client API config.""" self.clientapiBox = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("SC2 Client API Address")) layout = QGridLayout() self.cb_usesc2listener = QCheckBox( " " + _("Listen to SC2 Client API running" " on a different PC in the network.")) self.cb_usesc2listener.setChecked( scctool.settings.config.parser.getboolean( "SCT", "sc2_network_listener_enabled")) self.cb_usesc2listener.stateChanged.connect(self.changed) self.listener_address = MonitoredLineEdit() self.listener_address.setAlignment(Qt.AlignCenter) self.listener_address.setText( scctool.settings.config.parser.get("SCT", "sc2_network_listener_address")) self.listener_address.textModified.connect(self.changed) # self.tesseract.setAlignment(Qt.AlignCenter) self.listener_address.setPlaceholderText("[Your SC2 PC IP]:6119") self.listener_address.setToolTip( _('IP address and port of machine running SC2.')) ip_port = ( r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)" + r"{3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):[0-9]+$") self.listener_address.setValidator(QRegExpValidator(QRegExp(ip_port))) self.test_listener = QPushButton(" " + _("Test SC2 Client API Connection") + " ") self.test_listener.clicked.connect(self.testClientAPI) text = _("Activate this option if you are using a two computer " "setup with StarCraft Casting Tool running on a different" " PC than your SC2 client. Open the Battle.net launcher " "on the latter PC, click 'Options', 'Game Settings', and " "under SC2, check 'Additional Command Line Arguments', and " "enter '-clientapi 6119'. Finally set as network" " address below: '[Your SC2 PC IP]:6119'.") label = QLabel(text) label.setAlignment(Qt.AlignJustify) label.setOpenExternalLinks(True) label.setWordWrap(True) label.setMargin(5) layout.addWidget(label, 1, 0, 1, 3) layout.addWidget(self.cb_usesc2listener, 0, 0, 1, 3) layout.addWidget(QLabel(_("Network Address") + ": "), 3, 0) layout.addWidget(self.listener_address, 3, 1) layout.addWidget(self.test_listener, 3, 2) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.clientapiBox.setLayout(mainLayout) def testClientAPI(self): """Test for connection to sc2 client api.""" QApplication.setOverrideCursor(Qt.WaitCursor) address = self.listener_address.text().strip() url = "http://{}/ui".format(address) try: r = requests.get(url, timeout=10) r.raise_for_status() successfull = True except Exception: successfull = False module_logger.error("message") finally: QApplication.restoreOverrideCursor() title = _("Connection Test") if successfull: QMessageBox.information( self, title, _('Connection to SC2 client API established!')) else: QMessageBox.warning( self, title, _('Unable to connect to SC2 client API.' ' Please make sure that SC2 is currently' ' running on that machine.')) def createOcrBox(self): """Create forms for OCR.""" self.ocrBox = QWidget() mainLayout = QVBoxLayout() box = QGroupBox( _("Optical Character Recognition for" " Automatic Setting of Ingame Score")) layout = QGridLayout() self.cb_useocr = QCheckBox(" " + _("Activate Optical Character Recognition")) self.cb_useocr.setChecked( scctool.settings.config.parser.getboolean("SCT", "use_ocr")) self.cb_useocr.stateChanged.connect(self.changed) self.tesseract = MonitoredLineEdit() self.tesseract.setText( scctool.settings.config.parser.get("SCT", "tesseract")) self.tesseract.textModified.connect(self.changed) # self.tesseract.setAlignment(Qt.AlignCenter) self.tesseract.setPlaceholderText( "C:\\Program Files (x86)\\Tesseract-OCR\\tesseract") self.tesseract.setReadOnly(True) self.tesseract.setToolTip(_('Tesseract-OCR Executable')) self.browse = QPushButton(_("Browse...")) self.browse.clicked.connect(self.selectTesseract) text = _( "Sometimes the order of players given by the SC2-Client-API" " differs from the order in the Observer-UI resulting in a" " swapped match score. To correct this via Optical Character" " Recognition you have to download {} and install and select the" " exectuable below, if it is not detected automatically.") url = 'https://github.com/UB-Mannheim/tesseract' + \ '/wiki#tesseract-at-ub-mannheim' href = "<a href='{}'>" + "Tesseract-OCR" + "</a>" href = href.format(url) label = QLabel(text.format(href)) label.setAlignment(Qt.AlignJustify) label.setOpenExternalLinks(True) label.setWordWrap(True) label.setMargin(5) layout.addWidget(label, 1, 0, 1, 2) layout.addWidget(self.cb_useocr, 0, 0, 1, 2) layout.addWidget(QLabel(_("Tesseract-OCR Executable") + ":"), 2, 0) layout.addWidget(self.tesseract, 3, 0) layout.addWidget(self.browse, 3, 1) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.ocrBox.setLayout(mainLayout) if (not scctool.settings.windows): self.cb_useocr.setEnabled(False) self.cb_useocr.setAttribute(Qt.WA_AlwaysShowToolTips) self.cb_useocr.setToolTip( _("This feature is only available in Windows.")) self.tesseract.setEnabled(False) self.tesseract.setAttribute(Qt.WA_AlwaysShowToolTips) self.tesseract.setToolTip( _("This feature is only available in Windows.")) self.browse.setEnabled(False) self.browse.setAttribute(Qt.WA_AlwaysShowToolTips) self.browse.setToolTip( _("This feature is only available in Windows.")) def selectTesseract(self): """Create forms for tesseract.""" old_exe = self.tesseract.text() default = scctool.settings.config.findTesserAct(old_exe) exe, ok = QFileDialog.getOpenFileName( self, _("Select Tesseract-OCR Executable"), default, _("Tesseract-OCR Executable") + " (tesseract.exe);; " + _("Executable") + " (*.exe);; " + _("All files") + " (*)") if (ok and exe != old_exe): self.tesseract.setText(exe) self.changed() def createAligulacTab(self): """Create the aligulac tab.""" self.aligulacTab = QWidget() layout = QGridLayout() self.aligulacTreeview = AligulacTreeView( self, self.controller.aligulacManager) layout.addWidget(self.aligulacTreeview, 0, 0, 3, 1) self.pb_addAligulacID = QPushButton(_("Add Aligluac ID")) self.pb_addAligulacID.clicked.connect( lambda x, self=self: self.addAligulacID()) layout.addWidget(self.pb_addAligulacID, 1, 1) self.pb_removeAligulacID = QPushButton(_("Remove Aligulac ID")) self.pb_removeAligulacID.clicked.connect(self.removeAligulacID) layout.addWidget(self.pb_removeAligulacID, 2, 1) layout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), 0, 1) self.aligulacTab.setLayout(layout) def addAligulacID(self, name='', aligulac_id=1): """Add an aligulac ID.""" text, ok = QInputDialog.getText(self, _('Player Name'), _('Player Name') + ':', text=name) text = text.strip() if not ok or not text: return aligulac_id, ok = QInputDialog.getInt(self, _('Aligulac ID'), _('Aligulac ID') + ':', value=aligulac_id, min=1) if not ok: return self.aligulacTreeview.insertItem(text, aligulac_id) def removeAligulacID(self): """Remove an selected aligulac ID.""" self.aligulacTreeview.removeSelected() def createCounterTab(self): """Create the aligulac tab.""" self.counterTab = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("Countdown")) layout = QFormLayout() self.le_countdown_replacement = QLineEdit() self.le_countdown_replacement.setText( scctool.settings.config.parser.get("Countdown", "replacement")) self.le_countdown_replacement.textChanged.connect(self.changed) layout.addRow(QLabel(_('Replacement Text')), self.le_countdown_replacement) self.cb_counter_matchgrabber_update = QCheckBox('') self.cb_counter_matchgrabber_update.setChecked( scctool.settings.config.parser.getboolean("Countdown", "matchgrabber_update")) self.cb_counter_matchgrabber_update.stateChanged.connect(self.changed) layout.addRow(QLabel(_('Update Static Countdown via MatchGrabber')), self.cb_counter_matchgrabber_update) self.counter_pretext = QPlainTextEdit() self.counter_pretext.setPlainText( scctool.settings.config.parser.get("Countdown", "pre_txt")) self.counter_pretext.textChanged.connect(self.changed) self.counter_posttext = QPlainTextEdit() self.counter_posttext.setPlainText( scctool.settings.config.parser.get("Countdown", "post_txt")) self.counter_posttext.textChanged.connect(self.changed) layout.addRow(QLabel(_('Pre-Text (in countdown.txt)')), self.counter_pretext) layout.addRow(QLabel(_('Post-Text (in countdown.txt)')), self.counter_posttext) box.setLayout(layout) mainLayout.addWidget(box) box = QGroupBox(_("Ticker")) layout = QFormLayout() box.setLayout(layout) self.ticker_pretext = QLineEdit() self.ticker_pretext.setText( scctool.settings.config.parser.get("Ticker", "prefix")) self.ticker_pretext.textChanged.connect(self.changed) layout.addRow(QLabel(_('Prefix text (in ticker.txt)')), self.ticker_pretext) mainLayout.addWidget(box) self.counterTab.setLayout(mainLayout) def createMapsBox(self): """Create box for map manager.""" self.mapsize = 300 self.mapsBox = QWidget() layout = QGridLayout() self.maplist = QListWidget() self.maplist.setSortingEnabled(True) for sc2map in scctool.settings.maps: self.maplist.addItem(QListWidgetItem(sc2map)) self.maplist.setCurrentItem(self.maplist.item(0)) self.maplist.currentItemChanged.connect(self.changePreview) # self.maplist.setFixedHeight(self.mapsize) self.maplist.setMinimumWidth(150) layout.addWidget(self.maplist, 0, 1, 2, 1) self.mapPreview = QLabel() self.mapPreview.setFixedWidth(self.mapsize) self.mapPreview.setFixedHeight(self.mapsize) self.mapPreview.setAlignment(Qt.AlignCenter) layout.addWidget(self.mapPreview, 0, 0) self.mapInfo = QLabel() self.mapInfo.setIndent(10) layout.addWidget(self.mapInfo, 1, 0) self.pb_addMapLiquipedia = QPushButton(_("Add from Liquipedia")) self.pb_addMapLiquipedia.clicked.connect(self.addFromLquipedia) self.pb_addMap = QPushButton(_("Add from File")) self.pb_addMap.clicked.connect(self.addMap) self.pb_renameMap = QPushButton(_("Rename")) self.pb_renameMap.clicked.connect(self.renameMap) self.pb_changeMap = QPushButton(_("Change Image")) self.pb_changeMap.clicked.connect(self.changeMap) self.pb_removeMap = QPushButton(_("Remove")) self.pb_removeMap.clicked.connect(self.deleteMap) self.sc_removeMap = QShortcut(QKeySequence("Del"), self.maplist) self.sc_removeMap.setAutoRepeat(False) self.sc_removeMap.setContext(Qt.WidgetWithChildrenShortcut) self.sc_removeMap.activated.connect(self.deleteMap) self.cb_newMapsPrompt = QCheckBox( _('Prompt to download new ladders maps.')) self.cb_newMapsPrompt.setChecked( scctool.settings.config.parser.getboolean("SCT", "new_maps_prompt")) self.cb_newMapsPrompt.stateChanged.connect(self.changed) self.pb_downloadLadderMaps = QPushButton(_("Download Ladder Maps")) self.pb_downloadLadderMaps.clicked.connect(self.downloadLadderMaps) box = QWidget() container = QHBoxLayout() container.addWidget(self.pb_addMapLiquipedia, 0) container.addWidget(self.pb_addMap, 0) container.addWidget(QLabel(), 1) container.addWidget(self.pb_downloadLadderMaps, 0) container.addWidget(QLabel(), 1) container.addWidget(self.pb_renameMap, 0) container.addWidget(self.pb_changeMap, 0) container.addWidget(self.pb_removeMap, 0) box.setLayout(container) layout.addWidget(box, 2, 0, 1, 2) layout.addWidget(self.cb_newMapsPrompt, 3, 0, 1, 1) layout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 3, 2, 1, 2) layout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 4, 0, 1, 2) self.changePreview() self.mapsBox.setLayout(layout) def renameMap(self): """Rename maps.""" item = self.maplist.currentItem() mapname = item.text() text, ok = QInputDialog.getText(self, _('Map Name'), _('Map Name') + ':', text=mapname) if not ok: return text = text.strip() if (text == mapname): return if text.lower() == 'tbd': QMessageBox.critical( self, _("Error"), _('"{}" is not a valid map name.').format(text)) return if (text in scctool.settings.maps): buttonReply = QMessageBox.warning( self, _("Duplicate Entry"), _("Map is already in list! Overwrite?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.No: return self.controller.addMap(self.controller.getMapImg(mapname, True), text) self.controller.deleteMap(mapname) item.setText(text) def changeMap(self): """Change a map.""" current_map = self.maplist.currentItem().text() fileName, ok = QFileDialog.getOpenFileName( self, _("Select Map Image (> 500x500px recommended)"), "", _("Supported Images") + " (*.png *.jpg *.jpeg)") if ok: base = os.path.basename(fileName) name, __ = os.path.splitext(base) name = name.replace("_", " ") self.controller.deleteMap(current_map) self.controller.addMap(fileName, current_map) self.changePreview() def addMap(self): """Add a map.""" fileName, ok = QFileDialog.getOpenFileName( self, _("Select Map Image (> 500x500px recommended)"), "", _("Supported Images") + " (*.png *.jpg *.jpeg)") if ok: base = os.path.basename(fileName) name, __ = os.path.splitext(base) name = name.replace("_", " ") map_name, ok = QInputDialog.getText(self, _('Map Name'), _('Map Name') + ':', text=name) map_name = map_name.strip() if ok: if map_name.lower() == 'tbd': QMessageBox.critical( self, _("Error"), _('"{}" is not a valid map name.').format(map_name)) return if (map_name in scctool.settings.maps): buttonReply = QMessageBox.warning( self, _("Duplicate Entry"), _("Map is already in list! Overwrite?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.No: return else: self.controller.deleteMap(map_name) self.controller.addMap(fileName, map_name) items = self.maplist.findItems(map_name, Qt.MatchExactly) if len(items) == 0: item = QListWidgetItem(map_name) self.maplist.addItem(item) self.maplist.setCurrentItem(item) else: self.maplist.setCurrentItem(items[0]) self.changePreview() def downloadLadderMaps(self): players_per_team, ok = QInputDialog.getItem( self, _('Select the type of ladder maps to download'), _('Please select a map type') + ':', ['1vs1', '2vs2', '3vs3', '4vs4'], editable=False) players_per_team = int(players_per_team[0]) found_a_map = False for sc2map in LiquipediaGrabber().get_ladder_mappool(players_per_team): if not sc2map in scctool.settings.maps: found_a_map = True self.controller.autoDownloadMap(sc2map, self) scctool.settings.maps.append(sc2map) items = self.maplist.findItems(sc2map, Qt.MatchExactly) if len(items) == 0: item = QListWidgetItem(sc2map) self.maplist.addItem(item) self.maplist.setCurrentItem(item) else: self.maplist.setCurrentItem(items[0]) self.changePreview() if not found_a_map: QMessageBox.information( self, _("No missing map"), _('All of the current ladder maps are already present.')) def addFromLquipedia(self): """Add a map from Liquipedia.""" grabber = LiquipediaGrabber() search_str = '' while True: search_str, ok = QInputDialog.getText(self, _('Map Name'), _('Map Name') + ':', text=search_str) search_str.strip() try: if ok and search_str: if search_str.lower() == 'tbd': QMessageBox.critical( self, _("Error"), _('"{}" is not a valid map name.').format( search_str)) continue try: QApplication.setOverrideCursor(Qt.WaitCursor) sc2map = grabber.get_map(search_str) except MapNotFound: QMessageBox.critical( self, _("Map not found"), _('"{}" was not found on Liquipedia.').format( search_str)) continue finally: QApplication.restoreOverrideCursor() map_name = sc2map.get_name() if (map_name in scctool.settings.maps): buttonReply = QMessageBox.warning( self, _("Duplicate Entry"), _("Map {} is already in list! Overwrite?".format( map_name)), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.No: break else: self.controller.deleteMap(map_name) try: QApplication.setOverrideCursor(Qt.WaitCursor) images = grabber.get_images(sc2map.get_map_images()) image = "" for size in sorted(images): if not image or size <= 2500 * 2500: image = images[size] url = grabber._base_url + image downloader = MapDownloader(self, map_name, url) downloader.download() if map_name not in scctool.settings.maps: scctool.settings.maps.append(map_name) items = self.maplist.findItems(map_name, Qt.MatchExactly) if len(items) == 0: item = QListWidgetItem(map_name) self.maplist.addItem(item) self.maplist.setCurrentItem(item) else: self.maplist.setCurrentItem(items[0]) self.changePreview() except Exception: raise finally: QApplication.restoreOverrideCursor() except Exception as e: module_logger.exception("message") QMessageBox.critical(self, _("Error"), str(e)) break def deleteMap(self): """Delete a map.""" item = self.maplist.currentItem() mapname = item.text() buttonReply = QMessageBox.question( self, _('Delete map?'), _("Delete '{}' permanently?").format(mapname), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.Yes: self.controller.deleteMap(mapname) self.maplist.takeItem(self.maplist.currentRow()) def changePreview(self): """Change the map preview.""" if self.maplist.count() < 1: return mapname = self.maplist.currentItem().text() if (mapname == "TBD"): self.pb_renameMap.setEnabled(False) self.pb_removeMap.setEnabled(False) self.sc_removeMap.setEnabled(False) else: self.pb_removeMap.setEnabled(True) self.pb_renameMap.setEnabled(True) self.sc_removeMap.setEnabled(True) file = self.controller.getMapImg(mapname, True) pixmap = QPixmap(file) height = pixmap.height() width = pixmap.width() ext = os.path.splitext(file)[1].replace(".", "").upper() size = humanize.naturalsize(os.path.getsize(file)) pixmap = QPixmap(file).scaled(self.mapsize, self.mapsize, Qt.KeepAspectRatio) self.mapPreview.setPixmap(pixmap) text = f"{width}x{height}px, {size}, {ext}" self.mapInfo.setText(text) def createButtonGroup(self): """Create buttons.""" try: layout = QHBoxLayout() layout.addWidget(QLabel("")) buttonCancel = QPushButton(_('Cancel')) buttonCancel.clicked.connect(self.closeWindow) layout.addWidget(buttonCancel) buttonSave = QPushButton(_('&Save && Close')) buttonSave.setToolTip(_("Shortcut: {}").format("Ctrl+S")) self.shortcut = QShortcut(QKeySequence("Ctrl+S"), self) self.shortcut.setAutoRepeat(False) self.shortcut.activated.connect(self.saveCloseWindow) buttonSave.clicked.connect(self.saveCloseWindow) layout.addWidget(buttonSave) self.buttonGroup = layout except Exception: module_logger.exception("message") def saveData(self): """Save the data.""" if (self.__dataChanged): scctool.settings.config.parser.set( "SCT", "myteams", ", ".join(self.list_favTeams.getData())) scctool.settings.config.parser.set( "SCT", "commonplayers", ", ".join(self.list_favPlayers.getData())) scctool.settings.config.parser.set("SCT", "tesseract", self.tesseract.text().strip()) scctool.settings.config.parser.set("SCT", "use_ocr", str(self.cb_useocr.isChecked())) scctool.settings.config.parser.set( "SCT", "new_maps_prompt", str(self.cb_newMapsPrompt.isChecked())) scctool.settings.config.parser.set( "SCT", "transparent_match_banner", str(self.cb_trans_banner.isChecked())) scctool.settings.config.parser.set( "SCT", "CtrlShiftS", str(self.cb_ctrlshifts.isChecked())) scctool.settings.config.parser.set( "SCT", "CtrlShiftC", str(self.cb_ctrlshiftc.isChecked())) scctool.settings.config.parser.set( "SCT", "swap_myteam", str(self.cb_swapTeams.isChecked())) scctool.settings.config.parser.set("SCT", "CtrlN", str(self.cb_ctrln.isChecked())) scctool.settings.config.parser.set("SCT", "CtrlX", str(self.cb_ctrlx.isChecked())) scctool.settings.config.parser.set( "SCT", "CtrlShiftR", str(self.cb_ctrlshiftr.currentText())) scctool.settings.config.parser.set( "SCT", "blacklist_on", str(self.cb_blacklist.isChecked())) scctool.settings.config.parser.set( "SCT", "blacklist", ", ".join(self.list_blacklist.getData())) scctool.settings.config.parser.set( "SCT", "sc2_network_listener_address", self.listener_address.text().strip()) scctool.settings.config.parser.set( "SCT", "sc2_network_listener_enabled", str(self.cb_usesc2listener.isChecked())) scctool.settings.config.parser.set( "Countdown", "matchgrabber_update", str(self.cb_counter_matchgrabber_update.isChecked())) scctool.settings.config.parser.set( "Countdown", "replacement", self.le_countdown_replacement.text()) scctool.settings.config.parser.set( "Countdown", "pre_txt", self.counter_pretext.toPlainText()) scctool.settings.config.parser.set( "Countdown", "post_txt", self.counter_posttext.toPlainText()) scctool.settings.config.parser.set( "Ticker", "prefix", self.ticker_pretext.text().strip()) self.controller.matchControl.tickerChanged.emit() self.controller.refreshButtonStatus() # self.controller.setCBS() self.__dataChanged = False def saveCloseWindow(self): """Save and close window.""" self.saveData() self.passEvent = True self.close() def closeWindow(self): """Close window.""" self.passEvent = True self.close() def closeEvent(self, event): """Handle close event.""" try: self.mainWindow.updateAllMapCompleters() if (not self.__dataChanged): event.accept() return if (not self.passEvent): if (self.isMinimized()): self.showNormal() buttonReply = QMessageBox.question( self, _('Save data?'), _("Save data?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.Yes: self.saveData() event.accept() except Exception: module_logger.exception("message")