def __init__(self, main): super(PackageWidgetM, self).__init__() self.setObjectName("PackageWidgetM") self.main_win = main self.game_index = 0 self.selected = [ ] # 已选中的channel及所属game列表 如:[{"game": 当前game字典, "channel": 当前channel字典}, {}, {}] self.selected_name = [ ] # 已选中的渠道显示名称列表 如:["10878922-765321", "", "", ""] self.lbps = {} # 打包信息及进度条组合字典 {"765321": {}, "": {}} self.progress = None self.monitor = PackageMonitor() self.monitor.signal.connect(self.complete) v_layout = QVBoxLayout() h_layout1 = QHBoxLayout() # 全部游戏及其下的渠道列表 self.tool_box = QToolBox(self) self.tool_box.setFixedWidth(100) for game in self.main_win.games: clv = QListView() clv.setEditTriggers(QAbstractItemView.NoEditTriggers) clv.setContextMenuPolicy(Qt.CustomContextMenu) self.tool_box.addItem(clv, game['id']) self.tool_box.currentChanged.connect(self.select_game) self.tool_box.setCurrentIndex(self.game_index) channel_list_area = QScrollArea() channel_list_area.setWidget(self.tool_box) h_layout1.addWidget(channel_list_area, 1) # 已选择的渠道列表 self.cslv_model = QStringListModel() self.cslv_model.setStringList([]) self.cslv = QListView() self.cslv.setModel(self.cslv_model) self.cslv.setEditTriggers(QAbstractItemView.NoEditTriggers) self.cslv.doubleClicked.connect(self.delete_channel) self.cslv.setContextMenuPolicy(Qt.CustomContextMenu) h_layout1.addWidget(self.cslv, 2) # 打包进度条显示列表 self.qpb_list_widget = QListWidget() self.qpb_list_widget.setSelectionMode( QAbstractItemView.SingleSelection) self.qpb_list_widget.itemDoubleClicked.connect(self.select_qpb_list) h_layout1.addWidget(self.qpb_list_widget, 5) v_layout.addLayout(h_layout1) h_layout2 = QHBoxLayout() self.back_btn = QPushButton("返 回") self.back_btn.setFixedWidth(100) self.back_btn.clicked.connect(self.back) h_layout2.addWidget(self.back_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) h_layout2.addSpacing(100) select_apk_btn = QPushButton("选择母包:") select_apk_btn.setFixedWidth(100) select_apk_btn.clicked.connect(self.select_apk) h_layout2.addWidget(select_apk_btn) self.apk_path = QLabel() self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) h_layout2.addWidget(self.apk_path) h_layout2.addSpacing(100) self.pack_btn = QPushButton("打 包") self.pack_btn.setFixedWidth(100) self.pack_btn.clicked.connect(self.click) h_layout2.addWidget(self.pack_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout.addLayout(h_layout2) self.setLayout(v_layout)
class SideView(QObject): onBeforeItemDeletion = pyqtSignal(str) onEntrySelected = pyqtSignal(str) onDirectoryChanged = pyqtSignal(str) onRemoveRequested = pyqtSignal(str) def __init__(self, dirLister, entryProvider: IEntryProvider, newEntryText: str, itemNameNormalizer: IItemNameNormalizer): super().__init__() def initListView(): self.listView = QListView() self.listView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.listView.setModel(self.model) self.listView.setMinimumWidth(200) actionRemove = QAction("Remove", None) self.listView.addAction(actionRemove) self.listView.selectionModel().currentChanged.connect( lambda selectedItem, unselectedItem: self.onEntrySelected.emit( self.itemNameNormalizer.normalizeName( self.currentDir.filePath(selectedItem.data())))) def contextMenu(position): menu = QMenu() index = self.listView.indexAt(position) entry = self.model.data(index, Qt.DisplayRole) deleteAction = None renameAction = None addAction = menu.addAction("Add") if entry is not None: deleteAction = menu.addAction("Delete") renameAction = menu.addAction("Rename") refreshAction = menu.addAction("Refresh") chosenAction = menu.exec_(self.listView.mapToGlobal(position)) if chosenAction is None: return if chosenAction == deleteAction: self.onRemoveRequested.emit(entry) elif chosenAction == renameAction: self.renameEntry(index, entry) elif chosenAction == addAction: self.onCreateNewEntry() elif chosenAction == refreshAction: self.refreshListViewEntries() self.listView.customContextMenuRequested.connect(contextMenu) self.listView.setContextMenuPolicy(Qt.CustomContextMenu) self.currentDir: QDir = None self.model = QStringListModel() self.directoryLister = dirLister self.entryProvider = entryProvider self.newEntryText = newEntryText self.itemNameNormalizer = itemNameNormalizer self.sortingParser: IEntrySorting initListView() def renameEntry(self, index, oldName): def onNewEntryCommited(editedLine: QLineEdit): self.listView.itemDelegate().commitData.disconnect( onNewEntryCommited) try: newName = editedLine.text() normalizedNameOld = self.itemNameNormalizer.normalizeName( oldName) normalizedNameNew = self.itemNameNormalizer.normalizeName( newName) self.emitOnItemDeletion(normalizedNameOld) self.entryProvider.renameEnty(normalizedNameOld, normalizedNameNew) self.sortingParser.rename(oldName, newName) self.refreshListViewEntries() except Exception: pass self.listView.itemDelegate().commitData.connect(onNewEntryCommited) self.listView.edit(index) def emitOnItemDeletion(self, normalizedName): self.onBeforeItemDeletion.emit( self.currentDir.filePath(normalizedName)) def removeEntry(self, entry): normalizedName = self.itemNameNormalizer.normalizeName(entry) self.emitOnItemDeletion(normalizedName) self.entryProvider.removeEntry(normalizedName) self.refreshListViewEntries() def setDirectory(self, dirPath: str): self.currentDir = QDir(dirPath) self.sortingParser = EntrySortingFile( self.currentDir.filePath('.sorting')) self.entryProvider.setContext(dirPath) self.refreshListViewEntries() self.onDirectoryChanged.emit(self.currentDir.absolutePath()) def refreshListViewEntries(self): if self.currentDir is None: return entries = self.directoryLister.listEntries(self.currentDir) sortedEntries = self.sortingParser.getSortedList(entries) self.model.setStringList(sortedEntries) def onCreateNewEntry(self): def onNewEntryCommited(editedLine: QLineEdit): self.listView.itemDelegate().commitData.disconnect( onNewEntryCommited) try: if editedLine.text() == self.newEntryText: raise Exception() normalizedName = self.itemNameNormalizer.normalizeName( editedLine.text()) self.entryProvider.addEntry(normalizedName) self.refreshListViewEntries() self.listView.setCurrentIndex(index) except Exception: print(f"Error adding entry {editedLine.text()}", file=sys.stderr) self.model.removeRow(self.model.rowCount() - 1) if not self.model.insertRow(self.model.rowCount()): return self.listView.itemDelegate().commitData.connect(onNewEntryCommited) index = self.model.index(self.model.rowCount() - 1, 0) self.model.setData(index, self.newEntryText) self.listView.edit(index) pass
class PackageWidgetM(QWidget): def __init__(self, main): super(PackageWidgetM, self).__init__() self.setObjectName("PackageWidgetM") self.main_win = main self.game_index = 0 self.selected = [ ] # 已选中的channel及所属game列表 如:[{"game": 当前game字典, "channel": 当前channel字典}, {}, {}] self.selected_name = [ ] # 已选中的渠道显示名称列表 如:["10878922-765321", "", "", ""] self.lbps = {} # 打包信息及进度条组合字典 {"765321": {}, "": {}} self.progress = None self.monitor = PackageMonitor() self.monitor.signal.connect(self.complete) v_layout = QVBoxLayout() h_layout1 = QHBoxLayout() # 全部游戏及其下的渠道列表 self.tool_box = QToolBox(self) self.tool_box.setFixedWidth(100) for game in self.main_win.games: clv = QListView() clv.setEditTriggers(QAbstractItemView.NoEditTriggers) clv.setContextMenuPolicy(Qt.CustomContextMenu) self.tool_box.addItem(clv, game['id']) self.tool_box.currentChanged.connect(self.select_game) self.tool_box.setCurrentIndex(self.game_index) channel_list_area = QScrollArea() channel_list_area.setWidget(self.tool_box) h_layout1.addWidget(channel_list_area, 1) # 已选择的渠道列表 self.cslv_model = QStringListModel() self.cslv_model.setStringList([]) self.cslv = QListView() self.cslv.setModel(self.cslv_model) self.cslv.setEditTriggers(QAbstractItemView.NoEditTriggers) self.cslv.doubleClicked.connect(self.delete_channel) self.cslv.setContextMenuPolicy(Qt.CustomContextMenu) h_layout1.addWidget(self.cslv, 2) # 打包进度条显示列表 self.qpb_list_widget = QListWidget() self.qpb_list_widget.setSelectionMode( QAbstractItemView.SingleSelection) self.qpb_list_widget.itemDoubleClicked.connect(self.select_qpb_list) h_layout1.addWidget(self.qpb_list_widget, 5) v_layout.addLayout(h_layout1) h_layout2 = QHBoxLayout() self.back_btn = QPushButton("返 回") self.back_btn.setFixedWidth(100) self.back_btn.clicked.connect(self.back) h_layout2.addWidget(self.back_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) h_layout2.addSpacing(100) select_apk_btn = QPushButton("选择母包:") select_apk_btn.setFixedWidth(100) select_apk_btn.clicked.connect(self.select_apk) h_layout2.addWidget(select_apk_btn) self.apk_path = QLabel() self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) h_layout2.addWidget(self.apk_path) h_layout2.addSpacing(100) self.pack_btn = QPushButton("打 包") self.pack_btn.setFixedWidth(100) self.pack_btn.clicked.connect(self.click) h_layout2.addWidget(self.pack_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout.addLayout(h_layout2) self.setLayout(v_layout) def select_game(self, p_int): self.game_index = p_int if 'apk' in self.main_win.games[self.game_index]: self.apk_path.setText(self.main_win.games[self.game_index]['apk']) else: self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) # self.apk_path.setText("请浏览选择本地母包路径") # 当前选中的game,其下的所有渠道列表 self.channels = Utils.get_channels( Utils.get_full_path('games/' + self.main_win.games[p_int]['id'] + '/config.xml')) channel_ids = [] for channel in self.channels: channel_ids.append(channel['channelId']) list_model = QStringListModel() list_model.setStringList(channel_ids) self.clv = self.tool_box.currentWidget() self.clv.doubleClicked.connect(self.select_channel) self.clv.setModel(list_model) # 双击选中当前渠道,更新已选中列表的model def select_channel(self): if 'apk' not in self.main_win.games[self.game_index]: QMessageBox.warning(self, "警告", "请先添加母包!") return channel = self.channels[self.clv.currentIndex().row()] name = self.main_win.games[ self.game_index]['id'] + '-' + channel['channelId'] if name in self.selected_name: return self.selected_name.append(name) self.cslv_model.setStringList(self.selected_name) package = { 'game': self.main_win.games[self.game_index], 'channel': channel } self.selected.append(package) # 双击移除当前渠道,更新已选中列表的model def delete_channel(self): name = self.selected_name[self.cslv.currentIndex().row()] self.selected_name.remove(name) self.cslv_model.setStringList(self.selected_name) package = self.selected[self.cslv.currentIndex().row()] self.selected.remove(package) def select_qpb_list(self): index = self.qpb_list_widget.currentIndex().row() game_id = self.selected[index]['game']['id'] channel_id = self.selected[index]['channel']['channelId'] success = self.lbps[channel_id]['success'] dest_apk_dir = Utils.get_full_path('output/' + game_id + '/' + channel_id) if success: os.startfile(dest_apk_dir) else: QMessageBox.warning(self, "警告", "打包成功了吗?") def back(self): self.monitor.deleteLater() self.main_win.set_main_widget() def select_apk(self): f_name = QFileDialog.getOpenFileName( self, '选择母包', os.path.join(os.path.expanduser('~'), "Desktop"), ("Apk (*.apk)")) if f_name[0]: self.apk_path.setStyleSheet("font-size:12px") self.apk_path.setText(f_name[0]) self.main_win.games[self.game_index]['apk'] = f_name[0] def click(self): if self.pack_btn.text() == "打 包": self.package() elif self.pack_btn.text() == "取 消": self.cancel() def package(self): # 清空上次打包完成后的进度条显示列表 count = self.qpb_list_widget.count() if count > 0: for i in range(count): item = self.qpb_list_widget.takeItem(0) del item if len(self.selected) <= 0: QMessageBox.warning(self, "警告", "请选择需要打包的渠道!") return for package in self.selected: # {"success": 是否成功, "label": 进度条文本view, "qpb": 进度条view, "runnable": 打包任务} lbp = {'success': False} self.set_qpb_list_item(package['channel']['channelId'], lbp) runnable = PackRunnable(package['game'], package['channel'], package['game']['apk']) runnable.signal.signal.connect(self.set_value) self.monitor.add_runnable(runnable) lbp['runnable'] = runnable self.lbps[package['channel']['channelId']] = lbp # 开启监听线程 self.monitor.start() # 开始打包,不可返回,返回按钮禁用;设置打包按钮文本为"取 消" self.back_btn.setDisabled(True) self.pack_btn.setText("取 消") def set_qpb_list_item(self, channel_id, lbp): item = QListWidgetItem(self.qpb_list_widget) item.setSizeHint(QSize(400, 80)) widget = QWidget(self.qpb_list_widget) v_layout = QVBoxLayout() label = QLabel(channel_id + "==>>>等待出包...") v_layout.addWidget(label) lbp['label'] = label qpb = QProgressBar(self.qpb_list_widget) v_layout.addWidget(qpb) lbp['qpb'] = qpb widget.setLayout(v_layout) self.qpb_list_widget.addItem(item) self.qpb_list_widget.setItemWidget(item, widget) def set_value(self, channel_id, result, msg, step): lbp = self.lbps[channel_id] if result: # 打包步骤异常,提示异常,关闭进度条 lbp['label'].setText(channel_id + "==>>>" + msg) lbp['qpb'].close() if step == 100: lbp['success'] = True self.lbps[channel_id] = lbp else: # 打包正常,设置进度条进度 lbp['qpb'].setValue(step) if step == 0: lbp['label'].setText(channel_id + "==>>>" + msg) # 取消打包(全部取消) def cancel(self): self.progress = QProgressDialog(self) self.progress.setFixedWidth(500) self.progress.setFixedHeight(80) self.progress.setWindowTitle("正在取消,请稍等...") self.progress.setCancelButtonText("取消") self.progress.setMinimumDuration(1) self.progress.setWindowModality(Qt.ApplicationModal) self.progress.setRange(0, 0) self.progress.show() # 清空进度条显示列表 count = self.qpb_list_widget.count() for i in range(count): item = self.qpb_list_widget.takeItem(0) del item # 清空任务线程池;线程池清空后,会触发监听线程的完成信号,重置返回和打包按钮 # 因为打包任务调用外部程序,并不能立即终止外部程序连接,所以清空过程有延迟 for channel_id in self.lbps: self.lbps[channel_id]['runnable'].is_close = True self.monitor.clear() # 取消打包(清空任务完成),或打包完成, def complete(self): if self.progress is not None: self.progress.cancel() # 返回按钮解禁;设置打包按钮文本为"打 包" self.back_btn.setDisabled(False) self.pack_btn.setText("打 包")
class ChannelListWidget(QWidget): def __init__(self, main, channels): super(ChannelListWidget, self).__init__() self.main_win = main self.channels = channels self.channel_index = len(channels) - 1 self.linedit_list = [] self.channel_ids = [] for channel in self.channels: self.channel_ids.append(channel['channelId']) self.channel_ids.append(" + 添加渠道") self.setObjectName("ChannelListWidget") v_layout1 = QVBoxLayout() h_layout1 = QHBoxLayout() self.list_model = QStringListModel() self.list_model.setStringList(self.channel_ids) self.channel_list_view = QListView() self.channel_list_view.setModel(self.list_model) self.channel_list_view.setEditTriggers( QAbstractItemView.NoEditTriggers) self.channel_list_view.clicked.connect(self.list_item_onclick) self.channel_list_view.setContextMenuPolicy(Qt.CustomContextMenu) self.channel_list_view.customContextMenuRequested.connect( self.show_del_menu) h_layout1.addWidget(self.channel_list_view, 1) self.channel = self.channels[self.channel_index] form_layout = QFormLayout() form_layout.setContentsMargins(10, 100, 10, 50) form_layout.addRow( "游戏ID:", QLabel(self.main_win.games[self.main_win.game_index]['id'])) self.channel_id_value = QLabel(self.channel['channelId']) self.channel_id_value.setTextInteractionFlags(Qt.TextSelectableByMouse) form_layout.addRow("渠道ID:", self.channel_id_value) self.game_name_value = QLineEdit() form_layout.addRow("游戏名称:", self.game_name_value) self.game_package_value = QLineEdit() form_layout.addRow("游戏包名:", self.game_package_value) self.game_vcode_value = QLineEdit() form_layout.addRow("游戏版本号:", self.game_vcode_value) self.game_vname_value = QLineEdit() form_layout.addRow("游戏版本名:", self.game_vname_value) self.debug_value = QLineEdit() form_layout.addRow("打印日志:", self.debug_value) h_layout1.addLayout(form_layout, 4) self.form_layout2 = QFormLayout() self.form_layout2.setContentsMargins(10, 100, 10, 50) self.set_info() h_layout1.addLayout(self.form_layout2, 4) v_layout1.addLayout(h_layout1) h_layout2 = QHBoxLayout() back_btn = QPushButton("返 回") back_btn.setFixedWidth(100) back_btn.clicked.connect(self.back) h_layout2.addWidget(back_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) save_btn = QPushButton("保 存") save_btn.setFixedWidth(100) save_btn.clicked.connect(self.save_data) h_layout2.addWidget(save_btn, alignment=Qt.AlignHCenter) pack_btn = QPushButton("打 包") pack_btn.setFixedWidth(100) pack_btn.clicked.connect(self.to_package) h_layout2.addWidget(pack_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout1.addLayout(h_layout2) self.setLayout(v_layout1) def set_info(self): self.channel_id_value.setText(self.channel['channelId']) self.game_name_value.setText(self.channel['gameName']) self.game_package_value.setText(self.channel['package']) self.game_vcode_value.setText(self.channel['gameVersionCode']) self.game_vname_value.setText(self.channel['gameVersionName']) self.debug_value.setText(self.channel['debug']) # 先清空之前渠道参数表单,再添加 for i in range(self.form_layout2.rowCount()): # 因为formlayout清除一行,会自动上移,所以只需remove第一行 self.form_layout2.removeRow(0) self.linedit_list.clear() # 再添加当前选择的渠道参数 channel_name = QLabel(self.channel['name'] + '\t\t\tVersion:' + self.channel['sdkVersionName'] + '\t\tUpdate:' + self.channel['sdkUpdateTime']) channel_name.setAlignment(Qt.AlignRight) self.form_layout2.addRow(channel_name) for param in self.channel['sdkParams']: line_edit = QLineEdit(param['value']) self.form_layout2.addRow(param['showName'] + ':', line_edit) self.linedit_list.append(line_edit) def list_item_onclick(self): if self.channel_list_view.currentIndex().row() == len( self.channel_ids) - 1: self.main_win.set_add_channel_widget(self.channels, self.channel) else: self.channel_index = self.channel_list_view.currentIndex().row() self.channel = self.channels[self.channel_index] self.set_info() def back(self): self.main_win.set_game_list_widget(self.main_win.games) def to_package(self): if not self.save_data(): return self.main_win.set_package_widget(self.channels) def save_data(self): self.channel['gameName'] = self.game_name_value.text().strip() self.channel['package'] = self.game_package_value.text().strip() self.channel['gameVersionCode'] = self.game_vcode_value.text().strip() self.channel['gameVersionName'] = self.game_vname_value.text().strip() self.channel['debug'] = self.debug_value.text().strip() i = 0 while i < len(self.linedit_list): if self.linedit_list[i].text().strip() == "": QMessageBox.warning(self, "警告", "渠道参数不能为空!") return False self.channel['sdkParams'][i]['value'] = self.linedit_list[i].text( ).strip() i += 1 self.channels[self.channel_index] = self.channel game_id = self.main_win.games[self.main_win.game_index]['id'] return Utils.update_channels( Utils.get_full_path('games/' + game_id + '/config.xml'), self.channel, self.channel_index) def show_del_menu(self, point): self.list_item_onclick() if -1 < self.channel_index < len(self.channel_ids) - 1: menu = QMenu(self.channel_list_view) del_action = QAction("删 除", menu) del_action.triggered.connect(self.del_channel) menu.addAction(del_action) menu.popup(self.channel_list_view.mapToGlobal(point)) def del_channel(self): reply = QMessageBox.warning(self, "警告", "确定删除当前渠道?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # 更新listview self.channel_ids.pop(self.channel_index) self.list_model.setStringList(self.channel_ids) # 更新表单view(index前移一位) self.channel = self.channels[self.channel_index - 1] self.set_info() # 更新本地数据 self.channels.pop(self.channel_index) game_id = self.main_win.games[self.main_win.game_index]['id'] Utils.del_channel( Utils.get_full_path('games/' + game_id + '/config.xml'), self.channel_index) # 重置index,防止 index out of range self.channel_index = self.channel_index - 1 if self.channel_index < 0: reply = QMessageBox.warning(self, "警告", "当前游戏未添加渠道,将返回游戏列表界面!", QMessageBox.Yes) if reply == QMessageBox.Yes: self.back()