class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) layout = QVBoxLayout(self) # 列表 self.listWidget = QListWidget(self) layout.addWidget(self.listWidget) # 清空按钮 self.clearBtn = QPushButton('清空', self, clicked=self.doClearItem) layout.addWidget(self.clearBtn) # 添加测试数据 self.testData() def doDeleteItem(self, item): # 根据item得到它对应的行数 row = self.listWidget.indexFromItem(item).row() # 删除item item = self.listWidget.takeItem(row) # 删除widget self.listWidget.removeItemWidget(item) del item def doClearItem(self): # 清空所有Item for _ in range(self.listWidget.count()): # 删除item # 一直是0的原因是一直从第一行删,删掉第一行后第二行变成了第一行 # 这个和删除list [] 里的数据是一个道理 item = self.listWidget.takeItem(0) # 删除widget self.listWidget.removeItemWidget(item) del item def testData(self): # 生成测试数据 for i in range(100): item = QListWidgetItem(self.listWidget) widget = ItemWidget('item: {}'.format(i), item, self.listWidget) # 绑定删除信号 widget.itemDeleted.connect(self.doDeleteItem) self.listWidget.setItemWidget(item, widget)
class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.resize(800, 600) layout = QVBoxLayout(self) self.myhwnd = int(self.winId()) # 自己的句柄 layout.addWidget( QPushButton('获取所有可用、可视窗口', self, clicked=self._getWindowList, maximumHeight=30)) layout.addWidget( QLabel('双击列表中的项目则进行嵌入目标窗口到下方\n格式为:句柄|父句柄|标题|类名', self, maximumHeight=30)) self.windowList = QListWidget( self, itemDoubleClicked=self.onItemDoubleClicked, maximumHeight=200) layout.addWidget(self.windowList) def closeEvent(self, event): """窗口关闭""" if self.layout().count() == 4: self.restore() super(Window, self).closeEvent(event) def _getWindowList(self): """清空原来的列表""" self.windowList.clear() win32gui.EnumWindows(self._enumWindows, None) def onItemDoubleClicked(self, item): """列表双击选择事件""" # 先移除掉item self.windowList.takeItem(self.windowList.indexFromItem(item).row()) hwnd, phwnd, _, _ = item.text().split('|') # 开始嵌入 if self.layout().count() == 4: # 如果数量等于4说明之前已经嵌入了一个窗口,现在需要把它释放出来 self.restore() hwnd, phwnd = int(hwnd), int(phwnd) # 嵌入之前的属性 style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE) exstyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) print('save', hwnd, style, exstyle) widget = QWidget.createWindowContainer(QWindow.fromWinId(hwnd)) widget.hwnd = hwnd # 窗口句柄 widget.phwnd = phwnd # 父窗口句柄 widget.style = style # 窗口样式 widget.exstyle = exstyle # 窗口额外样式 self.layout().addWidget(widget) def restore(self): """归还窗口""" # 有bug,归还后窗口没有了WS_VISIBLE样式,不可见 widget = self.layout().itemAt(3).widget() print('restore', widget.hwnd, widget.style, widget.exstyle) win32gui.SetParent(widget.hwnd, widget.phwnd) # 让它返回它的父窗口 win32gui.SetWindowLong(widget.hwnd, win32con.GWL_STYLE, widget.style | win32con.WS_VISIBLE) # 恢复样式 win32gui.SetWindowLong(widget.hwnd, win32con.GWL_EXSTYLE, widget.exstyle) # 恢复样式 win32gui.ShowWindow(widget.hwnd, win32con.SW_SHOW) # 显示窗口 widget.close() self.layout().removeWidget(widget) # 从布局中移出 widget.deleteLater() def _enumWindows(self, hwnd, _): """遍历回调函数""" if hwnd == self.myhwnd: return # 防止自己嵌入自己 if win32gui.IsWindow(hwnd) and win32gui.IsWindowVisible( hwnd) and win32gui.IsWindowEnabled(hwnd): phwnd = win32gui.GetParent(hwnd) title = win32gui.GetWindowText(hwnd) name = win32gui.GetClassName(hwnd) self.windowList.addItem('{0}|{1}|\t标题:{2}\t|\t类名:{3}'.format( hwnd, phwnd, title, name))
class TagStructuresWidget(QWidget): def __init__(self, app): super().__init__() self.images_paths = [] self.selected_image_path = "" self.app = app self.files_list = QListWidget() self.structures_list = QListWidget() self.image_widget = TaggableImageWidget() self.highlighter_checkbox = QCheckBox("Wskaż wybraną strukturę") self.init_ui() def init_ui(self): self.image_widget.set_highlighter_pixmap( QPixmap("./resources/arrow.png")) self.image_widget.hide_highlighter() self.files_list.itemClicked.connect(self.on_files_list_item_clicked) self.files_list.itemActivated.connect(self.on_files_list_item_clicked) self.structures_list.itemChanged.connect( self.on_structure_name_changed) self.structures_list.itemClicked.connect( self.on_structure_name_clicked) self.structures_list.itemActivated.connect( self.on_structure_name_clicked) self.image_widget.on_tag_added.connect(self.on_tag_added) self.highlighter_checkbox.stateChanged.connect( self.set_hightlighter_visibility_state) self.grid = QGridLayout() self.grid.setColumnStretch(0, 1) self.grid.setColumnStretch(1, 3) self.lists_tab = QTabWidget() self.lists_tab.addTab(self.files_list, "Zdjęcia") self.lists_tab.addTab(self.structures_list, "Struktury") self.grid.addWidget(self.lists_tab, 0, 0) self.grid.addWidget(self.image_widget, 0, 1) back_btn = QPushButton("Zapisz i powróć") back_btn.clicked.connect(self.save_and_go_home) save_btn = QPushButton("Zapisz") save_btn.clicked.connect(self.app.save_workspace_structures) btn_layout = QHBoxLayout() btn_layout.addWidget(back_btn) btn_layout.addWidget(save_btn) self.grid.addLayout(btn_layout, 1, 0) self.grid.addWidget(self.highlighter_checkbox, 1, 1) self.setLayout(self.grid) def load_images_paths(self): self.images_paths = load_files_paths(self.app.workspace_path, ("jpeg", "jpg", "png")) self.files_list.clear() for path in self.images_paths: self.files_list.addItem(path) def on_files_list_item_clicked(self, item): self.selected_image_path = item.text() image_path = os.path.join(self.app.workspace_path, self.selected_image_path) self.image_widget.set_pixmap(QPixmap(image_path)) self.image_widget.hide_highlighter() self.image_widget.clear_tags() label_id = 0 self.structures_list.clear() for tagdata in self.app.workspace_structures.setdefault( self.selected_image_path, []): label_id += 1 self.add_tag(label_id, tagdata) def on_tag_added(self, x, y): text, ok = QInputDialog.getText(self, 'Wprowadź strukturę', 'Pol, Łac, Ang:') if ok: structures = self.app.workspace_structures[ self.selected_image_path] tagdata = {'text': text, 'x': x, 'y': y} structures.append(tagdata) label_id = len( self.app.workspace_structures[self.selected_image_path]) self.add_tag(label_id, tagdata) def on_structure_name_changed(self, item): item_id = self.structures_list.indexFromItem(item).row() if len(item.text()) > 0: self.update_structure_name(item_id, item) else: self.delete_structure(item_id, item) def on_structure_name_clicked(self, item): item_id = self.structures_list.indexFromItem(item).row() self.image_widget.highlight_tag_at(item_id) if self.highlighter_checkbox.checkState() == Qt.Checked: self.image_widget.show_highlighter() def update_structure_name(self, item_id, item): # update tag tag = self.image_widget.tag_at(item_id) tag.setToolTip(item.text()) # update structure text structures = self.app.workspace_structures[self.selected_image_path] structures[item_id]['text'] = item.text() def delete_structure(self, item_id, item): # delete from structures structures = self.app.workspace_structures[self.selected_image_path] del structures[item_id] # update list and tags, so IDs can be restored mocked_item = QListWidgetItem() mocked_item.setText(self.selected_image_path) self.on_files_list_item_clicked(mocked_item) def save_and_go_home(self): self.app.save_workspace_structures() self.app.go_home() def add_tag(self, label_id, tagdata): item_widget = QListWidgetItem() item_widget.setText(tagdata['text']) item_widget.setFlags(item_widget.flags() | Qt.ItemIsEditable) self.structures_list.addItem(item_widget) tag_widget = TagWidget(str(label_id), tagdata['text'], tagdata['x'], tagdata['y']) tag_widget.adjust_to_size(self.image_widget.image().size(), 0.015 / 2) self.image_widget.addWidget(tag_widget) def set_hightlighter_visibility_state(self, state): if len(self.structures_list.selectedItems()) > 0 and state: self.image_widget.show_highlighter() else: self.image_widget.hide_highlighter()
class ListParameterWidget(GenericParameterWidget): """Widget class for List parameter.""" def __init__(self, parameter, parent=None): """Constructor .. versionadded:: 2.2 :param parameter: A ListParameter object. :type parameter: ListParameter """ super().__init__(parameter, parent) self._input = QListWidget() self._input.setSelectionMode(QAbstractItemView.MultiSelection) if self._parameter.maximum_item_count != \ self._parameter.minimum_item_count: tool_tip = 'Select between %d and %d items' % ( self._parameter.minimum_item_count, self._parameter.maximum_item_count) else: tool_tip = 'Select exactly %d items' % ( self._parameter.maximum_item_count) self._input.setToolTip(tool_tip) for opt in self._parameter.options_list: item = QListWidgetItem() item.setFlags(item.flags() | Qt.ItemIsEditable) item.setText(str(opt)) self._input.addItem(item) if opt in self._parameter.value: item.setSelected(True) self.inner_input_layout.addWidget(self._input) # override self._input_layout arrangement to make the label at the top # reset the layout self.input_layout.setParent(None) self.help_layout.setParent(None) self.label.setParent(None) self.inner_input_layout.setParent(None) self.input_layout = QVBoxLayout() self.input_layout.setSpacing(0) # put element into layout self.input_layout.addWidget(self.label) self.input_layout.addLayout(self.inner_input_layout) self.main_layout.addLayout(self.input_layout) self.main_layout.addLayout(self.help_layout) def raise_invalid_type_exception(self): message = 'Expecting element type of %s' % ( self._parameter.element_type.__name__) err = ValueError(message) return err def get_parameter(self): """Obtain list parameter object from the current widget state. :returns: A ListParameter from the current state of widget """ selected_value = [] for opt in self._input.selectedItems(): index = self._input.indexFromItem(opt) selected_value.append(self._parameter.options_list[index.row()]) try: self._parameter.value = selected_value except ValueError: err = self.raise_invalid_type_exception() raise err return self._parameter
class ExtendedLineEdit(QLineEdit): signal_send_movie = pyqtSignal(list) signal_search_movie = pyqtSignal(str) signal_request_movie_data = pyqtSignal(int) signal_set_loading = pyqtSignal(str, bool) DEBUG = False def __init__(self, parent=None): super(ExtendedLineEdit, self).__init__(parent) self.movies = [] self.completer_lw = QListWidget() self.model = self.completer_lw.model() self.completer = QCompleter(self.model, self) self.completer.setPopup(self.completer_lw) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.completer.setCompletionRole( Qt.UserRole ) # Change role because default EditRole is paited to list somehow self.setCompleter(self.completer) self.textEdited.connect(self.text_edited) #self.completer.activated[QModelIndex].connect(self.on_completer_activated) #self.completer.activated[str].connect(self.on_completer_activated_str) self.completer_lw.itemClicked.connect(self.item_clicked) self.installEventFilter(self) def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self.completer.complete() return super(ExtendedLineEdit, self).eventFilter(obj, event) def setText(self, text: str) -> None: if self.DEBUG: print('setText', text, self.sender()) super(ExtendedLineEdit, self).setText(text) if not isinstance(self.sender(), QCompleter) and text: self.text_edited(text) @pyqtSlot(list) def update_movies_list(self, result): if self.DEBUG: print('update_movies_list', result[0], len(result) - 1) type = result.pop(0) if type == 'db': self.movies = [x + [ type, ] for x in result] self.completer_lw.clear() self.completer.complete() else: for item in result: self.movies.append(item + [ type, ]) for item in result: cwidget = MyWidget() cwidget.label_movie_name.setText(item[1] if item[1] else item[2]) cwidget.label_original_name.setText(item[2]) cwidget.label_source.setText(type) cwidget.label_year.setText(str(item[3])) completer_myQListWidgetItem = QListWidgetItem(self.completer_lw) completer_myQListWidgetItem.setSizeHint(cwidget.sizeHint()) completer_myQListWidgetItem.setData(Qt.UserRole, item[1]) self.completer_lw.addItem(completer_myQListWidgetItem) self.completer_lw.setItemWidget(completer_myQListWidgetItem, cwidget) if self.hasFocus() and not self.completer_lw.isVisible() and len( self.movies) > 0: self.completer.complete() @pyqtSlot(str) def text_edited(self, text): if self.DEBUG: print('text_edited', text, self.sender()) if text and isinstance(self.sender(), ExtendedLineEdit): self.signal_search_movie.emit(text) @pyqtSlot(str) def on_completer_activated_str(self, name: str): if self.DEBUG: print('on_completer_activated_str', name) @pyqtSlot(QModelIndex) def on_completer_activated(self, index: QModelIndex): if self.DEBUG: print('on_completer_activated', index.row()) item = self.movies[index.row()] if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) @pyqtSlot(QListWidgetItem) def item_clicked(self, item: QListWidgetItem): if self.DEBUG: print('item_clicked', item, [i[1] for i in self.movies]) index = self.completer_lw.indexFromItem(item) item = self.movies[index.row()] type = item[-1] if type == 'db': self.signal_send_movie.emit(item) else: self.signal_set_loading.emit('movie', True) if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) def reset(self): if self.DEBUG: print('reset') self.completer_lw.clear() self.clearFocus()
class SettingsDialog(QDialog): def __init__(self, parent=None): super(SettingsDialog, self).__init__(parent) self.currentDirectories = [] # self.addDirectories(directories) self.initButtons() self.initUi() self.restoreSettings() self.setLibraryDirectoriesWidget() def saveSettings(self): settings = QtCore.QSettings( QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, QtCore.QCoreApplication.organizationName(), QtCore.QCoreApplication.applicationName()) settings.beginGroup("preferences") settings.beginWriteArray('library_directories', len(self.currentDirectories)) for index, value in enumerate(self.currentDirectories): settings.setArrayIndex(index) settings.setValue("url", value) settings.endArray() settings.endGroup() def restoreSettings(self): settings = QtCore.QSettings( QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, QtCore.QCoreApplication.organizationName(), QtCore.QCoreApplication.applicationName()) # settings.setValue("preferences_window/position", self.pos()) settings.beginGroup("preferences") size = settings.beginReadArray('library_directories') if not size == 0: for i in range(0, size): settings.setArrayIndex(i) url = settings.value("url") self.currentDirectories.append(url) settings.endArray() settings.endGroup() def initButtons(self): self.libraryDirectories = QLabel() self.libraryDirectories.setText("Library Directories") self.libraryDirectories.setAlignment(QtCore.Qt.AlignLeft) self.addButton = QPushButton("Add Directory") self.addButton.clicked.connect(self._onAdd) self.removeButton = QPushButton("Remove Directory") self.removeButton.clicked.connect(self._onRemove) self.directoriesList = QListWidget(self) def addDirectories(self, directories): if not isinstance(directories, list): directories = [directories] for directory in directories: self.currentDirectories.append(directory) def setLibraryDirectoriesWidget(self): for directory in self.currentDirectories: newItem = QListWidgetItem(directory, self.directoriesList) def _onAdd(self): directories = self.choose_directory() if not directories: return None for directory in directories: if directory not in self.currentDirectories: self.currentDirectories.append(directory) newItem = QListWidgetItem(directory, self.directoriesList) def _onRemove(self): currentItem = self.directoriesList.currentItem() row = self.directoriesList.indexFromItem(currentItem).row() if currentItem: self.directoriesList.takeItem(row) self.currentDirectories.remove(currentItem.data( QtCore.Qt.DisplayRole)) def choose_directory(self): fileDialog = QFileDialog(self) fileDialog.setAcceptMode(QFileDialog.AcceptOpen) fileDialog.setFileMode(QFileDialog.Directory) fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setWindowTitle("Choose Media Directory") try: fileDialog.setDirectory(QtCore.QStandardPaths.standardLocations( QtCore.QStandardPaths.MusicLocation)[0]) except IndexError: fileDialog.setDirectory(QtCore.QDir.homePath()) if fileDialog.exec_() == QDialog.Accepted: return fileDialog.selectedFiles() def initUi(self): self.directoriesButtonsLayout = QVBoxLayout() self.directoriesButtonsLayout.addWidget(self.addButton) self.directoriesButtonsLayout.addWidget(self.removeButton) self.directoriesButtonsLayout.addSpacing(10) self.directoriesLayout = QHBoxLayout() self.directoriesLayout.addWidget(self.directoriesList) self.directoriesLayout.addLayout(self.directoriesButtonsLayout) self.closeButton = QPushButton("Close") self.saveButton = QPushButton("Save") self.closeButton.clicked.connect(self.reject) self.saveButton.clicked.connect(self.accept) self.bottomButtonsLayout = QHBoxLayout() self.bottomButtonsLayout.addStretch(1) self.bottomButtonsLayout.addSpacing(12) self.bottomButtonsLayout.addWidget(self.saveButton) self.bottomButtonsLayout.addWidget(self.closeButton) mainLayout = QVBoxLayout() mainLayout.addWidget(self.libraryDirectories) mainLayout.addLayout(self.directoriesLayout) mainLayout.addLayout(self.bottomButtonsLayout) self.setLayout(mainLayout) self.setWindowTitle("Settings") self.setMinimumSize(450, 340) self.resize(680, 420)
class TestNinePatchLabel(QWidget): skin_aio_friend_bubble_nor = "skin_aio_friend_bubble_nor.9.png" skin_aio_friend_bubble_pressed = "skin_aio_friend_bubble_pressed.9.png" skin_aio_user_bubble_nor = "skin_aio_user_bubble_nor.9.png" skin_aio_user_bubble_pressed = "skin_aio_user_bubble_pressed.9.png" def __init__(self): super(TestNinePatchLabel, self).__init__() self.resize(400, 600) self.listWidget = QListWidget(self) self.listWidget.setObjectName("listWidget") self.listWidget.setStyleSheet(""" QListWidget::item:selected { background: rgba(0,0,0,0); } QListWidget::item:hover { background: rgba(0,0,0,0); } """) self.textEdit = QTextEdit(self) self.textEdit.setMaximumHeight(100) self.textEdit.setObjectName("textEdit") self.sendBtn = QPushButton("发送", self) self.sendBtn.setObjectName("sendBtn") hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setSpacing(0) hlayout.addWidget(self.textEdit) hlayout.addWidget(self.sendBtn) vlayout = QVBoxLayout(self) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setSpacing(0) vlayout.addWidget(self.listWidget) vlayout.addItem(hlayout) QMetaObject.connectSlotsByName(self) # 通过objectname注册信号 self.init() self.setStyleSheet(SCROLLBARSTYLE) def init(self): '''加载气泡数组''' paths = os.listdir("..\\bubble") self.bubble = [(os.path.join(os.path.join("..\\bubble", path), self.skin_aio_friend_bubble_nor), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_friend_bubble_pressed), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_user_bubble_nor), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_user_bubble_pressed)) for path in paths] @pyqtSlot() def on_sendBtn_clicked(self): self.send() def send(self): '''发送消息''' text = self.textEdit.toPlainText().strip() if not text: return fn, fp, un, up = random.choice(self.bubble) # 从中随机一个 # 我说 item = QListWidgetItem() self.listWidget.addItem(item) pItem = Item(None, "我说: " + text, un, up) item.setSizeHint(pItem.size()) # 把item的大小变得和label一样 self.listWidget.setItemWidget(item, pItem) # 对方说 item = QListWidgetItem() self.listWidget.addItem(item) pItem = Item(None, "她说: " + text, fn, fp) item.setSizeHint(pItem.size()) # 把item的大小变得和label一样 self.listWidget.setItemWidget(item, pItem) self.listWidget.scrollTo(self.listWidget.indexFromItem(item))
class PageWidget(QWidget): ITEMCLICKED = pyqtSignal(CustomPageItem) RES_PATH = "uilib/img" # 图片资源的路径 STYLE = """ QPushButton#_previous_btn,QPushButton#_next_btn {{ max-width: 49px; max-height: 39px; min-width: 49px; min-height: 39px; width: 49px; height: 39px; border-color: #E4E7EA; }} QPushButton#_previous_btn:hover,QPushButton#_next_btn:hover {{ border-color: #1ABC9C; background-color: #1ABC9C; }} QPushButton#_previous_btn:pressed,QPushButton#_next_btn:pressed {{ border-color: #1ABC9C; background-color: #1ABC9C; }} QPushButton#_previous_btn {{ padding-right: 1px; border-top-left-radius: 6px; border-top-right-radius: 0px; border-bottom-left-radius: 6px; border-bottom-right-radius: 0px; background: #E4E7EA url({RES_PATH}/arrow_left_white.png) no-repeat center center; }} QPushButton#_next_btn {{ padding-left: 1px; border-top-left-radius: 0px; border-top-right-radius: 6px; border-bottom-left-radius: 0px; border-bottom-right-radius: 6px; background: #E4E7EA url({RES_PATH}/arrow_right_white.png) no-repeat center center; }} QListView#_page_list_widget {{ max-height: 40px; background-color: rgba(0,0,0,0); }} QListView#_page_list_widget::item {{ margin: 1px; color: white; background-color: #E4E7EA; height: 40px; }} QListView#_page_list_widget::item:selected {{ background-color: #1ABC9C; }} QListView#_page_list_widget::item:focus {{ background-color: #1ABC9C; }} QListView#_page_list_widget::item:hover {{ background-color: #1ABC9C; }}""" def __init__(self, parent = None, pages = 0): super(PageWidget, self).__init__(parent) self.setObjectName("_page_widget") self.pages = pages # 页数 self.cpage = 0 # 左边按钮 self._previous_btn = BothSidesItem(self, which = "left") self._previous_btn.clicked.connect(self._previous) self._previous_btn.setToolTip("上一页") # 右边按钮 self._next_btn = BothSidesItem(self, which = "right") self._next_btn.clicked.connect(self._next) self._next_btn.setToolTip("下一页") self._page_list_widget = QListWidget(self) self._page_list_widget.setObjectName("_page_list_widget") # 无边框 self._page_list_widget.setFrameShape(QFrame.NoFrame) # 无滑动条 self._page_list_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._page_list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 禁止自动滚动 self._page_list_widget.setAutoScroll(False) # tab切换 self._page_list_widget.setTabKeyNavigation(True) # 设置为横向 self._page_list_widget.setFlow(QListView.LeftToRight) # 设置字体 font = QFont() font.setPointSize(14) self._page_list_widget.setFont(font) # item点击事件 self._page_list_widget.itemClicked.connect(self.ITEMCLICKED.emit) # 布局 hLayout = QHBoxLayout(self) hLayout.setSpacing(0) hLayout.setContentsMargins(0, 0, 0, 0) spacerItem = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) hLayout.addWidget(self._previous_btn) hLayout.addWidget(self._page_list_widget) hLayout.addWidget(self._next_btn) hLayout.addItem(spacerItem) self._refresh(pages) self.updateStyle() # 设置样式 def _refresh(self, pages): # 动态调整宽度 if pages == 0: self.resize(100, 40) elif pages >= 8: self._page_list_widget.setMinimumSize(320, 40) self._page_list_widget.resize(320, 40) self.resize(420, 40) self._page_list_widget.clear() # 中间 for i in range(1, pages + 1): CustomPageItem(i, self._page_list_widget) def _previous(self, clicked): '''上一页''' num = int(self.pages / 8) if num == 0: return self.cpage -= 1 if self.cpage >= 0: print("_previous: ", self.cpage) if self.cpage == 0: previousItem = self._page_list_widget.item(0) else: previousItem = self._page_list_widget.item(self.cpage * 8 - 4) self._page_list_widget.scrollTo(self._page_list_widget.indexFromItem(previousItem)) else: self.cpage += 1 def _next(self, clicked): '''下一页''' num = int(self.pages / 8) if num == 0: return self.cpage += 1 if self.cpage <= num: print("_next: ", self.cpage) remainder = self.pages - self.cpage * 8 if remainder > 8: nextItem = self._page_list_widget.item(self.cpage * 8 + 7) else: nextItem = self._page_list_widget.item(self.pages - 1) self._page_list_widget.scrollTo(self._page_list_widget.indexFromItem(nextItem)) else: self.cpage -= 1 def setResPath(self, resPath): '''设置资源路径''' self.RES_PATH = resPath def getResPath(self): return self.RES_PATH def updateStyle(self): '''刷新样式''' self.setStyleSheet(self.STYLE.format(RES_PATH = self.RES_PATH)) def setPages(self, pages): '''设置页数''' self.pages = pages self._refresh(pages) def getPages(self): '''得到当前总的页数''' return self.pages
class InputListParameterWidget(GenericParameterWidget): """Widget class for List parameter.""" def __init__(self, parameter, parent=None): """Constructor .. versionadded:: 2.2 :param parameter: A ListParameter object. :type parameter: InputListParameter """ super().__init__(parameter, parent) # value cache for self._parameter.value # copy the list so the original is unaffected self._value_cache = list(self._parameter.value) self.input = QListWidget() self.input.setSelectionMode(QAbstractItemView.SingleSelection) if self._parameter.maximum_item_count != \ self._parameter.minimum_item_count: tool_tip = 'Select between %d and %d items' % ( self._parameter.minimum_item_count, self._parameter.maximum_item_count) else: tool_tip = 'Select exactly %d items' % ( self._parameter.maximum_item_count) self.input.setToolTip(tool_tip) # arrange widget self.insert_item_input = QLineEdit() vbox_layout = QVBoxLayout() hbox_layout = QHBoxLayout() self.input_add_button = QPushButton('Add') self.input_remove_button = QPushButton('Remove') # arrange line edit, add button, remove button in horizontal layout hbox_layout.addWidget(self.insert_item_input) hbox_layout.addWidget(self.input_add_button) hbox_layout.addWidget(self.input_remove_button) # arrange vertical layout vbox_layout.addLayout(hbox_layout) vbox_layout.addWidget(self.input) self.inner_input_layout.addLayout(vbox_layout) # override self._input_layout arrangement to make the label at the top # reset the layout self.input_layout.setParent(None) self.help_layout.setParent(None) self.label.setParent(None) self.inner_input_layout.setParent(None) self.input_layout = QVBoxLayout() self.input_layout.setSpacing(0) # put element into layout self.input_layout.addWidget(self.label) self.input_layout.addLayout(self.inner_input_layout) self.main_layout.addLayout(self.input_layout) self.main_layout.addLayout(self.help_layout) # connect handler # noinspection PyUnresolvedReferences self.input_add_button.clicked.connect(self.on_add_button_click) # noinspection PyUnresolvedReferences self.input_remove_button.clicked.connect(self.on_remove_button_click) # noinspection PyUnresolvedReferences self.insert_item_input.returnPressed.connect( self.input_add_button.click) # noinspection PyUnresolvedReferences self.input.itemChanged.connect(self.on_row_changed) self.refresh_list() # init row add error handler self._add_row_error_handler = None @property def add_row_error_handler(self): """return error handler if user mistakenly add row of unexpected type :return: a function handler :rtype: () -> None """ return self._add_row_error_handler @add_row_error_handler.setter def add_row_error_handler(self, value): """Set error handler to handle user mistakenly add row of unexpected type """ self._add_row_error_handler = value def refresh_list(self): self.input.clear() if not self._parameter.ordering == InputListParameter.NotOrdered: self._value_cache.sort() if self._parameter.ordering == InputListParameter.DescendingOrder: self._value_cache.reverse() for opt in self._value_cache: item = QListWidgetItem() item.setText(str(opt)) item.setFlags(item.flags() | Qt.ItemIsEditable) self.input.addItem(item) def on_add_button_click(self): try: value = self._parameter.element_type( self.insert_item_input.text()) self._value_cache.append(value) self.refresh_list() except ValueError: err = self.raise_invalid_type_exception() if self.add_row_error_handler is not None: self.add_row_error_handler(err) else: raise err def on_remove_button_click(self): for opt in self.input.selectedItems(): index = self.input.indexFromItem(opt) del self._value_cache[index.row()] self.refresh_list() def on_row_changed(self, item): try: index = self.input.indexFromItem(item).row() prev_value = self._value_cache[index] self._value_cache[index] = self._parameter.element_type( item.text()) self.refresh_list() except ValueError: item.setText(str(prev_value)) self.raise_invalid_type_exception() def raise_invalid_type_exception(self): message = 'Expecting element type of %s' % ( self._parameter.element_type.__name__) err = ValueError(message) return err def get_parameter(self): """Obtain list parameter object from the current widget state. :returns: A ListParameter from the current state of widget """ try: self._parameter.value = self._value_cache except ValueError: err = self.raise_invalid_type_exception() raise err return self._parameter
class TestNinePatchLabel(QWidget): skin_aio_friend_bubble_nor = "skin_aio_friend_bubble_nor.9.png" skin_aio_friend_bubble_pressed = "skin_aio_friend_bubble_pressed.9.png" skin_aio_user_bubble_nor = "skin_aio_user_bubble_nor.9.png" skin_aio_user_bubble_pressed = "skin_aio_user_bubble_pressed.9.png" def __init__(self): super(TestNinePatchLabel, self).__init__() self.resize(400, 600) self.listWidget = QListWidget(self) self.listWidget.setObjectName("listWidget") self.listWidget.setStyleSheet(""" QListWidget::item:selected { background: rgba(0,0,0,0); } QListWidget::item:hover { background: rgba(0,0,0,0); } """) self.textEdit = QTextEdit(self) self.textEdit.setMaximumHeight(100) self.textEdit.setObjectName("textEdit") self.sendBtn = QPushButton("发送", self) self.sendBtn.setObjectName("sendBtn") hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setSpacing(0) hlayout.addWidget(self.textEdit) hlayout.addWidget(self.sendBtn) vlayout = QVBoxLayout(self) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setSpacing(0) vlayout.addWidget(self.listWidget) vlayout.addItem(hlayout) QMetaObject.connectSlotsByName(self) # 通过objectname注册信号 self.init() self.setStyleSheet(SCROLLBARSTYLE) def init(self): '''加载气泡数组''' paths = os.listdir("..\\bubble") self.bubble = [ ( os.path.join(os.path.join("..\\bubble", path), self.skin_aio_friend_bubble_nor), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_friend_bubble_pressed), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_user_bubble_nor), os.path.join(os.path.join("..\\bubble", path), self.skin_aio_user_bubble_pressed) ) for path in paths ] @pyqtSlot() def on_sendBtn_clicked(self): self.send() def send(self): '''发送消息''' text = self.textEdit.toPlainText().strip() if not text: return fn , fp, un, up = random.choice(self.bubble) # 从中随机一个 # 我说 item = QListWidgetItem() self.listWidget.addItem(item) pItem = Item(None, "我说: " + text, un, up) item.setSizeHint(pItem.size()) # 把item的大小变得和label一样 self.listWidget.setItemWidget(item, pItem) # 对方说 item = QListWidgetItem() self.listWidget.addItem(item) pItem = Item(None, "她说: " + text, fn, fp) item.setSizeHint(pItem.size()) # 把item的大小变得和label一样 self.listWidget.setItemWidget(item, pItem) self.listWidget.scrollTo(self.listWidget.indexFromItem(item))
class Example(QWidget): def __init__(self): super().__init__() self.filenames = json_files() if type(self.filenames) is list: self.curr_file = self.filenames[0] else: self.curr_file = self.filenames self.initUI() def initUI(self): self.num = -1 # index for search bar query self.show_save = False # bool for showing unsaved changes dialog self.temp_file = ".temp.csv" self.refresh_file = False # failsafe 1 for itemChanged trigger self.list_1 = QListWidget() lister(file=self.curr_file, target=self.list_1, index=0, mode=0) self.list_1.clicked.connect(self.clear_selection) self.list_1.installEventFilter(self) self.list_items = self.list_1.count( ) # failsafe 2 for itemChanged trigger self.list_1.itemChanged.connect(self.edit_next_item) self.list_1.verticalScrollBar().valueChanged.connect(self.sync_scroll) self.list_2 = QListWidget() lister(file=self.curr_file, target=self.list_2, index=1, mode=0) self.list_2.clicked.connect(self.clear_selection) self.list_3 = QListWidget() lister(file=self.curr_file, target=self.list_3, index=2, mode=0) self.list_3.clicked.connect(self.clear_selection) self.all_lists = [self.list_1, self.list_2, self.list_3] self.menubar = QMenuBar() self.menubar.setNativeMenuBar(False) exit_event = QAction('Exit', self) exit_event.setShortcut('Ctrl+W') exit_event.triggered.connect(app.quit) showAct = QAction('Show extras', self, checkable=True) showAct.setChecked(False) showAct.setShortcut('Ctrl+E') showAct.triggered.connect(self.hide_notes) addAct = QAction('Fields', self) addAct.setShortcut('Ctrl+N') addAct.triggered.connect(self.add_item) fileOpen = QAction('Open file', self) fileOpen.triggered.connect(self.fileDialog) fileOpen.setShortcut('Ctrl+O') fileSave = QAction('Save file', self) fileSave.triggered.connect(self.save) fileSave.triggered.connect(self.refresh_recents) fileSave.setShortcut('Ctrl+S') self.fileRecents = QMenu('Recent file', self) self.refresh_recents() self.toggle_theme = QAction('Toggle theme', self, checkable=True) self.toggle_theme.setChecked(json_theme()) self.toggle_theme.triggered.connect(self.theme) self.toggle_theme.setShortcut('Ctrl+T') self.col_sort_index = QMenu('Sorting column index', self) self.col_sort_index.addAction(QAction(str(0), self)) self.col_sort_index.addAction(QAction(str(1), self)) self.col_sort_index.addAction(QAction(str(2), self)) self.col_sort_index.triggered.connect(self.sort_col_choice) self.col_search_index = QMenu('Searching column index', self) self.col_search_index.addAction(QAction(str(0), self)) self.col_search_index.addAction(QAction(str(1), self)) self.col_search_index.addAction(QAction(str(2), self)) self.col_search_index.triggered.connect(self.search_col_choice) self.sort = QAction('Sort entries', self, checkable=True) self.curr_col = 0 self.search_col = 0 self.sort.triggered.connect(self.refresh_list) self.sort.setShortcut('Ctrl+R') self.addFields = self.menubar.addMenu('Add') self.addFields.addAction(addAct) self.optionMenu = self.menubar.addMenu('Options') self.optionMenu.addAction(exit_event) self.optionMenu.addAction(showAct) self.optionMenu.addAction(self.toggle_theme) self.optionMenu.addMenu(self.col_sort_index) self.optionMenu.addMenu(self.col_search_index) self.optionMenu.addAction(self.sort) self.fileMenu = self.menubar.addMenu('File') self.fileMenu.addAction(fileOpen) self.fileMenu.addAction(fileSave) self.fileMenu.addMenu(self.fileRecents) self.search_bar = QLineEdit() self.search_bar.setPlaceholderText('Search vocab') self.search_bar.setClearButtonEnabled(True) self.search_bar.setMaxLength(10) self.search_bar.returnPressed.connect(self.search_item) self.status_bar = QStatusBar() status(self.status_bar, self.list_1) grid = QGridLayout() grid.setSpacing(10) grid.addWidget(self.menubar, 0, 0) grid.addWidget(self.list_1, 1, 0) grid.addWidget(self.list_2, 1, 1) grid.addWidget(self.list_3, 1, 2) grid.addWidget(self.search_bar, 0, 1) grid.addWidget(self.status_bar) self.theme() self.setLayout(grid) self.setGeometry(*json_window_size()) self.setWindowTitle(f'{split_name(self.curr_file)}') self.show() self.list_1.scrollToBottom() self.list_2.verticalScrollBar().setHidden(True) self.list_3.verticalScrollBar().setHidden(True) self.list_3.setHidden(True) def sync_scroll(self): scroll_location = self.list_1.verticalScrollBar().value() self.list_2.verticalScrollBar().setValue(scroll_location) self.list_3.verticalScrollBar().setValue(scroll_location) def edit_next_item(self, event): """When an item is added and edited on the first col, starts editing its counterpart on the next col""" if self.list_items == self.list_1.count( ) - 2 or self.list_items != self.list_1.count( ) and self.refresh_file == False: item = self.list_2.item(self.list_2.count() - 1) self.list_2.editItem(item) self.list_items = self.list_1.count() def closeEvent(self, event): """Triggered upon program exit, shows a dialog for unsaved changes using a bool""" if self.show_save == True: reply = QMessageBox.question( self, 'Message', "You may have unsaved changes, are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: try: remove(self.temp_file) except: pass event.accept() else: event.ignore() else: pass def sort_col_choice(self, action): self.curr_col = int(action.text()) def search_col_choice(self, action): self.search_col = int(action.text()) def refresh_list(self): """Refreshes the contents of the lists, when sorting is used""" self.save( mode=1 ) # saves a temp copy, with changes, but irreversable sorting introduced clear_lists(self.all_lists) if self.sort.isChecked() == True: mode = 2 else: mode = 0 try: lister(file=self.temp_file, target=self.list_1, index=0, mode=mode, column=self.curr_col) lister(file=self.temp_file, target=self.list_2, index=1, mode=mode, column=self.curr_col) lister(file=self.temp_file, target=self.list_3, index=2, mode=mode, column=self.curr_col) except: lister(file=self.curr_file, target=self.list_1, index=0, mode=mode, column=self.curr_col) lister(file=self.curr_file, target=self.list_2, index=1, mode=mode, column=self.curr_col) lister(file=self.curr_file, target=self.list_3, index=2, mode=mode, column=self.curr_col) def refresh_recents(self): try: file_1 = QAction(self.curr_file, self) self.fileRecents.addAction(file_1) file_1.triggered.connect(self.clickedFileAct) if type(self.filenames) is list: if self.filenames[1] != None: file_2 = QAction(self.filenames[1], self) self.fileRecents.addAction(file_2) file_2.triggered.connect(self.clickedFileAct) if self.filenames[2] != None: file_3 = QAction(self.filenames[2], self) self.fileRecents.addAction(file_3) file_3.triggered.connect(self.clickedFileAct) except: pass def clickedFileAct(self): self.refresh_file = True file = self.sender().text() self.curr_file = file self.setWindowTitle(f'{split_name(self.curr_file)}') clear_lists(self.all_lists) lister(file=self.curr_file, target=self.list_1, index=0) lister(file=self.curr_file, target=self.list_2, index=1) lister(file=self.curr_file, target=self.list_3, index=2) status(self.status_bar, self.list_1) self.theme() self.list_1.scrollToBottom() self.list_3.setHidden(True) self.refresh_file = False def eventFilter(self, source, event): """Item (row) deletion""" if (event.type() == QEvent.ContextMenu and source is self.list_1): menu = QMenu() menu.addAction("Delete row") if menu.exec_(event.globalPos()): item = source.itemAt(event.pos()) try: model = self.list_1.indexFromItem(item) row = model.row() self.show_save = True self.list_1.takeItem(row) self.list_2.takeItem(row) self.list_3.takeItem(row) status(self.status_bar, self.list_1, f'Deleted row number: {row+1}.') self.clearSelection() except: pass return True return super(Example, self).eventFilter(source, event) def hide_notes(self): """Toggles showing the note column and stretches the window for clearer reading of it""" self.list_3.setHidden(not self.list_3.isHidden()) def theme(self): """Sets the theme for the window and its widgets""" palette = QPalette() # dark theme if self.toggle_theme.isChecked() == True: palette.setColor(QPalette.Window, QColor(0, 0, 0)) dark = "background-color: rgb(0, 0, 0); color: rgb(255, 255, 255);" self.menubar.setStyleSheet(dark) self.addFields.setStyleSheet(dark) self.optionMenu.setStyleSheet(dark) self.fileMenu.setStyleSheet(dark) self.search_bar.setStyleSheet( "background-color: rgb(0, 0, 0); color: rgb(255, 255, 255)" ) # border: 0px; for transparency self.status_bar.setStyleSheet(dark) style_items(self.all_lists, dark_theme=True) # light theme elif self.toggle_theme.isChecked() == False: palette.setColor(QPalette.Window, QColor(255, 255, 255)) light = "background-color: rgb(255, 255, 255); color: rgb(0, 0, 0)" self.menubar.setStyleSheet(light) self.addFields.setStyleSheet(light) self.optionMenu.setStyleSheet(light) self.fileMenu.setStyleSheet(light) self.search_bar.setStyleSheet(light) self.status_bar.setStyleSheet(light) style_items(self.all_lists, dark_theme=False) self.setPalette(palette) self.theme_bool = self.toggle_theme.isChecked( ) # used in the save func def search_item(self): """Takes input from the search bar and matches with an item, gets index and scrolls to it, more reusults being qued with the num class var """ query = self.search_bar.text() search = self.all_lists[self.search_col].findItems( query, Qt.MatchContains) status(self.status_bar, self.list_1, f'Found {len(search)} results.') self.clear_selection() # testing search in all column # search_list =[] # for x in range(3): # search_list.append(self.all_lists[x].findItems(query, Qt.MatchContains)) # parent_list = [] # for x in range(3): # for y in range(len(search_list[x])): # parent_list.append(self.all_lists[x]) # replace with x # import itertools # merged = list(itertools.chain.from_iterable(search_list)) # search_dict = dict(zip(parent_list, merged)) # print(search_dict) # print() # print(len(merged)) # print(len(parent_list)) self.num += 1 for i in search: try: model_index = self.all_lists[self.search_col].indexFromItem( search[self.num]) except: self.num = 0 model_index = self.all_lists[self.search_col].indexFromItem( search[self.num]) item_index = model_index.row() self.all_lists[self.search_col].item(item_index).setSelected(True) self.list_1.scrollToItem(self.list_1.item(item_index), QAbstractItemView.PositionAtCenter) def add_item(self): self.show_save = True for x in range(3): if x == 0: lister(file=self.curr_file, target=self.list_1, index=x, mode=1) elif x == 1: lister(file=self.curr_file, target=self.list_2, index=x, mode=1) elif x == 2: lister(file=self.curr_file, target=self.list_3, index=x, mode=1) item = self.list_1.item(self.list_1.count() - 1) self.list_1.editItem(item) status(self.status_bar, self.list_1) self.list_1.scrollToBottom() self.list_2.scrollToBottom() self.list_3.scrollToBottom() def clear_selection(self): """Clears all item slections for aesthetical purposes, but only single clicks""" self.list_1.clearSelection() self.list_2.clearSelection() self.list_3.clearSelection() def fileDialog(self): fname = QFileDialog() path = fname.getOpenFileName(self, 'Open file', getcwd(), filter='csv (*.csv);;') if path[0] == '': # failsafe for canceling the dialog return self.curr_file self.curr_file = path[0] self.setWindowTitle(f'{split_name(self.curr_file)}') clear_lists(self.all_lists) lister(file=self.curr_file, target=self.list_1, index=0) lister(file=self.curr_file, target=self.list_2, index=1) lister(file=self.curr_file, target=self.list_3, index=2) status(self.status_bar, self.list_1) self.theme() def save(self, mode=0): self.show_save = False list1_items = items_text(self.list_1) list2_items = items_text(self.list_2) list3_items = items_text(self.list_3) total_dicts = [] for (a, b, c) in zip(list1_items, list2_items, list3_items): # each letter is a column dictionary = {'word_1': a, 'word_2': b, 'notes': c} total_dicts.append(dictionary) if mode == 0: writer(file=self.curr_file, data=total_dicts) status(self.status_bar, self.list_1, ('Saved current changes.')) try: json_template(theme=self.theme_bool, files=[self.curr_file, None, None], window_size=self.geometry().getRect() ) # current size values of the window except: json_template( ) # bug cannot be avoided, even though used setChecked at the beggining elif mode == 1: self.show_save = True writer(file=self.temp_file, data=total_dicts) # avoids stacking and refreshes recent file actions actions = self.fileRecents.actions() for action in actions: self.fileRecents.removeAction(action)
class Player(QMainWindow): """A simple Media Player using VLC and Qt """ def __init__(self, master=None): if '--clear' in sys.argv: os.remove('.cache') # Read Data if not os.path.exists('AllChannel.json'): print('Reffering channel info from server.') print( 'If you want to customize channel data, generate AllChannel.json file by generate_SKB_channels.py.', file=sys.stderr) from generate_SKB_channels import get_channels channels = dict([(_['ServiceId'], _) for _ in get_channels()]) else: print('Using AllChannel.json for referring channel info.') with open('AllChannel.json', encoding='UTF8') as f: channels = dict([(_['ServiceId'], _) for _ in json.loads(f.read())]) new_stream_db = read_m3u(sys.argv[1]) old_stream_db = read_m3u(sys.argv[2]) stream_urls = [_['multicast'] for _ in new_stream_db] if os.path.exists('.cache'): with open('.cache', 'rb') as f: cache = pickle.loads(f.read()) else: cache = {} # Checking broken stream global vlc_error_count, vlc_error_check, vlc_handle_mode instance = vlc.Instance("--verbose=-1") instance.log_set(vlc_log_callback, None) mediaplayer = instance.media_player_new() mediaplayer.video_set_scale(0.1) print('Checking broken stream...') for url in tqdm.tqdm(stream_urls): if not url in cache: vlc_error_count = 0 vlc_error_check = False mediaplayer.set_media(instance.media_new(url)) mediaplayer.play() time.sleep(3) cache[url] = vlc_error_check with open('.cache', 'wb') as f: f.write(pickle.dumps(cache)) mediaplayer.stop() self.stream_verify = cache vlc_handle_mode = 1 ############################## playlist = remove_unmapped_channel(old_stream_db, stream_urls) playlist = fill_unmapped_channel(playlist, channels) self.channel_info = channels self.playlist = playlist self.stream_urls = stream_urls import pprint #pprint.pprint(update_db) print(len(old_stream_db), len(playlist), len(new_stream_db), len(channels)) # QT Initialize QMainWindow.__init__(self, master) self.setWindowTitle("Media Player") # creating a basic vlc instance self.instance = vlc.Instance("--verbose=-1") self.instance.log_set(vlc_log_callback, None) # creating an empty vlc media player self.mediaplayer = self.instance.media_player_new() self.createUI() def createUI(self): """Set up the user interface, signals & slots """ self.widget = QWidget(self) self.setCentralWidget(self.widget) # In this widget, the video will be drawn if sys.platform == "darwin": # for MacOS from PyQt5.QtWidgets import QMacCocoaViewContainer self.videoframe = QMacCocoaViewContainer(0) else: self.videoframe = QFrame() self.palette = self.videoframe.palette() self.palette.setColor(QPalette.Window, QColor(0, 0, 0)) self.videoframe.setPalette(self.palette) self.videoframe.setAutoFillBackground(True) self.videoframe.setMinimumWidth(720) self.videoframe.setMinimumHeight(480) #self.hbuttonbox = QHBoxLayout() #self.openchannel = QPushButton("Open Channel Data") #self.hbuttonbox.addWidget(self.openchannel) #self.openchannel.clicked.connect(self.PlayPause) self.hcontrolbox = QHBoxLayout() self.hinfobox = QHBoxLayout() self.icon = QLabel() self.icon.setFixedSize(200, 60) self.icon.setAlignment(Qt.AlignCenter) self.hinfobox.addWidget(self.icon) self.vinfobox = QVBoxLayout() self.ch_name = QLabel("Loading...") font = QFont() font.setBold(True) font.setFamily('Malgun Gothic') font.setPointSize(16) self.ch_name.setFont(font) self.vinfobox.addWidget(self.ch_name) self.hservicebox = QHBoxLayout() self.hservicebox.addWidget(QLabel('Service ID ')) self.service_id = QLabel("[#]") self.hservicebox.addWidget(self.service_id) self.vinfobox.addLayout(self.hservicebox) self.hinfobox.addLayout(self.vinfobox) self.hcontrolbox.addLayout(self.hinfobox) self.hcontrolbox.addStretch(1) self.volumeslider = QSlider(Qt.Horizontal, self) self.volumeslider.setMaximum(100) self.volumeslider.setValue(self.mediaplayer.audio_get_volume()) self.volumeslider.setToolTip("Volume") self.hcontrolbox.addWidget(self.volumeslider) self.volumeslider.valueChanged.connect(self.setVolume) # self.channelbox = QVBoxLayout() self.channellist = QListWidget() self.channellist.setFixedWidth(320) self.channellist.itemClicked.connect(self.selectChannel) self.channelbox.addWidget(self.channellist) self.channelfilter = QLineEdit() self.channelfilter.setFixedWidth(320) self.channelfilter.textChanged.connect(self.find_channel) self.channelbox.addWidget(self.channelfilter) self.streambox = QVBoxLayout() self.streamlist = QListWidget() self.streamlist.setFixedWidth(320) self.streamlist.itemClicked.connect(self.selectStream) self.streambox.addWidget(self.streamlist) self.mapbutton = QPushButton("Map") self.mapbutton.clicked.connect(self.map) self.streambox.addWidget(self.mapbutton) self.vboxlayout = QVBoxLayout() self.vboxlayout.addWidget(self.videoframe) self.vboxlayout.addLayout(self.hcontrolbox) self.hboxlayout = QHBoxLayout() self.hboxlayout.addLayout(self.vboxlayout) self.hboxlayout.addLayout(self.channelbox) self.hboxlayout.addLayout(self.streambox) self.widget.setLayout(self.hboxlayout) export = QAction("&Export", self) export.triggered.connect(self.ExportFile) exit = QAction("E&xit", self) exit.triggered.connect(sys.exit) menubar = self.menuBar() filemenu = menubar.addMenu("&File") filemenu.addAction(export) filemenu.addSeparator() filemenu.addAction(exit) self.updatePlaylist() def ExportFile(self): print(os.path.expanduser('~')) #filename = QFileDialog.getSaveFileName(self, "Save File", os.path.expanduser('~'),)[0] filename = QFileDialog.getSaveFileName( self, "Export Playlist File", 'playlist.m3u', "M3U Playlist (*.m3u *.m3u8)", )[0] if not filename: return with open(filename, 'wt', encoding='UTF8') as f: f.write('#EXTM3U\n') for item in [ self.channellist.item(_) for _ in range(self.channellist.count()) ]: if item.checkState(): data = item.data(Qt.UserRole) f.write( f"#EXTINF:-1 tvg-id=\"{data['tvg-id']}\" tvg-logo=\"{data['tvg-logo']}\" tvg-chno=\"{data['tvh-chnum']}\" tvh-chnum=\"{data['tvh-chnum']}\", {data['ch-name']}\n" ) f.write(f"{data['multicast']}\n") filename = QFileDialog.getSaveFileName( self, "Export Channel File", 'Channel.json', "JSON File (*.json)", )[0] if not filename: return channels = [] for item in [ self.channellist.item(_) for _ in range(self.channellist.count()) ]: if item.checkState(): data = item.data(Qt.UserRole) channels.append(self.channel_info[data['tvg-id']]) with open(filename, 'wt', encoding='UTF8') as f: f.write(json.dumps(channels, indent=2)) def setVolume(self, Volume): """Set the volume """ self.mediaplayer.audio_set_volume(Volume) def find_channel(self, text): if text: for item in [ self.channellist.item(_) for _ in range(self.channellist.count()) ]: if item.text().lower().find(text.lower()) >= 0: item.setHidden(False) else: item.setHidden(True) else: for item in [ self.channellist.item(_) for _ in range(self.channellist.count()) ]: item.setHidden(False) def map(self, *args, **kwargs): item = self.channellist.currentItem() item.setCheckState(Qt.Checked) channel = item.data(Qt.UserRole) sitem = self.streamlist.currentItem() radio = self.streamlist.itemWidget(sitem) channel['multicast'] = radio.text() item.setData(Qt.UserRole, channel) self.updateMappedInfo() def playStream(self, stream_url): global vlc_error_count, vlc_error_check vlc_error_count = 0 vlc_error_check = False self.media = self.instance.media_new(stream_url) # put the media in the media player self.mediaplayer.set_media(self.media) # parse the metadata of the file self.media.parse() # set the title of the track as window title self.setWindowTitle(self.media.get_meta(0)) # the media player has to be 'connected' to the QFrame # (otherwise a video would be displayed in it's own window) # this is platform specific! # you have to give the id of the QFrame (or similar object) to # vlc, different platforms have different functions for this if sys.platform.startswith('linux'): # for Linux using the X Server self.mediaplayer.set_xwindow(self.videoframe.winId()) elif sys.platform == "win32": # for Windows self.mediaplayer.set_hwnd(self.videoframe.winId()) elif sys.platform == "darwin": # for MacOS self.mediaplayer.set_nsobject(int(self.videoframe.winId())) self.mediaplayer.play() def selectChannel(self, item): global vlc_error_count, vlc_error_check vlc_error_count = 0 vlc_error_check = False channel = item.data(Qt.UserRole) print(channel) if 'multicast' in channel and channel['multicast']: streams = dict([(item.data(Qt.UserRole), item) for item in [ self.streamlist.item(_) for _ in range(self.streamlist.count()) ]]) if channel['multicast'] in streams: self.selectStream(streams[channel['multicast']]) else: item.setCheckState(Qt.Unchecked) else: item.setCheckState(Qt.Unchecked) self.ch_name.setText(channel['ch-name']) frame = self.getIcon(channel['tvg-logo']) pixmap = QPixmap() pixmap.loadFromData(frame) self.icon.setPixmap( pixmap.scaled(self.icon.width(), self.icon.height(), Qt.KeepAspectRatio)) self.service_id.setText(f"[{channel['tvg-id']}]") def updateStreamRadioState(self, state): if state: found = False for item in [ self.streamlist.item(_) for _ in range(self.streamlist.count()) ]: radio = self.streamlist.itemWidget(item) if radio.isChecked(): self.channellist.currentItem().setCheckState(Qt.Checked) item.setText("") self.streamlist.setCurrentItem(item) self.streamlist.update(self.streamlist.currentIndex()) self.selectStreamImpl(item) found = True break if not found: item = self.streamlist.item(0) self.streamlist.setCurrentItem(item) self.selectStreamImpl(item) def selectStream(self, item): stream_info = item.data(Qt.UserRole) radio = self.streamlist.itemWidget(item) radio.setChecked(True) def selectStreamImpl(self, item): global vlc_error_count, vlc_error_check vlc_error_count = 0 vlc_error_check = False radio = self.streamlist.itemWidget(item) url = radio.text() item.setData(Qt.UserRole, url) print('URL from Radio :', url) if url: self.playStream(url) #citem = self.channellist.currentItem() #data = citem.data(Qt.UserRole) #data['multicast'] = url #citem.setData(Qt.UserRole, data) #print('Saved URL :', url) else: pass #self.mediaplayer.pause() #citem = self.channellist.currentItem() #data = citem.data(Qt.UserRole) #data['multicast'] = None #citem.setData(Qt.UserRole, data) #print('Saved URL :', None) self.updateMappedInfo() def getMappedDict(self): mapped_dict = {} for item in [ self.channellist.item(_) for _ in range(self.channellist.count()) ]: data = item.data(Qt.UserRole) if data['multicast']: if data['multicast'] in mapped_dict: mapped_dict[data['multicast']].append(data['ch-name']) else: mapped_dict[data['multicast']] = [data['ch-name']] return mapped_dict def updateMappedInfo(self): print('UpdateMappedInfo') mapped_dict = self.getMappedDict() for item in [ self.streamlist.item(_) for _ in range(self.streamlist.count()) ]: url = item.data(Qt.UserRole) if url == '__BROKEN__': item.setText("*** BROKEN ***") elif url in mapped_dict: item.setText(f"[{','.join(mapped_dict[url])}]" if mapped_dict[url] else "") else: item.setText("") self.streamlist.update(self.streamlist.indexFromItem(item)) def disableChannel(self): self.channellist.currentItem().setCheckState(Qt.Unchecked) self.channellist.update(self.channellist.currentIndex()) data = self.channellist.currentItem().data(Qt.UserRole) data['multicast'] = None self.channellist.currentItem().setData(Qt.UserRole, data) self.streamlist.update(self.streamlist.currentIndex()) self.streamlist.currentItem().setData(Qt.UserRole, '__BROKEN__') self.updateMappedInfo() #app.processEvents() @functools.lru_cache(maxsize=None) def getIcon(self, url): req = request.Request(url) req.add_header( 'User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0' ) frame = None for _ in range(3): try: resp = request.urlopen(req) frame = resp.read() break except: time.sleep(1) return frame def attachIcon(self, item): channel = item.data(Qt.UserRole) url = channel['tvg-logo'] pixmap = QPixmap() pixmap.loadFromData(self.getIcon(url)) item.setIcon(QIcon(pixmap)) def asyncCacheIcon(self): def asyncThread(listwidget): for item in [ listwidget.item(_) for _ in range(listwidget.count()) ]: channel = item.data(Qt.UserRole) self.attachIcon(item) listwidget.update(listwidget.indexFromItem(item)) threading.Thread(target=asyncThread, args=(self.channellist, ), daemon=True).start() def updatePlaylist(self): self.channellist.clear() for channel in self.playlist: item = QListWidgetItem() item.setData(Qt.UserRole, channel) item.setText(f"[{channel['tvh-chnum']}] {channel['ch-name']}") item.setCheckState(Qt.Checked if channel['multicast'] and not self.stream_verify[channel['multicast']] else Qt.Unchecked) self.channellist.addItem(item) item = QListWidgetItem() item.setData(Qt.UserRole, '') self.streamlist.addItem(item) radio = QRadioButton('Unbound Stream URL') radio.toggled.connect(self.updateStreamRadioState) self.streamlist.setItemWidget(item, radio) for url in self.stream_urls: item = QListWidgetItem() item.setTextAlignment(Qt.AlignRight) item.setData(Qt.UserRole, '__BROKEN__' if self.stream_verify[url] else url) self.streamlist.addItem(item) radio = QRadioButton(url) radio.toggled.connect(self.updateStreamRadioState) self.streamlist.setItemWidget(item, radio) self.updateMappedInfo() self.asyncCacheIcon() def delayedSelectChannel(n): time.sleep(n) self.channellist.setCurrentRow(0) self.selectChannel(self.channellist.currentItem()) threading.Thread(target=delayedSelectChannel, args=(3, ), daemon=True).start()
class ListLayout(QWidget, CueLayout): NAME = 'List Layout' DESCRIPTION = ''' This layout organize the cues in a list: <ul> <li>Unlimited cues; <li>Side panel with playing media-cues; <li>Cues can be moved in the list; <li>Keyboard control; </ul>''' HEADER = ['', 'Cue', 'Duration', 'Progress'] # I need to redefine those from CueLayout key_pressed = CueLayout.key_pressed cue_added = CueLayout.cue_added cue_removed = CueLayout.cue_removed focus_changed = CueLayout.focus_changed def __init__(self, app, **kwds): super().__init__(**kwds) self.mainWindow = app.mainWindow self.menuLayout = self.mainWindow.menuLayout self._cue_items = [] self._playing_widgets = {} self._context_item = None self._show_dbmeter = config['ListLayout']['ShowDbMeters'] == 'True' self._show_seek = config['ListLayout']['ShowSeek'] == 'True' self._accurate_time = config['ListLayout']['ShowAccurate'] == 'True' self._auto_next = config['ListLayout']['AutoNext'] == 'True' # Add layout-specific menus self.showDbMeterAction = QAction(self) self.showDbMeterAction.setCheckable(True) self.showDbMeterAction.setChecked(self._show_dbmeter) self.showDbMeterAction.triggered.connect(self.set_dbmeter_visible) self.showSeekAction = QAction(self) self.showSeekAction.setCheckable(True) self.showSeekAction.setChecked(self._show_seek) self.showSeekAction.triggered.connect(self.set_seek_visible) self.accurateTimingAction = QAction(self) self.accurateTimingAction.setCheckable(True) self.accurateTimingAction.setChecked(self._accurate_time) self.accurateTimingAction.triggered.connect(self.set_accurate_time) self.autoNextAction = QAction(self) self.autoNextAction.setCheckable(True) self.autoNextAction.setChecked(self._auto_next) self.autoNextAction.triggered.connect(self.set_auto_next) self.menuLayout.addAction(self.showDbMeterAction) self.menuLayout.addAction(self.showSeekAction) self.menuLayout.addAction(self.accurateTimingAction) self.menuLayout.addAction(self.autoNextAction) # Add a toolbar to MainWindow self.toolBar = QToolBar(self.mainWindow) self.toolBar.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.playAction = QAction(self) self.playAction.setIcon(QIcon.fromTheme("media-playback-start")) self.playAction.triggered.connect(self.play_current) self.pauseAction = QAction(self) self.pauseAction.setIcon(QIcon.fromTheme("media-playback-pause")) self.pauseAction.triggered.connect(self.pause_current) self.stopAction = QAction(self) self.stopAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.stopAction.triggered.connect(self.stop_current) self.stopAllAction = QAction(self) self.stopAllAction.font().setBold(True) self.stopAllAction.triggered.connect(self.stop_all) self.pauseAllAction = QAction(self) self.pauseAllAction.font().setBold(True) self.pauseAllAction.triggered.connect(self.pause_all) self.restartAllAction = QAction(self) self.restartAllAction.font().setBold(True) self.restartAllAction.triggered.connect(self.restart_all) self.toolBar.addAction(self.playAction) self.toolBar.addAction(self.pauseAction) self.toolBar.addAction(self.stopAction) self.toolBar.addSeparator() self.toolBar.addAction(self.stopAllAction) self.toolBar.addAction(self.pauseAllAction) self.toolBar.addAction(self.restartAllAction) self.mainWindow.addToolBar(self.toolBar) self.hLayout = QHBoxLayout(self) self.hLayout.setContentsMargins(5, 5, 5, 5) # On the left (cue list) self.listView = ListWidget(self) self.listView.context_event.connect(self.context_event) self.listView.key_event.connect(self.onKeyPressEvent) self.listView.drop_move_event.connect(self.move_cue_at) self.listView.drop_copy_event.connect(self._copy_cue_at) self.listView.setHeaderLabels(self.HEADER) self.listView.header().setSectionResizeMode(QHeaderView.Fixed) self.listView.header().setSectionResizeMode(self.HEADER.index('Cue'), QHeaderView.Stretch) self.listView.setColumnWidth(0, 40) self.hLayout.addWidget(self.listView) self.vLayout = QVBoxLayout() self.vLayout.setContentsMargins(0, 0, 0, 0) self.vLayout.setSpacing(2) # On the right (playing media-cues) self.label = QLabel('Playing', self) self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setStyleSheet('font-size: 17pt; font-weight: bold;') self.vLayout.addWidget(self.label) self.playView = QListWidget(self) self.playView.setMinimumWidth(300) self.playView.setMaximumWidth(300) self.playView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.playView.setFocusPolicy(QtCore.Qt.NoFocus) self.playView.setSelectionMode(QAbstractItemView.NoSelection) self.vLayout.addWidget(self.playView) self.hLayout.addLayout(self.vLayout) # Add cue preferences widgets self.add_settings_section(MediaCueGeneral, MediaCue) self.add_settings_section(Appearance) # Context menu actions self.edit_action = QAction(self) self.edit_action.triggered.connect(self.edit_context_cue) self.remove_action = QAction(self) self.remove_action.triggered.connect(self.remove_context_cue) self.select_action = QAction(self) self.select_action.triggered.connect(self.select_context_cue) self.add_context_item(self.edit_action) self.sep1 = self.add_context_separator() self.add_context_item(self.remove_action) self.add_context_item(self.select_action) self.retranslateUi() def retranslateUi(self): self.showDbMeterAction.setText("Show Db-meters") self.showSeekAction.setText("Show seek bars") self.accurateTimingAction.setText('Accurate timing') self.autoNextAction.setText('Auto select next cue') self.playAction.setText("Go") self.pauseAction.setText("Pause") self.stopAction.setText("Stop") self.stopAllAction.setText("Stop All") self.pauseAllAction.setText("Pause All") self.restartAllAction.setText("Restart All") self.edit_action.setText('Edit option') self.remove_action.setText('Remove') self.select_action.setText('Select') def current_cue(self): item = self.current_item() if item is not None: return item.cue def current_item(self): if len(self._cue_items) > 0: return self._cue_items[self.listView.currentIndex().row()] def select_context_cue(self): self._context_item.select() def __add_cue__(self, cue, index): if isinstance(cue, MediaCue): item = MediaItem(cue, self.listView) # Use weak-references for avoid cyclic-references with lambda(s) wself = weakref.ref(self) wcue = weakref.ref(cue) cue.media.on_play.connect(lambda: wself().show_playing(wcue())) cue.media.interrupted.connect(lambda: wself().hide_playing(wcue())) cue.media.stopped.connect(lambda: wself().hide_playing(wcue())) cue.media.error.connect(lambda: wself().hide_playing(wcue())) cue.media.eos.connect(lambda: wself().hide_playing(wcue())) elif isinstance(cue, ActionCue): item = ActionItem(cue) item.cue = cue else: raise Exception('Cue type not supported') item.setFlags(item.flags() & ~QtCore.Qt.ItemIsDropEnabled) if index is None or (index < 0 or index >= len(self._cue_items)): cue['index'] = len(self._cue_items) self.listView.addTopLevelItem(item) self._cue_items.append(item) else: self.listView.insertTopLevelItem(index, item) self._cue_items.insert(index, item) for n in range(index, len(self._cue_items)): self._cue_items[n].cue['index'] = n if isinstance(item, MediaItem): item.init() if len(self._cue_items) == 1: self.listView.setCurrentItem(item) self.listView.setFocus() self.listView.resizeColumnToContents(1) self.cue_added.emit(cue) def set_accurate_time(self, enable): self._accurate_time = enable for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_accurate_time(enable) for item in self._cue_items: if isinstance(item, MediaItem): item.set_accurate_time(enable) def set_auto_next(self, enable): self._auto_next = enable def set_seek_visible(self, visible): self._show_seek = visible for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_seek_visible(visible) def set_dbmeter_visible(self, visible): self._show_dbmeter = visible for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_dbmeter_visible(visible) def show_playing(self, cue): if cue not in self._playing_widgets: media_time = self._cue_items[cue['index']].media_time widget = PlayingMediaWidget(cue, media_time, self.playView) widget.set_dbmeter_visible(self._show_dbmeter) widget.set_seek_visible(self._show_seek) widget.set_accurate_time(self._accurate_time) list_item = QListWidgetItem() list_item.setSizeHint(widget.size()) self.playView.addItem(list_item) self.playView.setItemWidget(list_item, widget) self._playing_widgets[cue] = list_item def hide_playing(self, cue): if cue in self._playing_widgets: list_item = self._playing_widgets.pop(cue) widget = self.playView.itemWidget(list_item) row = self.playView.indexFromItem(list_item).row() self.playView.removeItemWidget(list_item) self.playView.takeItem(row) widget.destroy_widget() def onKeyPressEvent(self, e): if e.key() == QtCore.Qt.Key_Space: if qApp.keyboardModifiers() == Qt.ShiftModifier: cue = self.current_cue() if cue is not None: self.edit_cue(cue) elif qApp.keyboardModifiers() == Qt.ControlModifier: item = self.current_item() if item is not None: item.select() else: cue = self.current_cue() if cue is not None: cue.execute() if self._auto_next: nextitem = self.listView.currentIndex().row() + 1 if nextitem < self.listView.topLevelItemCount(): nextitem = self.listView.topLevelItem(nextitem) self.listView.setCurrentItem(nextitem) else: self.key_pressed.emit(e) e.accept() def play_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.play() else: cue.execute() def pause_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.pause() def stop_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.stop() def context_event(self, event): item = self.listView.itemAt(event.pos()) if item is not None: index = self.listView.indexOfTopLevelItem(item) self._context_item = self._cue_items[index] self.show_context_menu(event.globalPos()) event.accept() def remove_context_cue(self): self.remove_cue(self.get_context_cue()) def edit_context_cue(self): self.edit_cue(self.get_context_cue()) def stop_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): item.cue.media.stop() def pause_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): item.cue.media.pause() def restart_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): if item.cue.media.state == Media.PAUSED: item.cue.media.play() def __remove_cue__(self, cue): index = cue['index'] self.listView.takeTopLevelItem(index) self._cue_items.pop(index) if isinstance(cue, MediaCue): cue.media.interrupt() if cue in self._playing_widgets: self.hide_playing(self._playing_widgets[cue]) for item in self._cue_items[index:]: item.cue['index'] = item.cue['index'] - 1 cue.finalize() self.cue_removed.emit(cue) def move_cue_at(self, old_index, index): self.move_cue(self._cue_items[old_index].cue, index) def _copy_cue_at(self, old_index, index): newcue = CueFactory.clone_cue(self._cue_items[old_index].cue) self.add_cue(newcue, index) def __move_cue__(self, cue, index): item = self._cue_items.pop(cue['index']) self._cue_items.insert(index, item) self.listView.setCurrentItem(item) if isinstance(item, MediaItem): item.init() for n, item in enumerate(self._cue_items): item.cue['index'] = n def get_cues(self, cue_class=Cue): # i -> item return [i.cue for i in self._cue_items if isinstance(i.cue, cue_class)] def get_cue_at(self, index): if index < len(self._cue_items): return self._cue_items[index].cue def get_cue_by_id(self, cue_id): for item in self._cue_items: if item.cue.cue_id() == cue_id: return item.cue def get_selected_cues(self, cue_class=Cue): cues = [] for item in self._cue_items: if item.selected and isinstance(item.cue, cue_class): cues.append(item.cue) return cues def clear_layout(self): while len(self._cue_items) > 0: self.__remove_cue__(self._cue_items[-1].cue) def destroy_layout(self): self.clear_layout() self.menuLayout.clear() self.mainWindow.removeToolBar(self.toolBar) self.toolBar.deleteLater() # Remove context-items self.remove_context_item(self.edit_action) self.remove_context_item(self.sep1) self.remove_context_item(self.remove_action) self.remove_context_item(self.select_action) # Remove settings-sections self.remove_settings_section(Appearance) self.remove_settings_section(MediaCueGeneral) # !! Delete the layout references !! self.deleteLater() def get_context_cue(self): return self._context_item.cue def select_all(self): for item in self._cue_items: if not item.selected: item.select() def deselect_all(self): for item in self._cue_items: if item.selected: item.select() def invert_selection(self): for item in self._cue_items: item.select()
class SmartTileSelectorWidget(QWidget): def __init__(self, viewport): super().__init__() self.smart_tiles = [] self.mappings = [] self.load_tiles() self.layout = QVBoxLayout() self.list_widget = QListWidget(self) for tile in self.smart_tiles: self.list_widget.addItem(tile.name) self.width_label = QLabel("Width: ") self.width_input = QSpinBox(self) self.height_label = QLabel("Height: ") self.height_input = QSpinBox(self) self.width_box = QHBoxLayout() self.width_box.addWidget(self.width_label) self.width_box.addWidget(self.width_input) self.height_box = QHBoxLayout() self.height_box.addWidget(self.height_label) self.height_box.addWidget(self.height_input) self.layout.addWidget(self.list_widget) self.layout.addLayout(self.width_box) self.layout.addLayout(self.height_box) self.setLayout(self.layout) self.current = None self.list_widget.currentItemChanged.connect(self.item_changed) self.width_input.valueChanged.connect(self.set_brush) self.height_input.valueChanged.connect(self.set_brush) self.viewport = viewport def set_brush(self): current = self.get_current() # if current.has_mappings: self.brush = current.as_smart_brush(self.width_input.value(), self.height_input.value(), self.mappings) # else: # self.brush = current.as_brush() if self.brush is not None: ToolContext.current.tile_brush = self.brush def set_tiles(self, tiles): pass def get_current(self): return self.smart_tiles[self.current] def set_current(self, index): self.current = index self.set_brush() def item_changed(self, curr, prev): item_index = self.list_widget.indexFromItem(curr).row() self.set_current(item_index) self.height_input.setValue(self.get_current().height) self.width_input.setValue(self.get_current().width) if self.get_current().has_mappings: self.height_input.setReadOnly(False) self.width_input.setReadOnly(False) else: self.height_input.setReadOnly(True) self.width_input.setReadOnly(True) def load_tiles(self): with open('data/supertux/smarttiles_mapping.csv', 'r') as mappings: reader = csv.reader(mappings) first_row = True for row in reader: if first_row: # Ignore first heading row first_row = False continue mapping = SmartTileMapping() mapping.id = row[0] mapping.top = [int(val) for val in row[1].split("|")] mapping.right = [int(val) for val in row[2].split("|")] mapping.bottom = [int(val) for val in row[3].split("|")] mapping.left = [int(val) for val in row[4].split("|")] self.mappings.append(mapping) with open('data/supertux/smarttiles.csv', 'r') as csvfile: reader = csv.reader(csvfile) first_row = True for row in reader: if first_row: # Ignore first heading row first_row = False continue id = row[0] name = row[1] width = int(row[2]) height = int(row[3]) start = int(row[4]) reverse = row[5] if len( row) >= 6 and row[5] == "true" else False max_width = int(row[6]) if len(row) >= 7 else width max_height = int(row[7]) if len(row) >= 8 else height tile = SmartTile(id, name, width, height, max_width, max_height, start, reverse) mappings = row[8] if len(row) >= 9 else None if mappings is not None: tile.set_mappings(mappings) self.smart_tiles.append(tile)
class TAGenerateTab(QWidget): def __init__(self, ctx): super(TAGenerateTab, self).__init__() self.ctx = ctx self._table_being_updated = False self._order_lists_being_updated = False self._settings_being_updated = False self.create_ui() def create_ui(self): settings_layout = QHBoxLayout() self.order_cluster = TAListMove() self.order_cluster.trigger.connect(self.userchanged_order_lists) order_cluster_layout = QVBoxLayout() order_cluster_layout.addWidget(self.order_cluster) order_cluster_group = QGroupBox("Cluster Order") order_cluster_group.setLayout(order_cluster_layout) self.order_diverse = TAListMove() self.order_diverse.trigger.connect(self.userchanged_order_lists) order_diverse_layout = QVBoxLayout() order_diverse_layout.addWidget(self.order_diverse) order_diverse_group = QGroupBox("Diversify Order") order_diverse_group.setLayout(order_diverse_layout) self.order_manual = QListWidget() btn1 = QPushButton("Create") btn1.clicked.connect(self.buttonclicked_manual_add) btn2 = QPushButton("Delete") btn2.clicked.connect(self.buttonclicked_manual_del) order_manual_btnlst = QHBoxLayout() order_manual_btnlst.addWidget(btn1) order_manual_btnlst.addWidget(btn2) order_manual_btnlst_widget = QWidget() order_manual_btnlst_widget.setLayout(order_manual_btnlst) order_manual_layout = QVBoxLayout() order_manual_layout.addWidget(self.order_manual) order_manual_layout.addWidget(order_manual_btnlst_widget) order_manual_group = QGroupBox("Manual Allocation") order_manual_group.setLayout(order_manual_layout) settings_layout.addWidget(order_cluster_group) settings_layout.addWidget(order_diverse_group) settings_layout.addWidget(order_manual_group) settings_group = QGroupBox("Field Settings") settings_group.setLayout(settings_layout) run_layout = QHBoxLayout() run_settings1_layout = QFormLayout() self.seats_field = QLineEdit() self.seats_field.setValidator(QIntValidator(1, 16, self)) self.seats_field.textChanged.connect(self.userchanged_settings_fields) self.tables_field = QLineEdit() self.tables_field.setValidator(QIntValidator(1, 100, self)) self.tables_field.textChanged.connect(self.userchanged_settings_fields) run_settings1_layout.addRow(QLabel("Num. of Groups"), self.tables_field) run_settings1_layout.addRow(QLabel("Num. of People per Group"), self.seats_field) run_settings1_layout_widget = QWidget() run_settings1_layout_widget.setLayout(run_settings1_layout) run_settings2_layout = QFormLayout() self.number_field = QLineEdit() self.number_field.setValidator(QIntValidator(1, 16, self)) self.number_field.textChanged.connect(self.userchanged_settings_fields) run_settings2_layout.addRow(QLabel("Number of Allocations"), self.number_field) run_advanced_settings_button = QPushButton("Change Settings") run_advanced_settings_button.clicked.connect( self.buttonclicked_advanced_settings) run_settings2_layout.addRow(QLabel("Advanced Settings"), run_advanced_settings_button) run_settings2_layout_widget = QWidget() run_settings2_layout_widget.setLayout(run_settings2_layout) run_settings3_layout = QGridLayout() run_button = QPushButton("Generate Groups!") run_button.clicked.connect(self.buttonclicked_run_allocation) run_settings3_layout_widget = QWidget() run_settings3_layout_widget.setLayout(run_settings3_layout) run_layout.addWidget(run_settings1_layout_widget, 1) run_layout.addWidget(run_settings2_layout_widget, 1) run_layout.addWidget(run_button, 1) run_group = QGroupBox("Allocation Settings") run_group.setLayout(run_layout) layout = QGridLayout() layout.addWidget(settings_group, 0, 0) layout.addWidget(run_group, 1, 0) layout.setRowStretch(0, 1) self.setLayout(layout) def init_order_list(self): for j in self.ctx.app_data.order_cluster: if j not in self.ctx.app_data_manager.get_fields_with_mode( 'cluster'): self.ctx.app_data.order_cluster.remove(j) for j in self.ctx.app_data_manager.get_fields_with_mode('cluster'): if j not in self.ctx.app_data.order_cluster: self.ctx.app_data.order_cluster.append(j) for j in self.ctx.app_data.order_diverse: if j not in self.ctx.app_data_manager.get_fields_with_mode( 'diversify'): self.ctx.app_data.order_diverse.remove(j) for j in self.ctx.app_data_manager.get_fields_with_mode('diversify'): if j not in self.ctx.app_data.order_diverse: self.ctx.app_data.order_diverse.append(j) def update_field_order_lists(self): self.init_order_list() self._order_lists_being_updated = True self.order_cluster.clear() for j in self.ctx.app_data.order_cluster: new_item = QListWidgetItem() new_item.setData(Qt.UserRole, j) new_item.setText(self.ctx.app_data.peopledata_keys[j]) self.order_cluster.addItem(new_item) self.order_diverse.clear() for j in self.ctx.app_data.order_diverse: new_item = QListWidgetItem() new_item.setData(Qt.UserRole, j) new_item.setText(self.ctx.app_data.peopledata_keys[j]) self.order_diverse.addItem(new_item) self._order_lists_being_updated = False def update_manuals_list(self): self.order_manual.clear() for m in self.ctx.app_data.manuals: self.order_manual.addItem("{0}: Table {1}".format( self.ctx.app_data_manager.get_print_labels(m[0]), m[1] + 1)) def update_settings(self): self._settings_being_updated = True self.tables_field.setText(str(self.ctx.app_data.settings['tables'])) self.seats_field.setText(str(self.ctx.app_data.settings['seats'])) self.number_field.setText( str(self.ctx.app_data.settings['nallocations'])) self._settings_being_updated = False def userchanged_order_lists(self): if self._order_lists_being_updated: return self.ctx.app_data.order_cluster = [ self.order_cluster.item(l).data(Qt.UserRole) for l in range(self.order_cluster.count()) ] self.ctx.app_data.order_diverse = [ self.order_diverse.item(l).data(Qt.UserRole) for l in range(self.order_diverse.count()) ] self.ctx.set_unsaved() def buttonclicked_manual_add(self): try: people_list = [ [i, self.ctx.app_data_manager.get_print_labels(i)] for i in range(len(self.ctx.app_data.peopledata_vals)) if i not in [m[0] for m in self.ctx.app_data.manuals] ] except Exception as e: QMessageBox.critical(self, "Error", "Error: {}".format(str(e))) return tables = self.ctx.app_data.settings['tables'] status, person, table = TAManualDialog.get_input( self, people_list, tables) if not status: return self.ctx.app_data.manuals.append([person, table]) self.update_manuals_list() self.ctx.set_unsaved() def buttonclicked_manual_del(self): if not self.order_manual.currentItem(): return index = self.order_manual.indexFromItem( self.order_manual.currentItem()).row() self.ctx.app_data.manuals.pop(index) self.update_manuals_list() self.ctx.set_unsaved() def userchanged_settings_fields(self): if self._settings_being_updated: return try: self.ctx.app_data.settings['tables'] = int( self.tables_field.text()) if self.tables_field.text() else 0 self.ctx.app_data.settings['seats'] = int( self.seats_field.text()) if self.seats_field.text() else 0 self.ctx.app_data.settings['nallocations'] = int( self.number_field.text()) if self.number_field.text() else 0 except Exception as e: QMessageBox.critical( self, "Error", "Error occurred while processing your entry: {}".format( str(e))) self.ctx.set_unsaved() def buttonclicked_advanced_settings(self): try: attempts_default = self.ctx.app_data.settings['nattempts'] seed_default = self.ctx.app_data.settings['seed'] status, attempts, seed = TAAdvancedSettingsDialog.get_input( self, attempts_default, seed_default) if not status: return self.ctx.app_data.settings['nattempts'] = attempts self.ctx.app_data.settings['seed'] = seed except Exception as e: QMessageBox.critical( self, "Error", "Error occurred while processing your entry: {}".format( str(e))) self.ctx.set_unsaved() def buttonclicked_run_allocation(self): attempts = self.ctx.app_data.settings['nattempts'] progress_bar = QProgressDialog("Generating table allocations...", "", 0, attempts, self.ctx.window) progress_bar.setWindowTitle("Generating...") progress_bar.setWindowModality(Qt.WindowModal) progress_bar.setAutoClose(False) progress_bar.setMinimumDuration(0) progress_bar.setCancelButton(None) progress_bar.show() progress_bar.setValue(0) try: self.ctx.ta_manager.run(progress_bar) except Exception as e: progress_bar.close() QMessageBox.critical( self, "Error", "An error occured during allocation: {}".format(str(e))) return progress_bar.close() QMessageBox.information( self, "Success!", "The allocations were successfully computed. Average number of links is {:.2f} ({:.2f} % of max)." .format(self.ctx.ta_manager.links, 100 * self.ctx.ta_manager.links_rel)) self.ctx.set_unsaved() self.ctx.window.tabs.results_updated() self.ctx.window.results_menu.setEnabled(True)