def setup_ui(self): layout = QGridLayout() table = QTreeWidget() table.setHeaderLabels(['name', 'width', 'height', 'ratio']) table.setSortingEnabled(True) items = [] for size in self.manager.project.sizes: i = ComparingTreeWidgetItem(None, [ f'{size.width}x{size.height}', str(size.width), str(size.height), str(size.ratio()) ]) i.__data__ = size items.append(i) table.addTopLevelItems(items) @table.currentItemChanged.connect def _(item, prev): del_btn.setEnabled(item is not None) layout.addWidget(table, 0, 0, 1, 2) add_btn = QPushButton('add new') @add_btn.clicked.connect def _(*args): diag = SizeInput(self.manager.project.sizes, self) if diag.exec_() != QDialog.Accepted: return size = diag.value self.manager.add_size(size) item = ComparingTreeWidgetItem(None, [ f'{size.width}x{size.height}', str(size.width), str(size.height), str(size.ratio()) ]) table.addTopLevelItem(item) layout.addWidget(add_btn, 1, 0) del_btn = QPushButton('delete') del_btn.setEnabled(False) @del_btn.clicked.connect def _(*args): item = table.currentItem() size = item.__data__ self.manager.del_size(size) shiboken2.delete(item) layout.addWidget(del_btn, 1, 1) self.setLayout(layout)
class SelectTiVoWidget(QDialog): """Displays the list of TiVos that were discovered on the network.""" # Signals connect_to_tivo = Signal(str, str) def __init__(self): super(SelectTiVoWidget, self).__init__() self.tivos_found = [] self.label = QLabel(self) self.label.setText('Below is a listing of all TiVos discovered on your' ' network, which will refresh every 5 seconds. If y' 'ou do not see your TiVo, ' '<a href="#specify_ip">click here to specify an IP' ' address.</a>') self.label.linkActivated.connect(self.specify_ip_address) self.tivo_listings = QTreeWidget(self) self.tivo_listings.itemDoubleClicked.connect(self.tivo_selected) self.tivo_listings.setHeaderLabels(["Name", "IP Address"]) self.layout = QVBoxLayout(self) self.layout.addWidget(self.label) self.layout.addWidget(self.tivo_listings) self.setWindowTitle("Select TiVo") self.resize(512, 384) self.setModal(True) def add_tivo(self, name, ip_address): item = QTreeWidgetItem() item.setText(0, name) item.setText(1, ip_address) self.tivo_listings.addTopLevelItem(item) self.tivos_found.append(item) def tivo_selected(self, item, column): self.connect_to_tivo.emit(item.text(0), item.text(1)) def specify_ip_address(self, link): text, ok = QInputDialog().getText(self, "Specify TiVO IP address", "IP address:", QLineEdit.Normal) if ok: self.connect_to_tivo.emit("unknown", text)
def add_tree_with_header(self, parent): tw = QTreeWidget() parent.addWidget(tw) tw.setHeaderLabels(['Type', 'Legs', 'Has Hair/Fur']) tw.setSortingEnabled(True) tw.setAlternatingRowColors(True) i1 = QTreeWidgetItem(tw, ['Mammal']) i2 = QTreeWidgetItem(i1, ['Rodent']) i3 = QTreeWidgetItem(i2, ['Rat', '4', 'True']) i3 = QTreeWidgetItem(i2, ['Beaver', '2', 'True']) i2 = QTreeWidgetItem(i1, ['Primate']) i3 = QTreeWidgetItem(i2, ['Baboon', '2', 'True']) i3 = QTreeWidgetItem(i2, ['Human', '2', 'True']) i1 = QTreeWidgetItem(tw, ['Reptile']) i2 = QTreeWidgetItem(i1, ['Snake', '0', 'False']) i3 = QTreeWidgetItem(i1, ['Crocodile', '4', 'False']) self.add_expand_collapse_buttons(parent, tw)
def setup_ui(self): layout = QGridLayout() table = QTreeWidget() table.setHeaderLabels(['name', 'type', 'width', 'height']) table.setSortingEnabled(True) items = [] for photo in self.manager.project.photos: if not photo.is_available(): w = h = 'N/A!' else: w, h = photo.raw_size() i = ComparingTreeWidgetItem( None, [str(photo), type(photo).__name__, str(w), str(h)]) i.__data__ = photo items.append(i) table.addTopLevelItems(items) @table.currentItemChanged.connect def _(item, prev): del_btn.setEnabled(item is not None) layout.addWidget(table, 0, 0) del_btn = QPushButton('delete') del_btn.setEnabled(False) @del_btn.clicked.connect def _(*args): item = table.currentItem() size = item.__data__ self.manager.del_photo(size) shiboken2.delete(item) layout.addWidget(del_btn, 1, 0) self.setLayout(layout)
class ObjectTreeDialog(QDialog): def __init__(self, parent=None, root_object=None): super(ObjectTreeDialog, self).__init__(parent) self.setWindowTitle("Object Tree") layout = QtWidgets.QVBoxLayout() # Tree widget for displaying our object hierarchy self.tree_widget = QTreeWidget() self.tree_widget_columns = [ "TYPE", "OBJECT NAME", "TEXT", "ICONTEXT", "TITLE", "WINDOW_TITLE", "CLASSES", "POINTER_ADDRESS", "GEOMETRY" ] self.tree_widget.setColumnCount(len(self.tree_widget_columns)) self.tree_widget.setHeaderLabels(self.tree_widget_columns) # Only show our type and object name columns. The others we only use to store data so that # we can use the built-in QTreeWidget.findItems to query. for column_name in self.tree_widget_columns: if column_name == "TYPE" or column_name == "OBJECT NAME": continue column_index = self.tree_widget_columns.index(column_name) self.tree_widget.setColumnHidden(column_index, True) header = self.tree_widget.header() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Populate our object tree widget # If a root object wasn't specified, then use the Editor main window if not root_object: params = azlmbr.qt.QtForPythonRequestBus( azlmbr.bus.Broadcast, "GetQtBootstrapParameters") editor_id = QtWidgets.QWidget.find(params.mainWindowId) editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) root_object = editor_main_window self.build_tree(root_object, self.tree_widget) # Listen for when the tree widget selection changes so we can update # selected item properties self.tree_widget.itemSelectionChanged.connect( self.on_tree_widget_selection_changed) # Split our tree widget with a properties view for showing more information about # a selected item. We also use a stacked layout for the properties view so that # when nothing has been selected yet, we can show a message informing the user # that something needs to be selected. splitter = QSplitter() splitter.addWidget(self.tree_widget) self.widget_properties = QWidget(self) self.stacked_layout = QtWidgets.QStackedLayout() self.widget_info = QWidget() form_layout = QtWidgets.QFormLayout() self.name_value = QLineEdit("") self.name_value.setReadOnly(True) self.type_value = QLabel("") self.geometry_value = QLabel("") self.text_value = QLabel("") self.icon_text_value = QLabel("") self.title_value = QLabel("") self.window_title_value = QLabel("") self.classes_value = QLabel("") form_layout.addRow("Name:", self.name_value) form_layout.addRow("Type:", self.type_value) form_layout.addRow("Geometry:", self.geometry_value) form_layout.addRow("Text:", self.text_value) form_layout.addRow("Icon Text:", self.icon_text_value) form_layout.addRow("Title:", self.title_value) form_layout.addRow("Window Title:", self.window_title_value) form_layout.addRow("Classes:", self.classes_value) self.widget_info.setLayout(form_layout) self.widget_properties.setLayout(self.stacked_layout) self.stacked_layout.addWidget( QLabel("Select an object to view its properties")) self.stacked_layout.addWidget(self.widget_info) splitter.addWidget(self.widget_properties) # Give our splitter stretch factor of 1 so it will expand to take more room over # the footer layout.addWidget(splitter, 1) # Create our popup widget for showing information when hovering over widgets self.hovered_widget = None self.inspect_mode = False self.inspect_popup = InspectPopup() self.inspect_popup.resize(100, 50) self.inspect_popup.hide() # Add a footer with a button to switch to widget inspect mode self.footer = QWidget() footer_layout = QtWidgets.QHBoxLayout() self.inspect_button = QPushButton("Pick widget to inspect") self.inspect_button.clicked.connect(self.on_inspect_clicked) footer_layout.addStretch(1) footer_layout.addWidget(self.inspect_button) self.footer.setLayout(footer_layout) layout.addWidget(self.footer) self.setLayout(layout) # Delete ourselves when the dialog is closed, so that we don't stay living in the background # since we install an event filter on the application self.setAttribute(Qt.WA_DeleteOnClose, True) # Listen to events at the application level so we can know when the mouse is moving app = QtWidgets.QApplication.instance() app.installEventFilter(self) def eventFilter(self, obj, event): # Look for mouse movement events so we can see what widget the mouse is hovered over event_type = event.type() if event_type == QEvent.MouseMove: global_pos = event.globalPos() # Make our popup follow the mouse, but we need to offset it by 1, 1 otherwise # the QApplication.widgetAt will always return our popup instead of the Editor # widget since it is on top self.inspect_popup.move(global_pos + QtCore.QPoint(1, 1)) # Find out which widget is under our current mouse position hovered_widget = QtWidgets.QApplication.widgetAt(global_pos) if self.hovered_widget: # Bail out, this is the same widget we are already hovered on if self.hovered_widget is hovered_widget: return False # Update our hovered widget and label self.hovered_widget = hovered_widget self.update_hovered_widget_popup() elif event_type == QEvent.KeyRelease: if event.key() == Qt.Key_Escape: # Cancel the inspect mode if the Escape key is pressed # We don't need to actually hide the inspect popup here because # it will be hidden already by the Escape action self.inspect_mode = False elif event_type == QEvent.MouseButtonPress or event_type == QEvent.MouseButtonRelease: # Trigger inspecting the currently hovered widget when the left mouse button is clicked # Don't continue processing this event if self.inspect_mode and event.button() == Qt.LeftButton: # Only trigger the inspect on the click release, but we want to also eat the press # event so that the widget we clicked on isn't stuck in a weird state (e.g. thinks its being dragged) # Also hide the inspect popup since it won't be hidden automatically by the mouse click since we are # consuming the event if event_type == event_type == QEvent.MouseButtonRelease: self.inspect_popup.hide() self.inspect_widget() return True # Pass every event through return False def build_tree(self, obj, parent_tree): if len(obj.children()) == 0: return for child in obj.children(): object_type = type(child).__name__ object_name = child.objectName() text = icon_text = title = window_title = geometry_str = classes = "(N/A)" if isinstance(child, QtGui.QWindow): title = child.title() if isinstance(child, QAction): text = child.text() icon_text = child.iconText() if isinstance(child, QWidget): window_title = child.windowTitle() if not (child.property("class") == ""): classes = child.property("class") if isinstance(child, QAbstractButton): text = child.text() # Keep track of the pointer address for this object so we can search for it later pointer_address = str(int(getCppPointer(child)[0])) # Some objects might not have a geometry (e.g. actions, generic qobjects) if hasattr(child, 'geometry'): geometry_rect = child.geometry() geometry_str = "x: {x}, y: {y}, width: {width}, height: {height}".format( x=geometry_rect.x(), y=geometry_rect.y(), width=geometry_rect.width(), height=geometry_rect.height()) child_tree = QTreeWidgetItem([ object_type, object_name, text, icon_text, title, window_title, classes, pointer_address, geometry_str ]) if isinstance(parent_tree, QTreeWidget): parent_tree.addTopLevelItem(child_tree) else: parent_tree.addChild(child_tree) self.build_tree(child, child_tree) def update_hovered_widget_popup(self): if self.inspect_mode and self.hovered_widget: if not self.inspect_popup.isVisible(): self.inspect_popup.show() self.inspect_popup.update_widget(self.hovered_widget) else: self.inspect_popup.hide() def on_inspect_clicked(self): self.inspect_mode = True self.update_hovered_widget_popup() def on_tree_widget_selection_changed(self): selected_items = self.tree_widget.selectedItems() # If nothing is selected, then switch the stacked layout back to 0 # to show the message if not selected_items: self.stacked_layout.setCurrentIndex(0) return # Update the selected widget properties and switch to the 1 index in # the stacked layout so that all the rows will be visible item = selected_items[0] self.name_value.setText( item.text(self.tree_widget_columns.index("OBJECT NAME"))) self.type_value.setText( item.text(self.tree_widget_columns.index("TYPE"))) self.geometry_value.setText( item.text(self.tree_widget_columns.index("GEOMETRY"))) self.text_value.setText( item.text(self.tree_widget_columns.index("TEXT"))) self.icon_text_value.setText( item.text(self.tree_widget_columns.index("ICONTEXT"))) self.title_value.setText( item.text(self.tree_widget_columns.index("TITLE"))) self.window_title_value.setText( item.text(self.tree_widget_columns.index("WINDOW_TITLE"))) self.classes_value.setText( item.text(self.tree_widget_columns.index("CLASSES"))) self.stacked_layout.setCurrentIndex(1) def inspect_widget(self): self.inspect_mode = False # Find the tree widget item that matches our hovered widget, and then set it as the current item # so that the tree widget will scroll to it, expand it, and select it widget_pointer_address = str(int( getCppPointer(self.hovered_widget)[0])) pointer_address_column = self.tree_widget_columns.index( "POINTER_ADDRESS") items = self.tree_widget.findItems( widget_pointer_address, Qt.MatchFixedString | Qt.MatchRecursive, pointer_address_column) if items: item = items[0] self.tree_widget.clearSelection() self.tree_widget.setCurrentItem(item) else: print("Unable to find widget")
# --------------------------- # Treeにitemに子itemを追加する # --------------------------- import sys from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem app = QApplication(sys.argv) qw_tree = QTreeWidget() qw_tree.setHeaderLabels(["name", "tel", "mail"]) qw_tree_parent_item = QTreeWidgetItem(['family']) qw_tree_parent_item.addChild( QTreeWidgetItem(['A', '111-111-111', '*****@*****.**'])) # Itemに子Itemを追加 qw_tree.addTopLevelItem(qw_tree_parent_item) qw_tree.expandAll() # TreeのItemを全て開く qw_tree.show() sys.exit(app.exec_())
# --------------------------- # TreeをクリックしてTextEditを切り替える # --------------------------- import sys from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QTextEdit, QStackedWidget, QSplitter app = QApplication(sys.argv) qw_stack = QStackedWidget() qw_tree = QTreeWidget() qw_tree.setHeaderLabels(["test"]) def tree_item_clicked(item, column): print(item, column, item.type()) # TreeがクリックされたときにTextEditを切り替える qw_stack.setCurrentIndex(item.type()) for i in range(3): text = 'page No.' + str(i + 1) qw_tree_item = QTreeWidgetItem([text], type=i) qw_tree.addTopLevelItem(qw_tree_item) qw_text_edit = QTextEdit() qw_text_edit.append(text) stack_idx = qw_stack.addWidget(qw_text_edit) # print(stack_idx)
class App(QMainWindow): def __init__(self): super().__init__() self.init_ui() self.attach_event() self.setAcceptDrops(True) def init_ui(self): # Define & Configure Components normal_button_size = QSize(80, 24) icon_button_size = QSize(24, 24) icon_size = QSize(18, 18) self.central_widget = QWidget() self.central_layout = QGridLayout() self.central_widget.setLayout(self.central_layout) self.tab_group_widget = QTabWidget() self.tab_group_widget.setMinimumSize(400, 0) self.tab_group_widget.setFixedHeight(150) self.tab1_name = '스폰서 변환' self.tab2_name = '싱크 조절(초)' self.tab3_name = '싱크 조절(%)' self.tab_page_1 = QWidget() self.tab_grid_1 = QGridLayout() self.tab1_search_label = QLabel('검색 텍스트') self.tab1_search = QLineEdit() self.tab1_sponsor = QWidget() self.tab1_sponsor_layout = QHBoxLayout() self.tab1_sponsor_layout.setContentsMargins(0, 0, 0, 0) self.tab1_sponsor_label = QLabel('스폰서 영상 길이') self.tab1_sponsor_value = QDoubleSpinBox() self.tab1_sponsor_value.setFixedWidth(60) self.tab1_sponsor_value.setMinimum(-1000000000) self.tab1_sponsor_value.setValue(10) self.tab1_offset = QWidget() self.tab1_offset_layout = QHBoxLayout() self.tab1_offset_layout.setContentsMargins(0, 0, 0, 0) self.tab1_offset_label = QLabel('라인 오프셋') self.tab1_offset_value = QSpinBox() self.tab1_offset_value.setMinimum(-1000000000) self.tab1_offset_value.setValue(2) self.tab1_offset_value.setFixedWidth(50) self.tab1_ignore = QWidget() self.tab1_ignore_layout = QHBoxLayout() self.tab1_ignore_layout.setContentsMargins(0, 0, 0, 0) self.tab1_ignore_label1 = QLabel('시작부터') self.tab1_ignore_value = QSpinBox() self.tab1_ignore_value.setFixedWidth(50) self.tab1_ignore_value.setValue(5) self.tab1_ignore_label2 = QLabel('줄 ') self.tab1_ignore_sec = QSpinBox() self.tab1_ignore_sec.setFixedWidth(60) self.tab1_ignore_sec.setMaximum(1000) self.tab1_ignore_sec.setValue(90) self.tab1_ignore_label3 = QLabel('초 무시하기') self.tab1_add_button = QPushButton('추가하기') self.tab_page_2 = QWidget() self.tab_grid_2 = QGridLayout() self.tab2_shift = QWidget() self.tab2_shift_layout = QHBoxLayout() self.tab2_shift_layout.setContentsMargins(0, 0, 0, 0) self.tab2_shift_label1 = QLabel('자막 싱크') self.tab2_shift_value = QDoubleSpinBox() self.tab2_shift_value.setFixedWidth(60) self.tab2_shift_label2 = QLabel('초 ') self.tab2_slow_radio = QRadioButton('느리게') self.tab2_slow_radio.setChecked(True) self.tab2_fast_radio = QRadioButton('빠르게') self.tab2_add_button = QPushButton('추가하기') self.tab_page_3 = QWidget() self.tab_grid_3 = QGridLayout() self.tab3_speed_label1 = QLabel('자막 싱크') self.tab3_speed_value = QSpinBox() self.tab3_speed_value.setFixedWidth(70) self.tab3_speed_value.setRange(1, 1000) self.tab3_speed_value.setValue(100) self.tab3_speed_label2 = QLabel('%') self.tab3_add_button = QPushButton('추가하기') self.que_label = QLabel('작업 목록') self.que_label.setFixedHeight(24) self.que_widget = QWidget() self.que_widget.setFixedHeight(114) self.que_layout = QGridLayout() self.que_layout.setContentsMargins(0, 0, 0, 0) self.que_list = QTreeWidget() self.que_list.setHeaderLabels(['작업', '옵션']) self.que_delete_button = QPushButton(QIcon(':/remove.png'), '') self.que_delete_button.setFixedSize(icon_button_size) self.que_delete_button.setIconSize(icon_size) self.que_delete_button.setToolTip('목록 삭제') self.que_up_button = QPushButton(QIcon(':/up.png'), '') self.que_up_button.setIconSize(icon_size) self.que_up_button.setFixedSize(icon_button_size) self.que_up_button.setToolTip('위로') self.que_down_button = QPushButton(QIcon(':/down.png'), '') self.que_down_button.setIconSize(icon_size) self.que_down_button.setFixedSize(icon_button_size) self.que_down_button.setToolTip('아래로') self.que_clear_button = QPushButton(QIcon(':/clear.png'), '') self.que_clear_button.setIconSize(icon_size) self.que_clear_button.setFixedSize(icon_button_size) self.que_clear_button.setToolTip('비우기') self.file_label = QLabel('파일 목록') self.file_label.setFixedHeight(24) self.file_widget = QWidget() self.file_layout = QGridLayout() self.file_layout.setContentsMargins(0, 0, 0, 0) self.file_list = QTreeWidget() self.file_list.setAcceptDrops(True) self.file_list.setHeaderLabels(['이름', '경로']) self.file_file_open = QPushButton(QIcon(':/file.png'), '') self.file_file_open.setFixedSize(icon_button_size) self.file_file_open.setIconSize(icon_size) self.file_file_open.setToolTip('파일 열기') self.file_dir_open = QPushButton(QIcon(':/folder.png'), '') self.file_dir_open.setFixedSize(icon_button_size) self.file_dir_open.setIconSize(icon_size) self.file_dir_open.setToolTip('폴더 열기') self.file_delete = QPushButton(QIcon(':/remove.png'), '') self.file_delete.setFixedSize(icon_button_size) self.file_delete.setIconSize(icon_size) self.file_delete.setToolTip('목록 삭제') self.file_clear = QPushButton(QIcon(':/clear.png'), '') self.file_clear.setFixedSize(icon_button_size) self.file_clear.setIconSize(icon_size) self.file_clear.setToolTip('비우기') self.file_encode = QPushButton(QIcon(':/encode.png'), '') self.file_encode.setFixedSize(icon_button_size) self.file_encode.setIconSize(icon_size) self.file_encode.setToolTip('인코딩 설정') self.save_widget = QGroupBox('저장 옵션') self.save_widget.setMinimumSize(400, 0) self.save_widget.setFixedHeight(82) self.save_layout = QGridLayout() self.save_orig_radio = QRadioButton('원본 위치에 저장') self.save_orig_radio.setChecked(True) self.save_strip = QCheckBox('싱크 꼬임 무시') self.save_strip.setToolTip('싱크 꼬임을 무시하고 모든 자막을 보존합니다.') self.save_dir_radio = QRadioButton('다른 위치에 저장') self.save_dir_line = QLineEdit() self.save_dir_find = QPushButton('...') self.save_dir_find.setFixedWidth(40) self.ok_button = QPushButton('적용') self.ok_button.setFixedSize(normal_button_size) self.cancel_button = QPushButton('취소') self.cancel_button.setFixedSize(normal_button_size) # Display GUI Components self.central_layout.addWidget(self.tab_group_widget, 0, 0, 1, 3) self.central_layout.addWidget(self.que_label, 1, 0, 1, 3) self.central_layout.addWidget(self.que_widget, 2, 0, 1, 3) self.central_layout.addWidget(self.file_label, 3, 0, 1, 3) self.central_layout.addWidget(self.file_widget, 4, 0, 1, 3) self.central_layout.addWidget(self.save_widget, 5, 0, 1, 3) self.central_layout.addWidget(self.ok_button, 6, 1, 1, 1) self.central_layout.addWidget(self.cancel_button, 6, 2, 1, 1) self.tab_group_widget.addTab(self.tab_page_1, QIcon(), self.tab1_name) self.tab_group_widget.addTab(self.tab_page_2, QIcon(), self.tab2_name) self.tab_group_widget.addTab(self.tab_page_3, QIcon(), self.tab3_name) self.tab_page_1.setLayout(self.tab_grid_1) self.tab_grid_1.addWidget(self.tab1_search_label, 0, 0, 1, 1) self.tab_grid_1.addWidget(self.tab1_search, 0, 1, 1, 2) self.tab_grid_1.addWidget(self.tab1_sponsor, 1, 1, 1, 1) self.tab_grid_1.addWidget(self.tab1_offset, 1, 2, 1, 1) self.tab_grid_1.addWidget(self.tab1_ignore, 2, 1, 1, 2) self.tab_grid_1.addWidget(self.tab1_add_button, 3, 0, 1, 3) self.tab1_sponsor.setLayout(self.tab1_sponsor_layout) self.tab1_sponsor_layout.addWidget(self.tab1_sponsor_label) self.tab1_sponsor_layout.addWidget(self.tab1_sponsor_value) self.tab1_sponsor_layout.addStretch(1) self.tab1_offset.setLayout(self.tab1_offset_layout) self.tab1_offset_layout.addWidget(self.tab1_offset_label) self.tab1_offset_layout.addWidget(self.tab1_offset_value) self.tab1_offset_layout.addStretch(1) self.tab1_ignore.setLayout(self.tab1_ignore_layout) self.tab1_ignore_layout.addWidget(self.tab1_ignore_label1) self.tab1_ignore_layout.addWidget(self.tab1_ignore_value) self.tab1_ignore_layout.addWidget(self.tab1_ignore_label2) self.tab1_ignore_layout.addWidget(self.tab1_ignore_sec) self.tab1_ignore_layout.addWidget(self.tab1_ignore_label3) self.tab1_ignore_layout.addStretch(1) self.tab_page_2.setLayout(self.tab_grid_2) self.tab_grid_2.setRowStretch(0, 1) self.tab_grid_2.addWidget(self.tab2_shift, 1, 0, 2, 1) self.tab_grid_2.addWidget(self.tab2_slow_radio, 1, 1, 1, 1) self.tab_grid_2.addWidget(self.tab2_fast_radio, 2, 1, 1, 1) self.tab_grid_2.setColumnStretch(2, 1) self.tab_grid_2.setRowStretch(3, 1) self.tab_grid_2.addWidget(self.tab2_add_button, 4, 0, 1, 3) self.tab2_shift.setLayout(self.tab2_shift_layout) self.tab2_shift_layout.addWidget(self.tab2_shift_label1) self.tab2_shift_layout.addWidget(self.tab2_shift_value) self.tab2_shift_layout.addWidget(self.tab2_shift_label2) self.tab_page_3.setLayout(self.tab_grid_3) self.tab_grid_3.setRowStretch(0, 1) self.tab_grid_3.addWidget(self.tab3_speed_label1, 1, 0, 1, 1) self.tab_grid_3.addWidget(self.tab3_speed_value, 1, 1, 1, 1) self.tab_grid_3.addWidget(self.tab3_speed_label2, 1, 2, 1, 1) self.tab_grid_3.setColumnStretch(3, 1) self.tab_grid_3.setRowStretch(2, 1) self.tab_grid_3.addWidget(self.tab3_add_button, 3, 0, 1, 4) self.que_widget.setLayout(self.que_layout) self.que_layout.addWidget(self.que_list, 0, 0, 4, 1) self.que_layout.addWidget(self.que_delete_button, 0, 1, 1, 1) self.que_layout.addWidget(self.que_up_button, 1, 1, 1, 1) self.que_layout.addWidget(self.que_down_button, 2, 1, 1, 1) self.que_layout.addWidget(self.que_clear_button, 3, 1, 1, 1) self.file_widget.setLayout(self.file_layout) self.file_layout.addWidget(self.file_list, 0, 0, 6, 1) self.file_layout.addWidget(self.file_file_open, 0, 1, 1, 1) self.file_layout.addWidget(self.file_dir_open, 1, 1, 1, 1) self.file_layout.addWidget(self.file_delete, 2, 1, 1, 1) self.file_layout.addWidget(self.file_clear, 3, 1, 1, 1) self.file_layout.addWidget(self.file_encode, 5, 1, 1, 1) self.save_widget.setLayout(self.save_layout) self.save_layout.addWidget(self.save_orig_radio, 0, 0, 1, 1) self.save_layout.setColumnStretch(1, 1) self.save_layout.addWidget(self.save_strip, 0, 2, 1, 2) self.save_layout.addWidget(self.save_dir_radio, 1, 0, 1, 1) self.save_layout.addWidget(self.save_dir_line, 1, 1, 1, 2) self.save_layout.addWidget(self.save_dir_find, 1, 3, 1, 1) self.setWindowTitle('Batch SAMI Sync v0.2') self.setCentralWidget(self.central_widget) self.adjustSize() def attach_event(self): # Default encoding hack self.encoding = '자동' # Define and Connect event handlers def tab1_add(): sponsor_text = self.tab1_search.text() sponsor_time = self.tab1_sponsor_value.value() line_offset = self.tab1_offset_value.value() line_ignore = self.tab1_ignore_value.value() time_ignore = self.tab1_ignore_sec.value() data = [1, sponsor_time, sponsor_text, line_offset, line_ignore, time_ignore] item = QTreeWidgetItem(self.que_list, [self.tab1_name, '스폰서 영상 시간 : ' + str(sponsor_time) + '초, 오프셋 : ' + str(line_offset) + '줄, 시작부터 ' + str(line_ignore) + '번째 줄, ' + str(time_ignore) + '초 무시 - 검색어 : ' + sponsor_text]) item.setData(2, 2, data) def tab2_add(): shift_time = self.tab2_shift_value.value() shift_direction = self.tab2_fast_radio.isChecked() direction_text = '빠르게' if shift_direction else '느리게' data = [2, shift_time, shift_direction] item = QTreeWidgetItem(self.que_list, [self.tab2_name, '자막 싱크 ' + str(shift_time) + '초 ' + direction_text]) item.setData(2, 2, data) def tab3_add(): speed_rate = self.tab3_speed_value.value() data = [3, speed_rate] item = QTreeWidgetItem(self.que_list, [self.tab3_name, '자막 속도 ' + str(speed_rate) + '%']) item.setData(2, 2, data) def file_open(): selected = QFileDialog.getOpenFileNames(self, "자막 파일 선택", "", "SAMI Files (*.smi);;All Files (*)") for file in selected[0]: name = ntpath.basename(file) Utils.insert_list(self.file_list, name, file) def dir_open(): selected = QFileDialog.getExistingDirectory(self, "자막 폴더 선택") for paths, subdirs, files in os.walk(selected): for file in files: if fnmatch(file, '*.smi'): name = ntpath.basename(file) Utils.insert_list(self.file_list, name, file) def open_encode_dialog(): self.dialog = QInputDialog(self) self.dialog.setWindowTitle('인코딩 설정') self.dialog.setLabelText('텍스트 인코딩 설정') self.dialog.setComboBoxItems(['자동', 'EUC-KR', 'UTF-8', 'UTF-16LE', 'UTF-16BE', '직접 입력']) self.dialog.show() self.dialog.textValueChanged.connect(type_encode) self.dialog.textValueSelected.connect(set_encode) def type_encode(text): if text == '직접 입력': self.dialog.setComboBoxItems([]) self.dialog.setComboBoxEditable(True) def set_encode(text): self.encoding = text def save_dir(): selected = QFileDialog.getExistingDirectory(self, "저장 위치 선택") self.save_dir_line.setText(selected) def apply(): self.ok_button.setEnabled(False) ques = Utils.read_list(self.que_list, False) files = Utils.read_list(self.file_list, False) strip = False if self.save_strip.isChecked() else True log = [] for file in files: try: text = Utils.launch_que(file[1], ques, self.encoding, strip) if len(text): if self.save_orig_radio.isChecked(): savepath = file[1] else: savepath = self.save_dir_line.text() + '/' + file[0] Utils.save_file(savepath, text) except Exception as e: log.append(file[0] + ' 처리 오류 : ' + str(e)) if log: ScrollMessageBox(QMessageBox.Warning, 'Batch SAMI Sync', "\n".join(log)) else: QMessageBox.information(self, 'Batch SAMI Sync', '변환 완료!') self.ok_button.setEnabled(True) self.tab1_add_button.clicked.connect(tab1_add) self.tab2_add_button.clicked.connect(tab2_add) self.tab3_add_button.clicked.connect(tab3_add) self.que_delete_button.clicked.connect(lambda: Utils.delete_list(self.que_list)) self.que_clear_button.clicked.connect(lambda: Utils.clear_list(self.que_list)) self.que_up_button.clicked.connect(lambda: Utils.up_list(self.que_list)) self.que_down_button.clicked.connect(lambda: Utils.down_list(self.que_list)) self.file_file_open.clicked.connect(file_open) self.file_dir_open.clicked.connect(dir_open) self.file_delete.clicked.connect(lambda: Utils.delete_list(self.file_list)) self.file_clear.clicked.connect(lambda: Utils.clear_list(self.file_list)) self.file_encode.clicked.connect(open_encode_dialog) self.save_dir_find.clicked.connect(save_dir) self.ok_button.clicked.connect(apply) self.cancel_button.clicked.connect(sys.exit) def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasUrls(): event.setDropAction(Qt.CopyAction) event.accept() else: event.ignore() def dropEvent(self, event): if event.mimeData().hasUrls(): event.setDropAction(Qt.CopyAction) event.accept() for url in event.mimeData().urls(): if url.isLocalFile(): file = str(url.toLocalFile()) if fnmatch(file, '*.smi'): name = ntpath.basename(file) Utils.insert_list(self.file_list, name, file) elif not fnmatch(file, '*.*'): for paths, subdirs, files in os.walk(file): for file in files: if fnmatch(file, '*.smi'): name = ntpath.basename(file) Utils.insert_list(self.file_list, name, file) else: event.ignore()
def _initUI(self): def _init_tool_buttons(): # First logic buttons logic_buttons_layout = QHBoxLayout() logic_buttons = [('NO', QPushButton('NO')), ('AND', QPushButton('AND')), ('OR', QPushButton('OR')), ('implies', QPushButton('→')), ('equivalence', QPushButton('↔')), ('forall', QPushButton('∀')), ('exists', QPushButton('∃'))] for name, button in logic_buttons: if name in self.tool_buttons: logic_buttons_layout.addWidget(button) # Then proof buttons proof_buttons_layout = QHBoxLayout() proof_buttons = [\ ('p_contraposition', QPushButton('Proof by contraposition')), ('p_absurd', QPushButton('Proof by contradicton')), ('p_cases', QPushButton('Cases disjunction')), ('p_induction', QPushButton('Proof by induction'))] for name, button in proof_buttons: if name in self.tool_buttons: proof_buttons_layout.addWidget(button) # Put it all together buttons_layout = QVBoxLayout() buttons_layout.addLayout(logic_buttons_layout) buttons_layout.addLayout(proof_buttons_layout) return buttons_layout # Create widgets objects = PropobjList() objects.addItem('X : ensemble') objects.addItem('Y : ensemble') objects.addItem('f : X → Y') objects.addItem('x ∈ X') objects.addItem('A : partie de X') icon_item = QListWidgetItem('B partie de X') icon_item.setIcon(QIcon('icon_blue.png')) objects.addItem(icon_item) properties = PropobjList() properties.addItem('f est une fonction de remplissage') properties.addItem("transitivité de l'union") statements = QTreeWidget() statements.setAlternatingRowColors(True) statements.setHeaderLabels(['Énoncé', 'Identifiant']) anneaux_ideaux = StatementNode(statements, 'Anneaux et idéaux') Statement(anneaux_ideaux, ['Définition anneau', 'Définition 1.1']) Statement(anneaux_ideaux, ['Définition idéal', 'Définition 1.7']) Statement(anneaux_ideaux, ["Existence d'un idéal maximal", 'Théorème']) noetherianite = StatementNode(statements, 'Noetherianité') Statement(noetherianite, ['Transfert de Noethérianité', '']) Statement(noetherianite, ['Principal implique noethérien', 'Proposition 2.3']) statements.resizeColumnToContents(0) goal = Goal(GOAL) # Create layouts goal_layout = QHBoxLayout() logic_buttons = _init_tool_buttons() # already contains buttons main_layout = QVBoxLayout() workspace_layout = QHBoxLayout() propobj_layout = QVBoxLayout() tools_layout = QVBoxLayout() # Create QGroupBox to have titles propobj_gb = QGroupBox('Properties and objects') tools_gb = QGroupBox('Tools (affect goal, prop. and obj.)') # Put widgets in layouts and group boxes goal_layout.addStretch() goal_layout.addWidget(goal) goal_layout.addStretch() # Add space below goal goal_layout.setContentsMargins(0, 10, 0, 30) #LTRB propobj_layout.addWidget(objects) propobj_layout.addWidget(properties) tools_layout.addLayout(logic_buttons) tools_layout.addWidget(statements) propobj_gb.setLayout(propobj_layout) tools_gb.setLayout(tools_layout) workspace_layout.addWidget(propobj_gb) workspace_layout.addWidget(tools_gb) # Don't forget me main_layout.addLayout(goal_layout) main_layout.addLayout(workspace_layout) self.setWindowTitle("L'union des images réciproque est l'image "\ "réciproque de l'union — d∃∀duction") self.setLayout(main_layout) self.show()
# --------------------------- # Treeにヘッダーを表示する # --------------------------- import sys from PySide2.QtWidgets import QApplication, QTreeWidget app = QApplication(sys.argv) qw_tree = QTreeWidget() qw_tree.setHeaderLabels(["name", "tel", "mail"]) # Headerをつける qw_tree.show() sys.exit(app.exec_())
class CourseTreeWidget(QWidget, ABookCore): def __init__(self, path, settings, session): QWidget.__init__(self) ABookCore.__init__(self, path, settings, session) self.signal = CourseTreeWidgetSignals() self.selectedList = [] self.treeWidget = QTreeWidget() self.treeWidget.setHeaderLabels(['Name', "Course ID", "Chapter ID"]) self.treeWidget.itemChanged.connect(self.checkbox_toggled) self.treeWidget.clicked.connect(self.loadResourceList) self.addDownloadTaskButton = QPushButton("Add to Downloader") self.addDownloadTaskButton.clicked.connect(self.addDownloadTask) self.importCourseButton = QPushButton("Import Courses") self.importCourseButton.clicked.connect(self.startImportCourseWidget) main_layout = QGridLayout() main_layout.addWidget(self.treeWidget, 0, 0, 1, 2) main_layout.addWidget(self.importCourseButton, 1, 0) main_layout.addWidget(self.addDownloadTaskButton, 1, 1) main_layout.setMargin(0) self.setLayout(main_layout) if settings['first_launch'] is True: settings['first_launch'] = False self.importCourseButton.click() else: self.createTreeRoot() def createTreeRoot(self): courseList = self.getCourseList() for course in courseList: courseId = course['courseInfoId'] currentChapterList = self.getChapterList(courseId) self.createTree(self.treeWidget, 'course', course, currentChapterList, courseId) def createTree(self, parentItem, itemType, itemData, chapterList, courseId): if itemType == 'course': courseName = itemData['courseTitle'] courseId = itemData['courseInfoId'] courseItem = self.createCourseTreeItem(courseName, courseId, 'None', True) parentItem.addTopLevelItem(courseItem) childChapterList = self.getChildChapterList(chapterList, {'id': 0}) self.createTree(courseItem, 'chapter', childChapterList, chapterList, courseId) elif itemType == 'chapter': for chapter in itemData: childChapterList = self.getChildChapterList( chapterList, chapter) chapterName = chapter['name'] chapterId = chapter['id'] hasChild = len(childChapterList) > 0 chapterItem = self.createCourseTreeItem( chapterName, courseId, chapterId, hasChild) parentItem.addChild(chapterItem) if hasChild: self.createTree(chapterItem, 'chapter', childChapterList, chapterList, courseId) else: raise KeyError('Wrong TODO') def checkbox_toggled(self, node: QTreeWidgetItem, column: int): if node.checkState(column) == Qt.Checked: self.selectedList.append( [node.text(0), node.text(1), node.text(2)]) elif node.checkState(column) == Qt.Unchecked: if len(self.selectedList) > 1: self.selectedList.remove( [node.text(0), node.text(1), node.text(2)]) else: self.selectedList = [] def createCourseTreeItem(self, name: str, courseId: str, chapterId: str, hasChild: bool): item = QTreeWidgetItem() item.setText(0, str(name)) item.setText(1, str(courseId)) item.setText(2, str(chapterId)) if hasChild is True: item.setFlags(item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) else: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Unchecked) return item def addDownloadTask(self): for item in self.selectedList: if item[1] != "None" and item[2] != "None": courseId = item[1] chapterId = item[2] downloadList = self.getResourceList(courseId, chapterId) if downloadList is not None: for resource in downloadList: fileDir, filePath, fileName, coursePath = self.getResourcePath( courseId, chapterId, resource["resourceInfoId"]) self.signal.addDownloadTask.emit( fileName, filePath, "http://abook.hep.com.cn/ICourseFiles/" + resource["resFileUrl"]) def startImportCourseWidget(self): wizard = ImportCourseWizard(self) wizard.show() def loadResourceList(self): # When triggered on click, first adjust the width of the column self.treeWidget.resizeColumnToContents(0) self.treeWidget.resizeColumnToContents(1) self.treeWidget.resizeColumnToContents(2) # Get the course_id and chapter_id courseId = self.sender().currentItem().text(1) chapterId = self.sender().currentItem().text(2) # Ignore the root nodes if courseId != "None" and chapterId != "None": # Get the resource list # resource_list = self.get_resource_info(courseId, chapterId) resourceList = self.getResourceList(courseId, chapterId) # Clear the FileListWidget self.signal.clearFileListWidget.emit() # If resource list is not empty if isinstance(resourceList, list): # Each resource item is a QStandardItem # data role -1 stores the url of the resource # data role -2 stores the url of the preview image of the resource # data role Qt.TooltipRole stores the url of the resource # data role Qt.DecorationRole stores the preview image of the resource # We need to lazy load and cache the preview image so that the main thread will not be blocked # 1. create items without the Qt.DecorationRole and add it to resourceItemList # 2. pass the resource_item_list to LoadPicWorker to cache and load resourceItemList = [] for resource in resourceList: resName = resource["resTitle"] urlBase = "http://abook.hep.com.cn/ICourseFiles/" resFileUrl = urlBase + resource["resFileUrl"] resourceItem = QStandardItem(resName) resourceItem.setData(resFileUrl, Qt.ToolTipRole) resourceItem.setData(resFileUrl, -1) resourceItem.setData(resource['picUrl'], -2) self.signal.appendRowFileListWidget.emit(resourceItem) resourceItemList.append(resourceItem) loadPicWorker = LoadPicWorker(resourceItemList, self) loadPicWorker.start()
class TraceWindow(QMainWindow): def __init__(self, qmp): QMainWindow.__init__(self) self.qmp = qmp os.system('rm /tmp/errors.log 2>/dev/null') self.trace_events = self.qmp.hmp_command('info trace-events') self.qmp.hmp_command('logfile /tmp/errors.log') self.trace_events = sorted( self.trace_events['return'].split('\r\n'))[1:] self.activated = [] self.length = 100 self.timer = QTimer(self) self.timer.timeout.connect(self.disp_output) self.timer.start(100) self.init_ui() def init_ui(self): self.setWindowTitle('Trace Event Window') self.setGeometry(100, 100, 800, 600) bar = self.menuBar() file_ = bar.addMenu('File') export_log = QAction('Save to File', self, triggered=lambda: self.save_log()) options = bar.addMenu('Options') auto_refresh = QAction( 'Auto Refresh', self, checkable=True, triggered=lambda: self.timer.start(100) if auto_refresh.isChecked() else self.timer.stop()) auto_refresh.setChecked(True) options.addAction(auto_refresh) file_.addAction(export_log) vgrid = QVBoxLayout() grid = QHBoxLayout() self.tree = QTreeWidget() self.tree.setHeaderLabels(['Name']) self.top = [] self.lst = [] for n, event in enumerate(self.trace_events): word = event.split('_')[0] if word not in self.top: self.top.append(word) item = QTreeWidgetItem(self.tree) self.lst.append(item) item.setText(0, word) subitem = QTreeWidgetItem(item) subitem.setText(0, ' ' + event.split(' : ')[0]) # subitem.setCheckState(0, Qt.Unchecked) cbox = QCheckBox() cbox.stateChanged.connect(lambda state, text=subitem.text(0): self. handle_checked(state, text)) self.tree.setItemWidget(subitem, 0, cbox) # self.tree.setColumnWidth(0, 25) self.tracelist = QLabel() self.disp_output() self.traceview = QScrollArea() self.traceview.setWidget(self.tracelist) self.traceview.setWidgetResizable(True) search = QHBoxLayout() self.search_bar = QLineEdit(self) self.completer = QCompleter(self.top, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.search_bar.setCompleter(self.completer) search_button = QPushButton('Search') search_button.clicked.connect(lambda: self.tree.setCurrentItem( self.lst[self.top.index(self.search_bar.text())])) expand = QPushButton('▼') expand.setFixedSize(QSize(25, 25)) expand.clicked.connect(lambda: self.tree.expandAll()) collapse = QPushButton('▲') collapse.setFixedSize(QSize(25, 25)) collapse.clicked.connect(lambda: self.tree.collapseAll()) self.search_bar.returnPressed.connect(lambda: search_button.click()) search.addWidget(self.search_bar) search.addWidget(search_button) search.addWidget(expand) search.addWidget(collapse) self.digest = QLabel() vgrid.addLayout(search) vgrid.addWidget(self.tree) vgridwid = QWidget() vgridwid.setLayout(vgrid) split = QSplitter(Qt.Horizontal) split.addWidget(vgridwid) split.addWidget(self.traceview) split.setStretchFactor(1, 1) # grid.addLayout(vgrid) grid.addWidget(split) # grid.addWidget(self.tracelist) self.disp_output() center = QWidget() center.setLayout(grid) self.setCentralWidget(center) self.show() def disp_output(self): self.shorten_file() with open('/tmp/errors.log', 'r') as errors: self.digest = [] lines = 0 for line in errors: if re.match(r"\d+@\d+\.\d+:.*", line): self.digest.append(line) lines += 1 if not self.digest: self.digest = ['<font color="grey">Empty...</font>'] self.digest = ''.join(self.digest[-self.length:]) self.tracelist.setText(self.digest) self.tracelist.setFont(QFont('Monospace', 10)) self.tracelist.setTextInteractionFlags(Qt.TextSelectableByMouse) def shorten_file(self): with open('/tmp/errors.log', 'r+') as tracefile: content = ''.join(tracefile.readlines()[-(self.length * 3):]) tracefile.seek(0) tracefile.truncate() tracefile.write(content) def save_log(self): name = QFileDialog.getSaveFileName(self, 'Save File', '', 'Text files (*.txt)') log_file = open(name[0], 'w') log_file.write(self.digest) log_file.close() def handle_checked(self, state, text): if state: self.qmp.hmp_command('trace-event %s on' % text.strip()) self.activated.append(text) else: self.qmp.hmp_command('trace-event %s off' % text.strip()) self.activated.remove(text) def closeEvent(self, event): self.timer.stop() for e in self.activated: self.qmp.hmp_command('trace-event %s off' % e.strip()) os.system('rm /tmp/errors.log') event.accept()
class MemTree(QWidget): def __init__(self, qmp, parent): super().__init__() self.qmp = qmp self.qmp.memoryMap.connect(self.update_tree) self.parent = parent self.tree_sem = QSemaphore(1) self.sending_sem = QSemaphore( 1) # used to prevent sending too many requests at once self.init_ui() self.get_map() def init_ui(self): self.vbox = QVBoxLayout() self.refresh = QPushButton('Refresh') self.refresh.clicked.connect(lambda: self.get_map()) self.vbox.addWidget(self.refresh) self.tree = QTreeWidget() self.tree.itemDoubleClicked.connect(self.open_region) self.tree.setColumnCount(3) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) self.tree.setHeaderLabels( ['Memory Region', 'Start Address', 'End Address']) self.vbox.addWidget(self.tree) self.setLayout(self.vbox) self.setGeometry(100, 100, 500, 300) self.setWindowTitle("Memory Tree") self.show() def get_map(self): self.tree.clear() self.qmp.command('mtree') # finds item with name 'name' in self.tree # self.tree_sem must be acquired before use def find(self, name, node): if node.text(0) == name: return node else: for i in range(node.childCount()): result = self.find(name, node.child(i)) if result: return result return None def update_tree(self, value): if value != None: self.tree_sem.acquire() current_addr_space = '' for region in value: parent_node = self.tree parent = region['parent'] if parent != '': root = self.tree.invisibleRootItem() for i in range(root.childCount()): if root.child(i).text(0) == current_addr_space: root = root.child(i) break parent_node = self.find(parent, root) else: current_addr_space = region['name'] node = QTreeWidgetItem(parent_node) node.setText(0, region['name']) start = region['start'] end = region['end'] if start < 0: start = start + (1 << constants['bits']) if end < 0: end = end + (1 << constants['bits']) node.setText(1, f'{start:016x}') node.setText(2, f'{end:016x}') node.setFont(0, QFont('Courier New')) node.setFont(1, QFont('Courier New')) node.setFont(2, QFont('Courier New')) self.tree_sem.release() def open_region(self, node, col): self.parent.open_new_window( MemDumpWindow(self.qmp, base=int(node.text(1), 16), max=int(node.text(2), 16)))
class DiscoverUi(QWidget): def __init__(self): super().__init__() self.discover = Discover() self.devices = self.discover.devices self.listWidget = None self.treeWidget = None self.initUI() self.mac_addr = None def initDevices(self): for device in self.devices: newItem = QListWidgetItem() newItem.setText(device.name + " (" + device.address + ")") self.listWidget.addItem(newItem) def initUI(self): self.listWidget = QListWidget() self.initDevices() self.treeWidget = QTreeWidget() self.treeWidget.itemPressed.connect(self.onItemPressed) self.treeWidget.setColumnCount(4) self.treeWidget.setColumnWidth(0, 250) self.treeWidget.setColumnWidth(1, 300) self.treeWidget.setColumnWidth(2, 300) self.treeWidget.setColumnWidth(3, 150) self.treeWidget.setHeaderLabels(["Service", "Service UUID", "Characteristic UUID", "Characteristic Property"]) btn = QPushButton("Read Services") btn.clicked.connect(self.onPushButton) groupDevices = QGroupBox("Devices") groupDevices.setMaximumWidth(300) vbox = QVBoxLayout() vbox.addWidget(self.listWidget) vbox.addWidget(btn) groupDevices.setLayout(vbox) self.btnR = QPushButton("Read") self.btnR.clicked.connect(self.onReadButton) self.btnW = QPushButton("Write") self.btnW.clicked.connect(self.onWriteButton) self.lneI = QLineEdit() self.chkN = QCheckBox("Notify") self.chkN.toggled.connect(self.onNotifyCheck) hbox = QHBoxLayout() hbox.addWidget(self.btnR) hbox.addWidget(self.btnW) hbox.addWidget(self.lneI) hbox.addWidget(self.chkN) groupProperty = QGroupBox("Property") #groupProperty.setLayout(vbox) groupProperty.setLayout(hbox) groupServices = QGroupBox("Services") vbox = QVBoxLayout() vbox.addWidget(self.treeWidget) vbox.addWidget(groupProperty) groupServices.setLayout(vbox) hbox = QHBoxLayout() hbox.addWidget(groupDevices) hbox.addWidget(groupServices) self.setLayout(hbox) self.setGeometry(300, 300, 800, 600) self.setWindowTitle('BLE Discover') self.show() def onPushButton(self): try: self.mac_addr = self.devices[self.listWidget.currentRow()].address self.discover.getServices(self.mac_addr) except: print("Could not get GATT services.") else: svcs = self.discover.svcs for serviceKey, serviceValue in svcs.services.items(): item = QTreeWidgetItem(None, [serviceValue.description, serviceValue.uuid]) for characteristic in serviceValue.characteristics: for property in characteristic.properties: child = QTreeWidgetItem(["", "", characteristic.uuid, property]) item.addChild(child) self.treeWidget.addTopLevelItem(item) def onReadButton(self): byteArray = self.discover.readGattChar(self.mac_addr, self.chosenUuid) text = ''.join('{:02x}'.format(x) for x in byteArray) self.lneI.setText(text) def onWriteButton(self): text = self.lneI.text() print("onWriteButton") self.discover.writeGattChar(self.mac_addr, self.chosenUuid, bytes.fromhex(text)) def notifyCallback(self, sender, data): text = ''.join('{:02x}'.format(x) for x in data) self.lneI.textChanged.emit(text) def onNotifyCheck(self, checked): if checked: print("onNotifyCheck") self.discover.startNotify(self.mac_addr, self.chosenUuid, self.notifyCallback) else: print("onNotifyCheck else") def onItemPressed(self, item, column): if item.child(0) is None: print(item) print(item.text(2)) print(item.text(3)) self.chosenUuid = item.text(2) property = item.text(3) if property == "read": self.btnR.setEnabled(True) self.btnW.setEnabled(False) self.lneI.setEnabled(False) self.chkN.setEnabled(False) elif property == "write": self.btnR.setEnabled(True) self.btnW.setEnabled(True) self.lneI.setEnabled(True) self.chkN.setEnabled(False) elif property == "notify": self.btnR.setEnabled(False) self.btnW.setEnabled(False) self.lneI.setEnabled(False) self.chkN.setEnabled(True)