def create_widget(self, parent=None): main_group_box = QGroupBox() main_layout = QHBoxLayout() top = QFrame() top_layout = QGridLayout() top_layout.setColumnStretch(0, 2) top_layout.setColumnStretch(1, 1) top_layout.addWidget(self.tips_label, 0, 0) top_layout.addWidget(self.path_label, 1, 0) self.button_folder_selection = QPushButton('Select directory') self.button_folder_selection.clicked.connect( lambda: self.on_dir_selection()) top_layout.addWidget(self.button_folder_selection, 1, 1) top.setLayout(top_layout) splitter = QSplitter(Qt.Vertical) splitter.addWidget(top) gestures_group_box = self.create_gestures_group_box() splitter.addWidget(gestures_group_box) splitter.setSizes([50, 200]) main_layout.addWidget(splitter) main_group_box.setLayout(main_layout) return main_group_box
def _layout( self) -> Tuple[QGridLayout, FileTreeView, FileTagView, ImageView]: layout = QGridLayout() layout.setContentsMargins(5, 5, 5, 5) splitter = QSplitter() layout.addWidget(splitter, 0, 0, 0, 0) left_splitter = QSplitter(Qt.Vertical) splitter.addWidget(left_splitter) # Top left file_tree = FileTreeView(self._on_file_tree_selection_changed) left_splitter.addWidget(file_tree) # Bottom left tagging = FileTagView(self.global_state) left_splitter.addWidget(tagging) # Right image = ImageView() splitter.addWidget(wrap_image(image)) # Initialize equal widths (needs to be set at the end) splitter.setSizes([1000000, 1000000]) return layout, file_tree, tagging, image
def __init__(self): QMainWindow.__init__(self) self.setWindowTitle('vizzero') self.core_controller = CoreController() self.plugins = [Handsim(self.core_controller), RecordHandFixed(self.core_controller)] vbox = QVBoxLayout(self) window = QWidget() window.setLayout(vbox) self.myo_canvas = RealtimeCanvas(self.core_controller) self.myo_canvas.native.setParent(window) self.sensor_controls = SensorControls(self.core_controller.sensor_controller) vbox.addWidget(self.sensor_controls) vbox.addWidget(self.myo_canvas.native) self.node_proc = None self.tabs = Tabs(self.core_controller, self.plugins) splitter1 = QSplitter(Qt.Horizontal) splitter1.addWidget(self.tabs) splitter1.addWidget(window) splitter1.setSizes([70, 30]) self.setCentralWidget(splitter1) self.core_controller.sensor_controller.rx_sensor_data_subject\ .subscribe(self.myo_canvas.feed_data, scheduler=self.draw_data_scheduler)
def _widgets(self): splitter = QSplitter() # -- Left left_splitter = QSplitter(Qt.Vertical) splitter.addWidget(left_splitter) # Top left filetree = FileTree(self._root_path, self._on_filetree_selection_changed) left_splitter.addWidget(filetree) # Bottom left tagging = Tagging() left_splitter.addWidget(tagging) # -- Right image = ImageDisplay() # Wrap it in a resizable scroll area area = QScrollArea() area.setWidget(image) area.setWidgetResizable(True) area.setAlignment(Qt.AlignCenter) splitter.addWidget(area) # A slight hack to split width equally splitter.setSizes([100000, 100000]) return splitter, filetree, tagging, image
def __init__(self, model): super(_ControlTab, self).__init__() self._model = model layout = QGridLayout() splitter = QSplitter() self._bot_table = _BotTable() self._responses_tab = _ResponsesTab() self._execute_tab = _ExecuteTab(self._responses_tab, model) self._tab_widget = QTabWidget() self._tab_widget.addTab(self._execute_tab, "Execute") self._tab_widget.addTab(self._responses_tab, "Responses") splitter.setOrientation(Qt.Vertical) splitter.addWidget(self._bot_table) splitter.addWidget(self._tab_widget) splitter.setSizes([50, 100]) layout.addWidget(splitter) self.setLayout(layout) self._register_listeners()
def _init_widgets(self): # Subclass QPlainTextEdit class SmartPlainTextEdit(QPlainTextEdit): def __init__(self, parent, callback): super(SmartPlainTextEdit, self).__init__(parent) self._callback = callback def keyPressEvent(self, event): super(SmartPlainTextEdit, self).keyPressEvent(event) if event.key() == Qt.Key_Return: if not (event.modifiers() == Qt.ShiftModifier): self._callback() # Gui stuff splitter = QSplitter(self) output_wid = QWidget(splitter) output_wid.setLayout(QVBoxLayout(output_wid)) self._history_text = QTextEdit(output_wid) self._history_text.setCurrentFont(QFont('Times', 10)) self._history_text.setFontFamily('Source Code Pro') output_wid.layout().addWidget(QLabel("History")) output_wid.layout().addWidget(self._history_text) input_wid = QWidget(splitter) input_wid.setLayout(QVBoxLayout(input_wid)) self._command = SmartPlainTextEdit(input_wid, self._send_command) input_wid.layout().addWidget(QLabel("Command")) input_wid.layout().addWidget( QLabel( "Press Enter to send the command. Press Shift + Enter to add a newline." )) input_wid.layout().addWidget(self._command) splitter.setOrientation(Qt.Vertical) splitter.addWidget(output_wid) splitter.addWidget(input_wid) splitter.setSizes([300, 100]) send_button = QPushButton(self, text="Send") clear_history_button = QPushButton(self, text="Clear History") interact_button = QPushButton(self, text="Interact") send_button.clicked.connect(self._send_command) clear_history_button.clicked.connect(self._history_text.clear) interact_button.clicked.connect( lambda: self.initialize(self.workspace.instance.img_name)) buttons = QSplitter(self) buttons.addWidget(interact_button) buttons.addWidget(clear_history_button) buttons.addWidget(send_button) buttons.setSizes([100, 200, 600]) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(splitter) self.layout().addWidget(buttons)
def _make_layout(self, widgets): qw_splitter = QSplitter(self) for widget in widgets: qw_splitter.addWidget(widget) qw_splitter.setSizes( [self.WINDOW_SIZE['width'] * 0.1, self.WINDOW_SIZE['width'] * 0.9]) # layout = QHBoxLayout() # layout.addWidget(qw_splitter) # qw = QWidget() # qw.setLayout(layout) # self.setCentralWidget(qw) # self.setWindowFlags(Qt.WindowStaysOnTopHint) # 常に手前に表示する. # MainWindowのCentral領域にWidgetを設定 self.setCentralWidget(qw_splitter)
def initUi(self): self.liberation_map = QLiberationMap(self.game) self.info_panel = QInfoPanel(self.game) hbox = QSplitter(Qt.Horizontal) hbox.addWidget(self.info_panel) hbox.addWidget(self.liberation_map) hbox.setSizes([2, 8]) vbox = QVBoxLayout() vbox.setMargin(0) vbox.addWidget(QTopPanel(self.game)) vbox.addWidget(hbox) central_widget = QWidget() central_widget.setLayout(vbox) self.setCentralWidget(central_widget)
def __init__(self, parent: QWidget = None): super().__init__(parent) self.dataWidgetL = DataframeView(self) self.dataWidgetR = DataframeView(self) self.scrollTogetherH = QCheckBox('Lock horizontal scroll', self) self.scrollTogetherV = QCheckBox('Lock vertical scroll', self) cLayout = QHBoxLayout() cLayout.addWidget(self.scrollTogetherH) cLayout.addWidget(self.scrollTogetherV) splitter = QSplitter(self) splitter.addWidget(self.dataWidgetL) splitter.addWidget(self.dataWidgetR) layout = QVBoxLayout(self) layout.addLayout(cLayout) layout.addWidget(splitter) splitter.setSizes([10, 10]) self.scrollTogetherH.toggled.connect(self.lockScrollChangedHorizontal) self.scrollTogetherV.toggled.connect(self.lockScrollChangedVertical)
def _layout(self) -> Tuple: layout = QGridLayout() layout.setContentsMargins(5, 5, 5, 5) # Gallery gallery = GalleryView(self._load_image) layout.addWidget(gallery, 0, 0) splitter = QSplitter() layout.addWidget(splitter, 1, 0) left = QWidget() splitter.addWidget(left) left_layout = QGridLayout() left_layout.setContentsMargins(0, 0, 0, 0) left.setLayout(left_layout) # Tag search left_layout.addWidget(QLabel('Search:'), 0, 0) entry = MultiTagEntry() entry.returnPressed.connect(self._search) entry.setCompleter(self.global_state.tag_completer) left_layout.addWidget(entry, 0, 1) # Shuffle toggle shuffle = QCheckBox('Shuffle?') left_layout.addWidget(shuffle, 0, 2) # Tag list taglist = TagListView() left_layout.addWidget(taglist, 1, 0, 1, 3) # Image image = ImageView() splitter.addWidget(wrap_image(image)) # Initialize equal widths (needs to be set at the end) splitter.setSizes([1000000, 1000000]) return layout, gallery, entry, shuffle, taglist, image
def initUi(self): hbox = QSplitter(Qt.Horizontal) vbox = QSplitter(Qt.Vertical) hbox.addWidget(self.ato_panel) hbox.addWidget(vbox) vbox.addWidget(self.liberation_map) vbox.addWidget(self.info_panel) # Will make the ATO sidebar as small as necessary to fit the content. In # practice this means it is sized by the hints in the panel. hbox.setSizes([1, 10000000]) vbox.setSizes([600, 100]) vbox = QVBoxLayout() vbox.setMargin(0) vbox.addWidget(QTopPanel(self.game_model, self.sim_controller)) vbox.addWidget(hbox) central_widget = QWidget() central_widget.setLayout(vbox) self.setCentralWidget(central_widget)
class CustomSplitter(QWidget): def __init__(self): QWidget.__init__(self) self.splitter = QSplitter(self) self.splitter.addWidget(QTextEdit(self)) self.splitter.addWidget(QTextEdit(self)) layout = QVBoxLayout(self) layout.addWidget(self.splitter) handle = self.splitter.handle(1) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) button = QToolButton(handle) button.setArrowType(Qt.LeftArrow) button.clicked.connect(lambda: self.handleSplitterButton(True)) layout.addWidget(button) button = QToolButton(handle) button.setArrowType(Qt.RightArrow) button.clicked.connect(lambda: self.handleSplitterButton(False)) layout.addWidget(button) handle.setLayout(layout) def handleSplitterButton(self, left=True): if not all(self.splitter.sizes()): self.splitter.setSizes([1, 1]) elif left: self.splitter.setSizes([0, 1]) else: self.splitter.setSizes([1, 0])
def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) vsplit = QSplitter(QtCore.Qt.Vertical) hsplit = QSplitter(QtCore.Qt.Horizontal) self.layout().addWidget(vsplit) self.layout().setMargin(0) self.layout().setSpacing(0) self.unity_area = UnityWidget() self.prop_area = Properties() self.timeline = Timeline() hsplit.addWidget(self.unity_area) hsplit.setCollapsible(0, False) hsplit.addWidget(self.prop_area) hsplit.setStretchFactor(0, 1) hsplit.setSizes([hsplit.width() - 400, 400]) vsplit.addWidget(hsplit) vsplit.setCollapsible(0, False) vsplit.addWidget(self.timeline) vsplit.setStretchFactor(0, 1) vsplit.setSizes([vsplit.height() - 150, 150])
def _initCentralWidget(self): centralView = QWidget() centralView.setLayout(QHBoxLayout()) centralView.layout().setSpacing(0) centralView.layout().setContentsMargins(0, 0, 0, 0) self.browser = Browser(self.document, self.subject, self.powermode) centralView.layout().addWidget(self.browser) canvas = QSplitter() canvas.setFrameStyle(QFrame.NoFrame | QFrame.Plain) canvas.setOrientation(Qt.Vertical) canvas.setRubberBand(-1) self.editor = Editor(self.subject, self.powermode) self.console = Console(self.subject) canvas.addWidget(self.editor) canvas.addWidget(self.console) centralView.layout().addWidget(canvas) canvas.setSizes([4, 4, 1, 1]) self.setCentralWidget(centralView)
class ABookDownloaderMainWindow(QMainWindow): def __init__(self, path, settings, session): QMainWindow.__init__(self) self.settings = settings self.course_tree_widget = CourseTreeWidget(path, settings, session) self.file_list_widget = FileListWidget() self.download_dir_tree_widget = DownloadDirTreeWidget(settings['download_path']) self.file_downloader = FileDownloaderWidget() self.vSplitter = QSplitter(Qt.Vertical) self.hSplitter1 = QSplitter(Qt.Horizontal) self.hSplitter2 = QSplitter(Qt.Horizontal) self.hSplitter1.addWidget(self.course_tree_widget) self.hSplitter1.addWidget(self.file_list_widget) self.hSplitter2.addWidget(self.download_dir_tree_widget) self.hSplitter2.addWidget(self.file_downloader) self.vSplitter.addWidget(self.hSplitter1) self.vSplitter.addWidget(self.hSplitter2) self.maxWidth = QGuiApplication.primaryScreen().size().width() self.maxHeight = QGuiApplication.primaryScreen().size().height() self.hSplitter1.setSizes([self.maxWidth, self.maxWidth]) self.hSplitter2.setSizes([self.maxWidth, self.maxWidth]) self.vSplitter.setSizes([self.maxHeight, self.maxHeight]) mainWidget = QWidget(self) mainLayout = QGridLayout() mainWidget.setLayout(mainLayout) mainLayout.addWidget(self.vSplitter) self.course_tree_widget.signal.clearFileListWidget.connect(self.file_list_widget.clear) self.course_tree_widget.signal.appendRowFileListWidget.connect(self.file_list_widget.appendRow) self.course_tree_widget.signal.addDownloadTask.connect(self.file_downloader.addDownloadTask) self.setCentralWidget(mainWidget) self.init_menubar() self.setWindowTitle("ABookDownloader Dev") self.showMaximized() def init_menubar(self): exitAction = QAction('Exit', self) exitAction.setShortcut('Alt+F4') exitAction.setStatusTip('Quit') exitAction.triggered.connect(self.close) aboutAction = QAction('About', self) aboutAction.setStatusTip('About') updateAction = QAction('Check Updates', self) updateAction.setStatusTip('Check Update') updateAction.triggered.connect(self.checkUpdate) aboutQtAction = QAction("About Qt", self) aboutQtAction.triggered.connect(QApplication.aboutQt) debugAction = QAction('Debug', self) debugAction.triggered.connect(self.debug) maximizeCourseWindow = QAction('Maximize Course Window', self) maximizeCourseWindow.triggered.connect(self.maximizeCourse) maximizeCourseWindow.setShortcut('Alt+D') maximizeResourceWindow = QAction('Maximize Resource Window', self) maximizeResourceWindow.triggered.connect(self.maximizeResource) maximizeResourceWindow.setShortcut('Alt+F') maximizeLocalFilesWindow = QAction('Maximize Local Files Window', self) maximizeLocalFilesWindow.triggered.connect(self.maximizeLocalFiles) maximizeLocalFilesWindow.setShortcut('Alt+C') maximizeDownloaderWindow = QAction('Maximize Downloader Window', self) maximizeDownloaderWindow.triggered.connect(self.maximizeDownloader) maximizeDownloaderWindow.setShortcut('Alt+V') resetWindow = QAction('Reset Window Layout', self) resetWindow.triggered.connect(self.resetWindow) resetWindow.setShortcut('Alt+R') self.menuBar().setNativeMenuBar(True) fileMenu = QMenu('About') fileMenu.addAction(exitAction) fileMenu.addAction(aboutQtAction) fileMenu.addAction(updateAction) fileMenu.addAction(debugAction) windowMenu = QMenu('Window') windowMenu.addAction(maximizeCourseWindow) windowMenu.addAction(maximizeResourceWindow) windowMenu.addAction(maximizeLocalFilesWindow) windowMenu.addAction(maximizeDownloaderWindow) windowMenu.addAction(resetWindow) self.menuBar().addMenu(fileMenu) self.menuBar().addMenu(windowMenu) def maximizeCourse(self): self.hSplitter1.setSizes([self.maxWidth, 0]) self.vSplitter.setSizes([self.maxHeight, 0]) def maximizeResource(self): self.hSplitter1.setSizes([0, self.maxWidth]) self.vSplitter.setSizes([self.maxHeight, 0]) def maximizeLocalFiles(self): self.hSplitter2.setSizes([self.maxWidth, 0]) self.vSplitter.setSizes([0, self.maxHeight]) def maximizeDownloader(self): self.hSplitter2.setSizes([0, self.maxWidth]) self.vSplitter.setSizes([0, self.maxHeight]) def resetWindow(self): self.hSplitter1.setSizes([self.maxWidth, self.maxWidth]) self.hSplitter2.setSizes([self.maxWidth, self.maxWidth]) self.vSplitter.setSizes([self.maxHeight, self.maxHeight]) def checkUpdate(self): checkUpdateDialog = CheckUpdateDialog(self.settings) checkUpdateDialog.exec_() def debug(self): raise SystemError('An debug error')
class MainWindow(QMainWindow): forum = Forum(id=-1, name="UNKNOWN") topics = [] forum_model = ForumModel() day_model = DayModel() def __init__(self): QMainWindow.__init__(self) self.date_view = QListView() self.forum_view = QListView() self.contentMax = 0 self.ioloop = asyncio.get_event_loop() icon = QIcon() icon.addPixmap(QPixmap(FAVICON_ICO), QIcon.Normal) self.setWindowIcon(icon) self.setWindowTitle(APP_TITLE) self.settings = QSettings('karoStudio', 'nnm-stats') self.resize(self.settings.value('main/size', QSize(640, 480))) self.move(self.settings.value('main/pos', QPoint(200, 200))) self.splitter = QSplitter() self.get_menu() self.content = QScrollArea() self.content.verticalScrollBar().valueChanged.connect( self.scrollBarChanged) self.content.verticalScrollBar().rangeChanged.connect( self.rangeChanged) self.torrents_list_view = QWidget() layout = QGridLayout() self.torrents_list_view.setLayout(layout) self.content.setWidget(self.torrents_list_view) self.splitter.addWidget(self.content) self.splitter.setSizes([160, 350]) self.setCentralWidget(self.splitter) self.timer = QTimer() self.timer.singleShot(1600, self.load_task) def get_menu(self): scroll = QScrollArea(self) self.forum_view.setStyleSheet("QListView{font: bold 12px;}") self.forum_view.clicked.connect(self.listViewClick) self.forum_view.setModel(self.forum_model) self.date_view.setStyleSheet("QListView{font: bold 12px;}") self.date_view.clicked.connect(self.listViewClick) self.date_view.setModel(self.day_model) menu_splitter = QSplitter(self) menu_splitter.setOrientation(Qt.Vertical) menu_splitter.addWidget(self.forum_view) menu_splitter.addWidget(self.date_view) scroll.setWidget(menu_splitter) scroll.setWidgetResizable(True) self.splitter.addWidget(scroll) def load_task(self): self.ioloop.run_until_complete(self.load_forums()) async def load_forums(self): tasks = [asyncio.ensure_future((get_forums("http://nnmclub.to/")))] done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED) forums = done.pop().result() for forum in forums: print(forum) self.forum_model.add(forum) def load_torrents_task(self, forum, start): self.ioloop.run_until_complete(self.load_torrents(forum, start)) async def load_torrents(self, forum, start=0): tasks = [asyncio.ensure_future((get_topics(forum, start)))] done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED) if start == 0: self.topics = done.pop().result() else: self.topics = self.topics + done.pop().result() layout = QGridLayout() self.topics.sort(key=lambda x: x.likes, reverse=True) days = {} for i, topic in enumerate(self.topics): d = topic.published.date() if d in days.keys(): days[d]['count'] += 1 days[d]['likes'] += topic.likes else: days[d] = {'count': 1, 'likes': topic.likes} # layout.addWidget(TopicView(topic), i, 0) for day in days.keys(): self.day_model.add(day, days[day]) self.torrents_list_view = QWidget() self.torrents_list_view.setLayout(layout) self.content.setWidget(self.torrents_list_view) def rangeChanged(self, vert_min, vert_max): self.contentMax = vert_max def scrollBarChanged(self, value): if value == self.contentMax: print("LOADING {}".format(self.torrents_list_view.children())) self.timer.singleShot( 1000, lambda: self.load_torrents_task(self.forum, len(self.topics))) def listViewClick(self, index): self.forum = self.forum_model.forums[index.row()] self.topics = [] self.setWindowTitle("{} / {}".format(APP_TITLE, self.forum.name)) self.day_model.clear() # self.timer.singleShot(1000, lambda: self.load_torrents_task(self.forum, 0)) self.timer.timeout.connect( lambda: self.load_torrents_task(self.forum, len(self.topics))) self.timer.start(8000) def closeEvent(self, event): self.settings.setValue('main/size', self.size()) self.settings.setValue('main/pos', self.pos()) self.ioloop.close()
def __init__(self): super().__init__(None, Qt.WindowStaysOnTopHint) self.stdout = StreamOutput() self.stderr = StreamError() self.exec = StreamInput() self.frame = QFrame() self.setCentralWidget(self.frame) self.screen = QDesktopWidget().screenGeometry() self.setGeometry(self.screen) self.grid = QGridLayout() self.frame.setLayout(self.grid) self.map = SimulatedFieldMap() # -- setting splitters splitter_bottom = QSplitter(Qt.Horizontal) # STDIN, STDOUT splitter_main = QSplitter(Qt.Vertical) # Map ((STDIN,STDOUT), STDERR) # -------------- # -- top -- frame = QFrame() grid = QGridLayout() frame.setLayout(grid) splitter_main.addWidget(frame) grid.addWidget(self.map, 0, 0, 10, 1) slider_zoom = QSlider(Qt.Horizontal) slider_zoom.setMinimum(100) slider_zoom.setMaximum(1000) grid.addWidget(slider_zoom, 1, 1) sp = QSizePolicy() sp.setVerticalPolicy(QSizePolicy.Maximum) label = QLabel("Zoom") label.setSizePolicy(sp) grid.addWidget(label, 0, 1) zoom_label = QLabel("1") zoom_label.setSizePolicy(sp) slider_zoom.valueChanged.connect(lambda n: zoom_label.setText(str(n / 100))) grid.addWidget(zoom_label, 2, 1, Qt.AlignHCenter) splitter_main.addWidget(frame) # ------ # -- bottom left -- box = QVBoxLayout() frame = QFrame() frame.setLayout(box) box.addWidget(QLabel('Exec')) box.addWidget(self.exec) splitter_bottom.addWidget(frame) # ------- # -- bottom middle -- box = QVBoxLayout() frame = QFrame() frame.setLayout(box) splitter_bottom.addWidget(frame) box.addWidget(QLabel('Output')) box.addWidget(self.stdout) # ------- # -- bottom right -- box = QVBoxLayout() frame = QFrame() frame.setLayout(box) splitter_bottom.addWidget(frame) box.addWidget(QLabel('Error')) box.addWidget(self.stderr) # ------- splitter_main.addWidget(splitter_bottom) self.grid.addWidget(splitter_main, 0, 0) splitter_main.setSizes((self.screen.height() * 0.6, self.screen.height() * 0.4)) splitter_bottom.setSizes((self.map.width / 2, self.map.width / 2, self.stderr.sizeHint().width()))
# --------------------------- # Splitterの左右の比率を変える # --------------------------- import sys from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit app = QApplication(sys.argv) qw_text_edit_left = QTextEdit() qw_text_edit_left.append('left') qw_text_edit_right = QTextEdit() qw_text_edit_right.append('right') qw_splitter = QSplitter() # Orientationの初期値は水平 qw_splitter.addWidget(qw_text_edit_left) qw_splitter.addWidget(qw_text_edit_right) qw_splitter_size = qw_splitter.size() # Splitterのサイズを取得する qw_splitter_size_width = qw_splitter_size.width() # Splitterの横サイズを取得する qw_splitter.setSizes( [qw_splitter_size_width * 0.1, qw_splitter_size_width * 0.9]) # Splitterの横の比率を1:9に変更する print(qw_splitter.size()) # Splitter全体のサイズ print(qw_splitter.sizes()) # Splitterの子widgetごとのサイズ qw_splitter.show() sys.exit(app.exec_())
class Player(QWidget): media_loaded = Signal(str) stopped = Signal(str) playlist_size_changed = Signal(int) handle_double_click = Slot() def __init__(self, parent=None): super(Player, self).__init__(parent) self.duration = 0 self.volume = 50 self.player = QMediaPlayer() self.playlist = Playlist(self) self.videoWidget = VideoWidget() self.next_url = QUrl() self.context_menu = QMenu(self) self.display_splitter = QSplitter(Qt.Horizontal) self.repeat_control = RepeatControl(parent=self) self.repeat_control.get_player_position = self.player.position self.repeat_control.set_position_to_player = self.player.setPosition self.player.positionChanged.connect(self.repeat_control.set_pos) self.setAcceptDrops(True) std_icon = self.style().standardIcon self.play_button = create_flat_button(std_icon(QStyle.SP_MediaPlay)) self.stopButton = create_flat_button(std_icon(QStyle.SP_MediaStop), '') self.backwardButton = create_flat_button( std_icon(QStyle.SP_MediaSkipBackward), '') self.forwardButton = create_flat_button( std_icon(QStyle.SP_MediaSkipForward), '') self.order_list = self.repeat_control.menu() self.order_list.setFixedWidth(115) self.playback_rate_menu = QComboBox() self.playback_rate_menu.addItems( ('0.5x', '0.75x', '0.9x', '1.0x', '1.1x', '1.25x', '1.5x')) self.playback_rate_menu.setCurrentText('1.0x') self.muteButton = create_flat_button( std_icon(QStyle.SP_MediaVolume if not self.player.isMuted() else QStyle.SP_MediaVolumeMuted)) self.volumeBar = QSlider(Qt.Horizontal) self.volumeBar.setRange(0, 100) self.volumeBar.setValue(self.volume) self.labelVolume = QLabel(str(self.volume)) self.labelVolume.setMinimumWidth(24) self.statusInfoLabel = QLabel() self.seekBar = QSlider(Qt.Horizontal) self.seekBar.setRange(0, self.player.duration() / 1000) self.labelTotalTime = QLabel('00:00') self.labelCurrentTime = QLabel('00:00') self.create_layout() self.create_connections() self.player.setVideoOutput(self.videoWidget) self.videoWidget.show() def create_layout(self): seekBarLayout = QHBoxLayout() seekBarLayout.addWidget(self.labelCurrentTime) seekBarLayout.addWidget(self.seekBar) seekBarLayout.addWidget(self.labelTotalTime) controlWithoutSeekBarLayout = QHBoxLayout() controlWithoutSeekBarLayout.setSpacing(1) controlWithoutSeekBarLayout.addWidget(self.play_button) controlWithoutSeekBarLayout.addWidget(self.stopButton) controlWithoutSeekBarLayout.addWidget(self.backwardButton) controlWithoutSeekBarLayout.addWidget(self.forwardButton) controlWithoutSeekBarLayout.addWidget(self.order_list) controlWithoutSeekBarLayout.addWidget(self.playback_rate_menu) controlWithoutSeekBarLayout.addStretch(stretch=2) controlWithoutSeekBarLayout.addWidget(self.muteButton) controlWithoutSeekBarLayout.addWidget(self.volumeBar) controlWithoutSeekBarLayout.addWidget(self.labelVolume, alignment=Qt.AlignRight) controlLayout = QVBoxLayout() controlLayout.addLayout(seekBarLayout) controlLayout.addLayout(controlWithoutSeekBarLayout) self.display_splitter.setOpaqueResize(False) self.display_splitter.addWidget(self.videoWidget) self.display_splitter.addWidget(self.playlist.widget) self.display_splitter.setSizes([300, 200]) layout = QVBoxLayout() layout.setContentsMargins(11, 0, 11, 0) layout.addWidget(self.display_splitter, 1) layout.addLayout(controlLayout) layout.addWidget(self.repeat_control.ab_repeat_widget) layout.addWidget(self.statusInfoLabel) self.setLayout(layout) def create_connections(self): self.play_button.clicked.connect(self.optimal_play) self.stopButton.clicked.connect(self.stop) self.backwardButton.clicked.connect(self.skip_backward) self.forwardButton.clicked.connect(self.skip_forward) self.muteButton.clicked.connect(self.toggleMute) self.playback_rate_menu.currentTextChanged.connect( self.set_playback_rate) self.player.stateChanged.connect(self.playerStateChanged) self.player.mediaStatusChanged.connect(self.mediaStatusChanged) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.error.connect(self.handleError) self.volumeBar.sliderMoved.connect(self.setVolume) self.volumeBar.sliderReleased.connect(self.setVolume) self.volumeBar.valueChanged.connect(self.volumeChanged) self.seekBar.sliderMoved.connect(self.seek) self.seekBar.sliderReleased.connect(self.seekBarClicked) self.repeat_control.pos_exceeded.connect(self.seek) self.player.currentMediaChanged.connect(self.repeat_control.reset) self.playlist.double_clicked.connect(self.load_and_play) self.videoWidget.double_clicked.connect(self.no_future) def contextMenuEvent(self, event): self.context_menu.exec_(event.globalPos()) def read_settings(self): settings = QSettings() settings.beginGroup('player') self.order_list.setCurrentIndex(settings.value('order_list', 0)) self.display_splitter.restoreState( QByteArray(settings.value('splitter_sizes'))) settings.endGroup() self.playlist.read_settings() def no_future(self): self.display_splitter.moveSplitter(0, 1) def autoplay(self): """メディアを読み込み、再生する。 order_listに応じて、次に何を再生するかを決める。 """ i = self.order_list.currentIndex() if i == 1: # self.repeat_track() return elif i == 2: self.repeat_all() else: self.next_track() self.play() def optimal_play(self): if self.player.state() == QMediaPlayer.StoppedState: self.load_and_play() else: self.play() def load_and_play(self): self.load(self.playlist.get_new_one()) self.play() def load(self, file_url: QUrl): if file_url is None: return None if file_url.isValid(): c = QMediaContent(file_url) self.player.setMedia(c) self.media_loaded.emit(self.playlist.current_title()) self.enableInterface() def play(self): if self.player.state() == QMediaPlayer.PlayingState: self.player.pause() return if self.player.mediaStatus() == QMediaPlayer.LoadingMedia or\ self.player.mediaStatus() == QMediaPlayer.StalledMedia: QTimer.singleShot(600, self.player.play) self.player.play() self.playlist.update_listview() def stop(self): if not self.player.state() == QMediaPlayer.StoppedState: self.seek(0) self.player.stop() self.setStatusInfo('Stopped') self.stopped.emit('') def playerStateChanged(self, state): if state == QMediaPlayer.PlayingState: self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def durationChanged(self, duration): self.repeat_control.set_duration(duration) duration /= 1000 self.duration = duration totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60, (duration % 60), (duration * 1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' totalTimeStr = totalTime.toString(format) self.labelTotalTime.setText(totalTimeStr) self.seekBar.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 self.updateCurrentTime(progress) self.seekBar.setValue(progress) def updateCurrentTime(self, currentInfo): if currentInfo: currentTime = QTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60, currentInfo % 60, (currentInfo * 1000) % 1000) format = 'hh:mm:ss' if self.duration > 3600 else 'mm:ss' currentTimeStr = currentTime.toString(format) else: currentTimeStr = '00:00' self.labelCurrentTime.setText(currentTimeStr) def repeat_track(self): QTimer.singleShot(50, self.play) def repeat_all(self): if self.playlist.count() - 1 == self.playlist.current_row(): url = self.playlist.first() self.load(url) else: self.next_track() def setVolume(self): self.player.setVolume(self.volumeBar.sliderPosition() * 2) def volumeChanged(self): self.labelVolume.setText(str(self.volumeBar.sliderPosition())) self.volume = self.volumeBar.sliderPosition() def seekBarClicked(self): self.seek(self.seekBar.sliderPosition()) def seek(self, seconds): self.player.setPosition(seconds * 1000) def set_playback_rate(self, rate_text): self.player.setPlaybackRate(float(rate_text[:-1])) def toggleMute(self): if self.player.isMuted(): self.player.setMuted(False) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume)) else: self.player.setMuted(True) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolumeMuted)) def disableInterface(self): self.play_button.setEnabled(False) self.stopButton.setEnabled(False) self.backwardButton.setEnabled(False) self.forwardButton.setEnabled(False) self.labelCurrentTime.setText('00:00') self.labelTotalTime.setText('00:00') def enableInterface(self): self.play_button.setEnabled(True) self.stopButton.setEnabled(True) self.backwardButton.setEnabled(True) self.forwardButton.setEnabled(True) def mediaStatusChanged(self, status): if status == QMediaPlayer.LoadingMedia: self.setStatusInfo('Loading...') elif status == QMediaPlayer.BufferingMedia: self.setStatusInfo('Buffering') elif status == QMediaPlayer.EndOfMedia: # self.player.stop() self.autoplay() elif status == QMediaPlayer.InvalidMedia: self.handleError() #TODO: Step Forward を割り当てる def clearStatusInfo(self): self.statusInfoLabel.setText("") def handleError(self): self.disableInterface() self.setStatusInfo('Error: ' + self.player.errorString()) def setStatusInfo(self, message, seconds=5): if not message == '': self.statusInfoLabel.setText(message) QTimer.singleShot(seconds * 1000, self.clearStatusInfo) def next_track(self): url = self.playlist.next() if url is None: return None else: self.load(url) def previous_track(self): url = self.playlist.previous() if url is None: return None else: self.load(url) def skip_forward(self): self.next_track() self.play() def skip_backward(self): if self.seekBar.sliderPosition() > 2: self.seek(0) else: self.previous_track() self.play() def forward(self, seconds): currentPosition = self.seekBar.sliderPosition() if currentPosition + seconds < self.duration: self.seek(currentPosition + seconds) else: self.seek(self.duration - 1) def backward(self, seconds): self.forward(-seconds) def forward_short(self): self.forward(SeekStep.SHORT) def forward_medium(self): self.forward(SeekStep.MEDIUM) def forward_long(self): self.forward(SeekStep.LONG) def forward_verylong(self): self.forward(SeekStep.VERYLONG) def backward_short(self): self.backward(SeekStep.SHORT) def backward_medium(self): self.backward(SeekStep.MEDIUM) def backward_long(self): self.backward(SeekStep.LONG) def backward_verylong(self): self.backward(SeekStep.VERYLONG) def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() def dragMoveEvent(self, event): if event.mimeData().hasUrls(): event.accept() def dropEvent(self, event): if event.mimeData().hasUrls(): urls = event.mimeData().urls() self.load(urls[0]) # self.stop() self.play()
class ManageRelationshipsDialog(AddOrManageRelationshipsDialog): """A dialog to query user's preferences for managing relationships. """ def __init__(self, parent, db_mngr, *db_maps, relationship_class_key=None): """ Args: parent (SpineDBEditor): data store widget db_mngr (SpineDBManager): the manager to do the removal *db_maps: DiffDatabaseMapping instances relationship_class_key (str, optional): relationships class name, object_class name list string. """ super().__init__(parent, db_mngr, *db_maps) self.setWindowTitle("Manage relationships") self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.remove_rows_button.setToolButtonStyle(Qt.ToolButtonIconOnly) self.remove_rows_button.setToolTip( "<p>Remove selected relationships.</p>") self.remove_rows_button.setIconSize(QSize(24, 24)) self.db_map = db_maps[0] self.relationship_ids = dict() layout = self.header_widget.layout() self.db_combo_box = QComboBox(self) layout.addSpacing(32) layout.addWidget(QLabel("Database")) layout.addWidget(self.db_combo_box) self.splitter = QSplitter(self) self.add_button = QToolButton(self) self.add_button.setToolTip( "<p>Add relationships by combining selected available objects.</p>" ) self.add_button.setIcon(QIcon(":/icons/menu_icons/cubes_plus.svg")) self.add_button.setIconSize(QSize(24, 24)) self.add_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.add_button.setText(">>") label_available = QLabel("Available objects") label_existing = QLabel("Existing relationships") self.layout().addWidget(self.header_widget, 0, 0, 1, 4, Qt.AlignHCenter) self.layout().addWidget(label_available, 1, 0) self.layout().addWidget(label_existing, 1, 2) self.layout().addWidget(self.splitter, 2, 0) self.layout().addWidget(self.add_button, 2, 1) self.layout().addWidget(self.table_view, 2, 2) self.layout().addWidget(self.remove_rows_button, 2, 3) self.layout().addWidget(self.button_box, 3, 0, -1, -1) self.hidable_widgets = [ self.add_button, label_available, label_existing, self.table_view, self.remove_rows_button, ] for widget in self.hidable_widgets: widget.hide() self.existing_items_model = MinimalTableModel(self, lazy=False) self.new_items_model = MinimalTableModel(self, lazy=False) self.model.sub_models = [ self.new_items_model, self.existing_items_model ] self.db_combo_box.addItems([db_map.codename for db_map in db_maps]) self.reset_relationship_class_combo_box(db_maps[0].codename, relationship_class_key) self.connect_signals() def make_model(self): return CompoundTableModel(self) def splitter_widgets(self): return [self.splitter.widget(i) for i in range(self.splitter.count())] def connect_signals(self): """Connect signals to slots.""" super().connect_signals() self.db_combo_box.currentTextChanged.connect( self.reset_relationship_class_combo_box) self.add_button.clicked.connect(self.add_relationships) @Slot(str) def reset_relationship_class_combo_box(self, database, relationship_class_key=None): self.db_map = self.keyed_db_maps[database] self.relationship_class_keys = list( self.db_map_rel_cls_lookup[self.db_map]) self.rel_cls_combo_box.addItems( [f"{name}" for name, _ in self.relationship_class_keys]) try: current_index = self.relationship_class_keys.index( relationship_class_key) self.reset_model(current_index) self._handle_model_reset() except ValueError: current_index = -1 self.rel_cls_combo_box.setCurrentIndex(current_index) @Slot(bool) def add_relationships(self, checked=True): object_names = [[item.text(0) for item in wg.selectedItems()] for wg in self.splitter_widgets()] candidate = list(product(*object_names)) existing = self.new_items_model._main_data + self.existing_items_model._main_data to_add = list(set(candidate) - set(existing)) count = len(to_add) self.new_items_model.insertRows(0, count) self.new_items_model._main_data[0:count] = to_add self.model.refresh() @Slot(int) def reset_model(self, index): """Setup model according to current relationship_class selected in combobox. """ self.class_name, self.object_class_name_list = self.relationship_class_keys[ index] object_class_name_list = self.object_class_name_list.split(",") self.model.set_horizontal_header_labels(object_class_name_list) self.existing_items_model.set_horizontal_header_labels( object_class_name_list) self.new_items_model.set_horizontal_header_labels( object_class_name_list) self.relationship_ids.clear() for db_map in self.db_maps: relationship_classes = self.db_map_rel_cls_lookup[db_map] rel_cls = relationship_classes.get( (self.class_name, self.object_class_name_list), None) if rel_cls is None: continue for relationship in self.db_mngr.get_items_by_field( db_map, "relationship", "class_id", rel_cls["id"]): key = tuple(relationship["object_name_list"].split(",")) self.relationship_ids[key] = relationship["id"] existing_items = list(self.relationship_ids) self.existing_items_model.reset_model(existing_items) self.model.refresh() self.model.modelReset.emit() for wg in self.splitter_widgets(): wg.deleteLater() for name in object_class_name_list: tree_widget = QTreeWidget(self) tree_widget.setSelectionMode(QAbstractItemView.ExtendedSelection) tree_widget.setColumnCount(1) tree_widget.setIndentation(0) header_item = QTreeWidgetItem([name]) header_item.setTextAlignment(0, Qt.AlignHCenter) tree_widget.setHeaderItem(header_item) objects = self.db_mngr.get_items_by_field(self.db_map, "object", "class_name", name) items = [QTreeWidgetItem([obj["name"]]) for obj in objects] tree_widget.addTopLevelItems(items) tree_widget.resizeColumnToContents(0) self.splitter.addWidget(tree_widget) sizes = [wg.columnWidth(0) for wg in self.splitter_widgets()] self.splitter.setSizes(sizes) for widget in self.hidable_widgets: widget.show() def resize_window_to_columns(self, height=None): table_view_width = (self.table_view.frameWidth() * 2 + self.table_view.verticalHeader().width() + self.table_view.horizontalHeader().length()) self.table_view.setMinimumWidth(table_view_width) self.table_view.setMinimumHeight( self.table_view.verticalHeader().defaultSectionSize() * 16) margins = self.layout().contentsMargins() if height is None: height = self.sizeHint().height() self.resize( margins.left() + margins.right() + table_view_width + self.add_button.width() + self.splitter.width(), height, ) @Slot() def accept(self): """Collect info from dialog and try to add items.""" keys_to_remove = set(self.relationship_ids) - set( self.existing_items_model._main_data) to_remove = [self.relationship_ids[key] for key in keys_to_remove] self.db_mngr.remove_items({self.db_map: {"relationship": to_remove}}) to_add = [[self.class_name, object_name_list] for object_name_list in self.new_items_model._main_data] self.db_mngr.import_data({self.db_map: { "relationships": to_add }}, command_text="Add relationships") super().accept()
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setWindowTitle("Lab-Data-Converter") self.create_actions() self.setup_menu() self.settings = QSettings("UCB", "Lab-Data-Converter") self.data_sets = {} self.output_widget = OutputWidget(self) self.data_table = DataTabel(self) self.splitter1 = QSplitter() self.splitter1.setOrientation(Qt.Vertical) self.splitter1.addWidget(self.data_table) self.splitter1.addWidget(self.output_widget) self.splitter1.setSizes([200, 100]) #self.splitter1.setStretchFactor(0, 8) #self.splitter1.setStretchFactor(1, 4) self.setCentralWidget(self.splitter1) QDir.setCurrent(QStandardPaths.standardLocations( QStandardPaths.DocumentsLocation)[-1]) if (self.settings.value("work_dir")): try: QDir.setCurrent(self.settings.value("work_dir")) except: pass def create_actions(self): self.open_act = QAction("Open", self) self.open_act.setShortcuts(QKeySequence.Open) self.open_act.setStatusTip("Open one or multiple files"); self.open_act.triggered.connect(self.open_file) self.close_act = QAction("Close", self) self.close_act.setShortcuts(QKeySequence.Close) self.close_act.setStatusTip("Close selected files"); self.close_act.triggered.connect(self.close_file) def setup_menu(self): self.menuBar().addAction(self.open_act) self.menuBar().addAction(self.close_act) def open_file(self): files = QFileDialog.getOpenFileNames(self, "Open file", QDir.currentPath(), "Files (*.csv *.txt)") for file in files[0]: if (file not in self.data_sets): data = read_file(file) if (data is not None): self.data_sets[file] = data self.settings.setValue("work_dir", QFileInfo(file).absolutePath()) self.output_widget.add_to_list(file) else: QMessageBox.critical(self, "Main window", "File " + file[0] + " has an unknown format!") self.data_table.initialize(self.data_sets) def close_file(self): files_to_close = self.output_widget.files_selected() for file_to_close in files_to_close: self.data_sets.pop(file_to_close, None) if (not self.output_widget.remove_from_list(file_to_close)): raise Exception("{} doesn't exist in checkboxes!".format(file_to_close)) self.data_table.initialize(self.data_sets) def on_convert(self): export_dialog = QFileDialog() outfile = export_dialog.getSaveFileName(self, "Export to csv file", QDir.currentPath() + "/preview.csv", "csv (*.csv)") info = self.output_widget.output_info() min_rows = min([data.shape[0] for data in self.data_sets.values()]) if (info["first_line"] < 1 or info["first_line"] > min_rows or info["last_line"] < 1 or info["last_line"] > min_rows or info["first_line"] > info["last_line"]): QMessageBox.critical(self, "Row range", "Invalid row range!") else: self.write_file(outfile[0], info["files"], info["first_line"], info["last_line"]) def write_file(self, out_file, in_files, first, last): res = pd.DataFrame() for in_file in in_files: data = self.data_sets[in_file] base_name = QFileInfo(in_file).baseName() if base_name in res: QMessageBox.warning(self, "Export", "Identical file name! Use full path instead.") base_name = in_file for col in data.columns: res[base_name+":"+col] = data[col][first-1:last] res.to_csv(out_file, index=False) QMessageBox.information(self, "Export", "Selected rows have been written to {}.".format(out_file))
class DebugView(QWidget, View): class DebugViewHistoryEntry(HistoryEntry): def __init__(self, memory_addr, address, is_raw): HistoryEntry.__init__(self) self.memory_addr = memory_addr self.address = address self.is_raw = is_raw def __repr__(self): if self.is_raw: return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr) return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr) def __init__(self, parent, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = binjaplug.get_state(data) memory_view = self.debug_state.memory_view self.debug_state.ui.debug_view = self QWidget.__init__(self, parent) self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state) View.__init__(self) self.setupView(self) self.current_offset = 0 self.splitter = QSplitter(Qt.Orientation.Horizontal, self) frame = ViewFrame.viewFrameForWidget(self) self.memory_editor = LinearView(memory_view, frame) self.binary_editor = DisassemblyContainer(frame, data, frame) self.binary_text = TokenizedTextView(self, memory_view) self.is_raw_disassembly = False self.raw_address = 0 self.is_navigating_history = False self.memory_history_addr = 0 # TODO: Handle these and change views accordingly # Currently they are just disabled as the DisassemblyContainer gets confused # about where to go and just shows a bad view self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction()) self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction()) self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction()) self.memory_editor.actionHandler().bindAction("View in Types View", UIAction()) small_font = QApplication.font() small_font.setPointSize(11) bv_layout = QVBoxLayout() bv_layout.setSpacing(0) bv_layout.setContentsMargins(0, 0, 0, 0) bv_label = QLabel("Loaded File") bv_label.setFont(small_font) bv_layout.addWidget(bv_label) bv_layout.addWidget(self.binary_editor) self.bv_widget = QWidget() self.bv_widget.setLayout(bv_layout) disasm_layout = QVBoxLayout() disasm_layout.setSpacing(0) disasm_layout.setContentsMargins(0, 0, 0, 0) disasm_label = QLabel("Raw Disassembly at PC") disasm_label.setFont(small_font) disasm_layout.addWidget(disasm_label) disasm_layout.addWidget(self.binary_text) self.disasm_widget = QWidget() self.disasm_widget.setLayout(disasm_layout) memory_layout = QVBoxLayout() memory_layout.setSpacing(0) memory_layout.setContentsMargins(0, 0, 0, 0) memory_label = QLabel("Debugged Process") memory_label.setFont(small_font) memory_layout.addWidget(memory_label) memory_layout.addWidget(self.memory_editor) self.memory_widget = QWidget() self.memory_widget.setLayout(memory_layout) self.splitter.addWidget(self.bv_widget) self.splitter.addWidget(self.memory_widget) # Equally sized self.splitter.setSizes([0x7fffffff, 0x7fffffff]) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.controls) layout.addWidget(self.splitter, 100) self.setLayout(layout) self.needs_update = True self.update_timer = QTimer(self) self.update_timer.setInterval(200) self.update_timer.setSingleShot(False) self.update_timer.timeout.connect(lambda: self.updateTimerEvent()) self.add_scripting_ref() def add_scripting_ref(self): # Hack: The interpreter is just a thread, so look through all threads # and assign our state to the interpreter's locals for thread in threading.enumerate(): if type(thread) == PythonScriptingInstance.InterpreterThread: thread.locals["dbg"] = self.debug_state def getData(self): return self.bv def getFont(self): return binaryninjaui.getMonospaceFont(self) def getCurrentOffset(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentOffset() return self.raw_address def getSelectionOffsets(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getSelectionOffsets() return (self.raw_address, self.raw_address) def getCurrentFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentFunction() return None def getCurrentBasicBlock(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentBasicBlock() return None def getCurrentArchitecture(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentArchitecture() return None def getCurrentLowLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction() return None def getCurrentMediumLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction() return None def getHistoryEntry(self): if self.is_navigating_history: return None memory_addr = self.memory_editor.getCurrentOffset() if memory_addr != self.memory_history_addr: self.memory_history_addr = memory_addr if self.is_raw_disassembly and self.debug_state.connected: rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address) return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True) else: address = self.binary_editor.getDisassembly().getCurrentOffset() return DebugView.DebugViewHistoryEntry(memory_addr, address, False) def navigateToHistoryEntry(self, entry): self.is_navigating_history = True if hasattr(entry, 'is_raw'): self.memory_editor.navigate(entry.memory_addr) if entry.is_raw: if self.debug_state.connected: address = self.debug_state.modules.relative_addr_to_absolute(entry.address) self.navigate_raw(address) else: self.navigate_live(entry.address) View.navigateToHistoryEntry(self, entry) self.is_navigating_history = False def navigate(self, addr): # If we're not connected we cannot even check if the address is remote if not self.debug_state.connected: return self.navigate_live(addr) if self.debug_state.memory_view.is_local_addr(addr): local_addr = self.debug_state.memory_view.remote_addr_to_local(addr) if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0: return self.navigate_live(local_addr) # This runs into conflicts if some other address space is mapped over # where the local BV is currently loaded, but this is was less likely # than the user navigating to a function from the UI if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0: return self.navigate_live(addr) return self.navigate_raw(addr) def navigate_live(self, addr): self.show_raw_disassembly(False) return self.binary_editor.getDisassembly().navigate(addr) def navigate_raw(self, addr): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return False self.raw_address = addr self.show_raw_disassembly(True) self.load_raw_disassembly(addr) return True def notifyMemoryChanged(self): self.needs_update = True def updateTimerEvent(self): if self.needs_update: self.needs_update = False # Refresh the editor if not self.debug_state.connected: self.memory_editor.navigate(0) return # self.memory_editor.navigate(self.debug_state.stack_pointer) def showEvent(self, event): if not event.spontaneous(): self.update_timer.start() self.add_scripting_ref() def hideEvent(self, event): if not event.spontaneous(): self.update_timer.stop() def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def load_raw_disassembly(self, start_ip): # Read a few instructions from rip and disassemble them inst_count = 50 arch_dis = self.debug_state.remote_arch rip = self.debug_state.ip # Assume the worst, just in case read_length = arch_dis.max_instr_length * inst_count data = self.debug_state.memory_view.read(start_ip, read_length) lines = [] # Append header line tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")] contents = DisassemblyTextLine(tokens, start_ip) if (major == 2): line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents) else: line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, 0, contents) lines.append(line) total_read = 0 for i in range(inst_count): line_addr = start_ip + total_read (insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr) if insn_tokens is None: insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")] length = arch_dis.instr_alignment if length == 0: length = 1 tokens = [] color = HighlightStandardColor.NoHighlightColor if line_addr == rip: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint & pc tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5)) color = HighlightStandardColor.RedHighlightColor else: # PC tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> ")) color = HighlightStandardColor.BlueHighlightColor else: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5)) color = HighlightStandardColor.RedHighlightColor else: # Regular line tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) # Address tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr)) tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) tokens.extend(insn_tokens) # Convert to linear disassembly line contents = DisassemblyTextLine(tokens, line_addr, color=color) if (major == 2): line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents) else: line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, 0, contents) lines.append(line) total_read += length # terrible workaround for libshiboken conversion issue for line in lines: # line is LinearDisassemblyLine last_tok = line.contents.tokens[-1] #if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue #if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0") if last_tok.size != 8: continue #print('fixing: %s' % line) last_tok.value &= 0x7FFFFFFFFFFFFFFF self.binary_text.setLines(lines) def show_raw_disassembly(self, raw): if raw != self.is_raw_disassembly: self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget) self.is_raw_disassembly = raw def refresh_raw_disassembly(self): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return if self.is_raw_disassembly: self.load_raw_disassembly(self.getCurrentOffset())
class DebugView(QWidget, View): def __init__(self, parent, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = binjaplug.get_state(data) memory_view = self.debug_state.memory_view self.debug_state.ui.debug_view = self QWidget.__init__(self, parent) self.controls = ControlsWidget.DebugControlsWidget( self, "Controls", data, self.debug_state) View.__init__(self) self.setupView(self) self.current_offset = 0 self.splitter = QSplitter(Qt.Orientation.Horizontal, self) frame = ViewFrame.viewFrameForWidget(self) self.memory_editor = LinearView(memory_view, frame) self.binary_editor = DisassemblyContainer(frame, data, frame) self.binary_text = TokenizedTextView(self, memory_view) self.is_raw_disassembly = False # TODO: Handle these and change views accordingly # Currently they are just disabled as the DisassemblyContainer gets confused # about where to go and just shows a bad view self.binary_editor.getDisassembly().actionHandler().bindAction( "View in Hex Editor", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction( "View in Linear Disassembly", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction( "View in Types View", UIAction()) self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction()) self.memory_editor.actionHandler().bindAction( "View in Disassembly Graph", UIAction()) self.memory_editor.actionHandler().bindAction("View in Types View", UIAction()) small_font = QApplication.font() small_font.setPointSize(11) bv_layout = QVBoxLayout() bv_layout.setSpacing(0) bv_layout.setContentsMargins(0, 0, 0, 0) bv_label = QLabel("Loaded File") bv_label.setFont(small_font) bv_layout.addWidget(bv_label) bv_layout.addWidget(self.binary_editor) self.bv_widget = QWidget() self.bv_widget.setLayout(bv_layout) disasm_layout = QVBoxLayout() disasm_layout.setSpacing(0) disasm_layout.setContentsMargins(0, 0, 0, 0) disasm_label = QLabel("Raw Disassembly at PC") disasm_label.setFont(small_font) disasm_layout.addWidget(disasm_label) disasm_layout.addWidget(self.binary_text) self.disasm_widget = QWidget() self.disasm_widget.setLayout(disasm_layout) memory_layout = QVBoxLayout() memory_layout.setSpacing(0) memory_layout.setContentsMargins(0, 0, 0, 0) memory_label = QLabel("Debugged Process") memory_label.setFont(small_font) memory_layout.addWidget(memory_label) memory_layout.addWidget(self.memory_editor) self.memory_widget = QWidget() self.memory_widget.setLayout(memory_layout) self.splitter.addWidget(self.bv_widget) self.splitter.addWidget(self.memory_widget) # Equally sized self.splitter.setSizes([0x7fffffff, 0x7fffffff]) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.controls) layout.addWidget(self.splitter, 100) self.setLayout(layout) self.needs_update = True self.update_timer = QTimer(self) self.update_timer.setInterval(200) self.update_timer.setSingleShot(False) self.update_timer.timeout.connect(lambda: self.updateTimerEvent()) self.add_scripting_ref() def add_scripting_ref(self): # Hack: The interpreter is just a thread, so look through all threads # and assign our state to the interpreter's locals for thread in threading.enumerate(): if type(thread) == PythonScriptingInstance.InterpreterThread: thread.locals["dbg"] = self.debug_state def getData(self): return self.bv def getCurrentOffset(self): return self.binary_editor.getDisassembly().getCurrentOffset() def getFont(self): return binaryninjaui.getMonospaceFont(self) def navigate(self, addr): return self.binary_editor.getDisassembly().navigate(addr) def notifyMemoryChanged(self): self.needs_update = True def updateTimerEvent(self): if self.needs_update: self.needs_update = False # Refresh the editor if not self.debug_state.connected: self.memory_editor.navigate(0) return self.memory_editor.navigate(self.debug_state.stack_pointer) def showEvent(self, event): if not event.spontaneous(): self.update_timer.start() self.add_scripting_ref() def hideEvent(self, event): if not event.spontaneous(): self.update_timer.stop() def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def setRawDisassembly(self, raw=False, lines=[]): if raw != self.is_raw_disassembly: self.splitter.replaceWidget( 0, self.disasm_widget if raw else self.bv_widget) self.is_raw_disassembly = raw # terrible workaround for libshiboken conversion issue for line in lines: # line is LinearDisassemblyLine last_tok = line.contents.tokens[-1] #if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue #if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0") if last_tok.size != 8: continue #print('fixing: %s' % line) last_tok.value &= 0x7FFFFFFFFFFFFFFF self.binary_text.setLines(lines)
class MainWidget(QWidget): def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) logger.add(self.log) self.mainlayout = QVBoxLayout() self.setLayout(self.mainlayout) self.splitter = QSplitter(Qt.Vertical) self.stack = QStackedWidget() self.splitter.addWidget(self.stack) # mod list widget self.modlistwidget = QWidget() self.modlistlayout = QVBoxLayout() self.modlistlayout.setContentsMargins(0, 0, 0, 0) self.modlistwidget.setLayout(self.modlistlayout) self.stack.addWidget(self.modlistwidget) # search bar self.searchbar = QLineEdit() self.searchbar.setPlaceholderText('Search...') self.modlistlayout.addWidget(self.searchbar) # mod list self.modlist = ModList(self, model) self.modlistlayout.addWidget(self.modlist) self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e)) # welcome message welcomelayout = QVBoxLayout() welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomewidget = QWidget() welcomewidget.setLayout(welcomelayout) welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent # type: ignore welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent # type: ignore welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent # type: ignore welcomewidget.dropEvent = self.modlist.dropEvent # type: ignore welcomewidget.setAcceptDrops(True) icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico'))) iconpixmap = icon.pixmap(32, 32) icon = QLabel() icon.setPixmap(iconpixmap) icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) icon.setContentsMargins(4, 4, 4, 4) welcomelayout.addWidget(icon) welcome = QLabel('''<p><font> No mod installed yet. Drag a mod into this area to get started! </font></p>''') welcome.setAttribute(Qt.WA_TransparentForMouseEvents) welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomelayout.addWidget(welcome) self.stack.addWidget(welcomewidget) # output log self.output = QTextEdit(self) self.output.setTextInteractionFlags(Qt.NoTextInteraction) self.output.setReadOnly(True) self.output.setContextMenuPolicy(Qt.NoContextMenu) self.output.setPlaceholderText('Program output...') self.splitter.addWidget(self.output) # TODO: enhancement: add a launch game icon # TODO: enhancement: show indicator if scripts have to be merged self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 0) self.mainlayout.addWidget(self.splitter) if len(model): self.stack.setCurrentIndex(0) self.splitter.setSizes([self.splitter.size().height(), 50]) else: self.stack.setCurrentIndex(1) self.splitter.setSizes([self.splitter.size().height(), 0]) model.updateCallbacks.append(self.modelUpdateEvent) def keyPressEvent(self, event: QKeyEvent) -> None: if event.key() == Qt.Key_Escape: self.modlist.setFocus() self.searchbar.setText('') elif event.matches(QKeySequence.Find): self.searchbar.setFocus() elif event.matches(QKeySequence.Paste): self.pasteEvent() super().keyPressEvent(event) def pasteEvent(self) -> None: clipboard = QApplication.clipboard().text().splitlines() if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]): self.parentWidget().showDownloadModDialog() else: urls = [ url for url in QApplication.clipboard().text().splitlines() if len(str(url.strip())) ] if all( isValidModDownloadUrl(url) or isValidFileUrl(url) for url in urls): asyncio.create_task(self.modlist.checkInstallFromURLs(urls)) def modelUpdateEvent(self, model: Model) -> None: if len(model) > 0: if self.stack.currentIndex() != 0: self.stack.setCurrentIndex(0) self.repaint() else: if self.stack.currentIndex() != 1: self.stack.setCurrentIndex(1) self.repaint() def unhideOutput(self) -> None: if self.splitter.sizes()[1] < 10: self.splitter.setSizes([self.splitter.size().height(), 50]) def unhideModList(self) -> None: if self.splitter.sizes()[0] < 10: self.splitter.setSizes([50, self.splitter.size().height()]) def log(self, message: Any) -> None: # format log messages to user readable output settings = QSettings() record = message.record message = record['message'] extra = record['extra'] level = record['level'].name.lower() name = str(extra['name'] ) if 'name' in extra and extra['name'] is not None else '' path = str(extra['path'] ) if 'path' in extra and extra['path'] is not None else '' dots = bool( extra['dots'] ) if 'dots' in extra and extra['dots'] is not None else False newline = bool( extra['newline'] ) if 'newline' in extra and extra['newline'] is not None else False output = bool( extra['output'] ) if 'output' in extra and extra['output'] is not None else bool( message) modlist = bool( extra['modlist'] ) if 'modlist' in extra and extra['modlist'] is not None else False if level in ['debug' ] and settings.value('debugOutput', 'False') != 'True': if newline: self.output.append(f'') return n = '<br>' if newline else '' d = '...' if dots else '' if len(name) and len(path): path = f' ({path})' if output: message = html.escape(message, quote=True) if level in ['success', 'error', 'warning']: message = f'<strong>{message}</strong>' if level in ['success']: message = f'<font color="#04c45e">{message}</font>' if level in ['error', 'critical']: message = f'<font color="#ee3b3b">{message}</font>' if level in ['warning']: message = f'<font color="#ff6500">{message}</font>' if level in ['debug', 'trace']: message = f'<font color="#aaa">{message}</font>' path = f'<font color="#aaa">{path}</font>' if path else '' d = f'<font color="#aaa">{d}</font>' if d else '' time = record['time'].astimezone( tz=None).strftime('%Y-%m-%d %H:%M:%S') message = f'<font color="#aaa">{time}</font> {message}' self.output.append( f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}' ) else: self.output.append(f'') self.output.verticalScrollBar().setValue( self.output.verticalScrollBar().maximum()) self.output.repaint() if modlist: self.unhideModList() if settings.value('unhideOutput', 'True') == 'True' and output: self.unhideOutput()
class ScatterPlotMatrix(DataView): def __init__(self, workbench: WorkbenchModel, parent=None): super().__init__(workbench, parent) self.__frameModel: FrameModel = None # Create widget for the two tables sideLayout = QVBoxLayout() self.__matrixAttributes = SearchableAttributeTableWidget( self, True, False, False, [Types.Numeric, Types.Ordinal]) matrixLabel = QLabel( 'Select at least two numeric attributes and press \'Create chart\' to plot' ) matrixLabel.setWordWrap(True) self.__createButton = QPushButton('Create chart', self) self.__colorByBox = QComboBox(self) self.__autoDownsample = QCheckBox('Auto downsample', self) self.__useOpenGL = QCheckBox('Use OpenGL', self) self.__autoDownsample.setToolTip( 'If too many points are to be rendered, this will try\n' 'to plot only a subsample, improving performance with\n' 'zooming and panning, but increasing rendering time') self.__useOpenGL.setToolTip( 'Enforce usage of GPU acceleration to render charts.\n' 'It is still an experimental feature but should speed\n' 'up rendering with huge set of points') # Layout for checkboxes optionsLayout = QHBoxLayout() optionsLayout.addWidget(self.__autoDownsample, 0, Qt.AlignRight) optionsLayout.addWidget(self.__useOpenGL, 0, Qt.AlignRight) sideLayout.addWidget(matrixLabel) sideLayout.addWidget(self.__matrixAttributes) sideLayout.addLayout(optionsLayout) sideLayout.addWidget(self.__colorByBox, 0, Qt.AlignBottom) sideLayout.addWidget(self.__createButton, 0, Qt.AlignBottom) self.__matrixLayout: pg.GraphicsLayoutWidget = pg.GraphicsLayoutWidget( ) self.__layout = QHBoxLayout(self) self.__comboModel = AttributeProxyModel( [Types.String, Types.Ordinal, Types.Nominal], self) # Error label to signal errors self.errorLabel = QLabel(self) self.errorLabel.setWordWrap(True) sideLayout.addWidget(self.errorLabel) self.errorLabel.hide() self.__splitter = QSplitter(self) sideWidget = QWidget(self) sideWidget.setLayout(sideLayout) # chartWidget.setMinimumWidth(300) self.__splitter.addWidget(self.__matrixLayout) self.__splitter.addWidget(sideWidget) self.__splitter.setSizes([600, 300]) self.__layout.addWidget(self.__splitter) self.__splitter.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # Connect self.__createButton.clicked.connect(self.showScatterPlots) self.spinner = QtWaitingSpinner(self.__matrixLayout) @Slot() def showScatterPlots(self) -> None: self.__createButton.setDisabled(True) # Create plot with selected attributes attributes: Set[int] = self.__matrixAttributes.model().checked if len(attributes) < 2: self.errorLabel.setText('Select at least 2 attributes') self.errorLabel.setStyleSheet('color: red') self.errorLabel.show() return # stop elif self.errorLabel.isVisible(): self.errorLabel.hide() # Get index of groupBy Attribute group: int = None selectedIndex = self.__colorByBox.currentIndex() if self.__comboModel.rowCount() > 0 and selectedIndex != -1: index: QModelIndex = self.__comboModel.mapToSource( self.__comboModel.index(selectedIndex, 0, QModelIndex())) group = index.row() if index.isValid() else None # Create a new matrix layout and delete the old one matrix = GraphicsPlotLayout(parent=self) self.spinner = QtWaitingSpinner(matrix) oldM = self.__splitter.replaceWidget(0, matrix) self.__matrixLayout = matrix safeDelete(oldM) matrix.useOpenGL(self.__useOpenGL.isChecked()) matrix.show() # Get attributes of interest toKeep: List[int] = list(attributes) if group is None else [ group, *attributes ] filterDf = self.__frameModel.frame.getRawFrame().iloc[:, toKeep] # Create a worker to create scatter-plots on different thread worker = Worker(ProcessDataframe(), (filterDf, group, attributes)) worker.signals.result.connect(self.__createPlots) # No need to deal with error/finished signals since there is nothing to do worker.setAutoDelete(True) self.spinner.start() QThreadPool.globalInstance().start(worker) def resetScatterPlotMatrix(self) -> None: # Create a new matrix layout matrix = pg.GraphicsLayoutWidget(parent=self) self.spinner = QtWaitingSpinner(matrix) oldM = self.__splitter.replaceWidget(0, matrix) self.__matrixLayout = matrix safeDelete(oldM) matrix.show() @Slot(object, object) def __createPlots( self, _, result: Tuple[pd.DataFrame, List[str], List[int], bool]) -> None: """ Create plots and render all graphic items """ # Unpack results df, names, attributes, grouped = result # Populate the matrix for r in range(len(attributes)): for c in range(len(attributes)): if r == c: name: str = names[r] self.__matrixLayout.addLabel(row=r, col=c, text=name) else: xColName: str = names[c] yColName: str = names[r] seriesList = self.__createScatterSeries( df=df, xCol=xColName, yCol=yColName, groupBy=grouped, ds=self.__autoDownsample.isChecked()) plot = self.__matrixLayout.addPlot(row=r, col=c) for series in seriesList: plot.addItem(series) # Coordinates and data for later use plot.row = r plot.col = c plot.xName = xColName plot.yName = yColName # When all plot are created stop spinner and re-enable button self.spinner.stop() self.__createButton.setEnabled(True) @staticmethod def __createScatterSeries(df: Union[pd.DataFrame, pd.core.groupby.DataFrameGroupBy], xCol: str, yCol: str, groupBy: bool, ds: bool) -> List[pg.PlotDataItem]: """ Creates a list of series of points to be plotted in the scatterplot :param df: the input dataframe :param xCol: name of the feature to use as x-axis :param yCol: name of the feature to use as y-axis :param groupBy: whether the feature dataframe is grouped by some attribute :param ds: whether to auto downsample the set of points during rendering :return: """ allSeries = list() if groupBy: df: pd.core.groupby.DataFrameGroupBy colours = randomColors(len(df)) i = 0 for groupName, groupedDf in df: # Remove any row with nan values gdf = groupedDf.dropna() qSeries1 = pg.PlotDataItem(x=gdf[xCol], y=gdf[yCol], autoDownsample=ds, name=str(groupName), symbolBrush=pg.mkBrush(colours[i]), symbol='o', pen=None) allSeries.append(qSeries1) i += 1 else: df: pd.DataFrame # Remove any row with nan values df = df.dropna() series = pg.PlotDataItem(x=df[xCol], y=df[yCol], autoDownsample=ds, symbol='o', pen=None) allSeries.append(series) return allSeries @Slot(str, str) def onFrameSelectionChanged(self, frameName: str, *_) -> None: if not frameName: return self.__frameModel = self._workbench.getDataframeModelByName(frameName) self.__matrixAttributes.setSourceFrameModel(self.__frameModel) # Combo box attributes = AttributeTableModel(self, False, False, False) attributes.setFrameModel(self.__frameModel) oldModel = self.__comboModel.sourceModel() self.__comboModel.setSourceModel(attributes) if oldModel: oldModel.deleteLater() self.__colorByBox.setModel(self.__comboModel) # Reset attribute panel self.resetScatterPlotMatrix()