class DownloaderDialog(JDialog): def __init__(self): super().__init__() self.setWindowIcon(QIcon('GUI\jinndl.ico')) self.setInitialSize(800, 800) self.initUi() self.setActions() def initUi(self): self.setWindowTitle("Download Jinn") self.windowLayout = QVBoxLayout(self) # Advanced options # Stored in a QFrame, this is hidden or shown by self.btnAdvancedOptions self.topBar = QHBoxLayout() self.btnAdvancedOptions = QPushButton("Show advanced options") self.comboBranch = LabelledComboBox("Choose branch:") self.comboBranch.cb.addItems(["HJinn", "LJinn", "master"]) self.advancedOptionsLayout = QHBoxLayout() self.advancedOptionsLayout.addWidget(self.comboBranch) self.advancedOptionsFrame = QFrame() self.advancedOptionsFrame.setHidden(True) self.advancedOptionsFrame.setLayout(self.advancedOptionsLayout) self.topBar.addWidget(self.btnAdvancedOptions) self.topBar.addWidget(self.advancedOptionsFrame) self.updateLog = JTextEdit( "Press 'Download' below to download the latest Jinn code to the USB memory stick" ) self.statusLayout = QVBoxLayout() self.statusLayout.addWidget(self.updateLog) self.btnDownload = JPushButton("Download") self.btnCancel = JCancelButton("Cancel") self.buttonBtm = QHBoxLayout() self.buttonBtm.addWidget(self.btnDownload) self.buttonBtm.addWidget(self.btnCancel) self.windowLayout.addLayout(self.topBar) self.windowLayout.addLayout(self.statusLayout) self.windowLayout.addLayout(self.buttonBtm) def setActions(self): self.btnAdvancedOptions.clicked.connect(self.showAdvancedOptions) self.btnDownload.clicked.connect(self.doDownload) self.btnCancel.clicked.connect(self.reject) def showAdvancedOptions(self): if self.advancedOptionsFrame.isHidden(): self.advancedOptionsFrame.show() self.btnAdvancedOptions.setText("Hide advanced options") else: self.advancedOptionsFrame.hide() self.btnAdvancedOptions.setText("Show advanced options") def getBranch(self): return self.comboBranch.cb.currentText() def doDownload(self): branch = self.getBranch() updateVariables = UpdateVariables(branch) try: cwd = os.getcwd() dirname = os.path.basename(cwd) if dirname.upper() == updateVariables.codeDir.upper(): raise Exception( "This script must not be run with the current directory being \"{}\"" ", because it needs to replace that directory".format(cwd)) # compute the root directory (`Jinn`) via where this script is being run from stdOut = setRootDir() updateJTextEdit(self.updateLog, stdOut) # download the zip file from github to the `Jinn` directory downloadZipFile(self.updateLog, updateVariables) # extract from the zip file to create `Jinn-master` directory in `Jinn` directory extractFromZipFile(self.updateLog, updateVariables) updateJTextEdit( self.updateLog, "Finished downloading and extracting latest Jinn code. Please insert the USB into your " "work computer and run the installer.") except Exception as ex: raise ex InfoMsgBox( "Finished", "Download has finished. You can now close the downloader and remove the USB stick. To continue " "upgrading Jinn plug this USB stick into your work PC and run the 'Jinn updater' shortcut", "Download finished").exec()
class Filter: locks = dict() def __init__(self, filter_content): self.filter_content = filter_content self.filter = dict() self.frame = QFrame() self.frame.setObjectName("Filter_frame") self.frame.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) actual_layout = QVBoxLayout() widget_frame = QFrame() self.layout = QVBoxLayout() widget_frame.setLayout(self.layout) actual_layout.addWidget(widget_frame) actual_layout.addStretch(1) self.filter_names = [] self.frame.setLayout(actual_layout) self.frame.setHidden(True) def add_range(self, name, capitalize=False): min_input = QLineEdit() max_input = QLineEdit() min_input.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) min_input.setMaximumWidth(50) max_input.setMaximumWidth(50) if capitalize: name = name.capitalize() title = QLabel(name) dash = QLabel("-") frame = QFrame() layout = QHBoxLayout() layout.addWidget(min_input) layout.addStretch(0) layout.addWidget(dash) layout.addStretch(0) layout.addWidget(max_input) frame.setLayout(layout) # frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.layout.addWidget(title) self.layout.addWidget(frame) min_input.textChanged.connect( lambda: self.set_range_filter(name, min_input, max_input)) max_input.textChanged.connect( lambda: self.set_range_filter(name, min_input, max_input)) def set_range_filter(self, name, min_input, max_input): name = name.lower() minimum = -9001 maximum = 9001 try: minimum = int(min_input.text()) except: pass try: maximum = int(max_input.text()) except: pass if minimum == -9001 and maximum == 9001: if name in self.filter.keys(): del self.filter[name] else: self.filter[name] = [minimum, maximum] self.filter_content() def add_dropdown(self, name, options, suboptions=None, default=None, alphabetical=True): if alphabetical: options.sort() combo_box = QComboBox() combo_box.addItem("Any") combo_box.addItems(options) if suboptions is not None: sub_combo_box = QComboBox() sub_combo_box.setHidden(True) else: sub_combo_box = None label = QLabel(name) self.filter_names.append(name) self.layout.addWidget(label) self.layout.addWidget(combo_box) if suboptions is not None: self.layout.addWidget(sub_combo_box) sub_combo_box.currentIndexChanged.connect( lambda: self.set_sub_filters(name, combo_box, sub_combo_box)) combo_box.currentIndexChanged.connect(lambda: self.set_filters( name, combo_box, sub_combo_box, suboptions)) if default is not None: index = combo_box.findText(default, QtCore.Qt.MatchFixedString) if index >= 0: combo_box.setCurrentIndex(index) def set_sub_filters(self, name, combo_box, sub_combo_box): name = name.lower() main = combo_box.currentText().lower() sub = sub_combo_box.currentText().lower() if sub == "any": self.filter[name] = main + ".*" else: self.filter[name] = main + ".*(\ \(|\,\ )(" + sub + ").*" self.filter_content() def set_filters(self, name, combo_box, sub_combo_box=None, suboptions=None): main = combo_box.currentText() sub_cond = sub_combo_box is not None if sub_cond: sub = sub_combo_box.currentText() name = name.lower() if main == "Any" and name in self.filter.keys(): del self.filter[name] if sub_cond: sub_combo_box.setHidden(True) else: if sub_cond: if main in suboptions.keys(): _suboptions = [ subopt for subopt in suboptions[main] if subopt != "Any" ] # remove Any as suboption sub_combo_box.setHidden(False) sub_combo_box.clear() sub_combo_box.addItem("Any") sub_combo_box.addItems(_suboptions) else: sub_combo_box.clear() sub_combo_box.setHidden(True) self.filter[name] = main + ".*" # print(self.filter[name]) self.filter_content() def lock(self, attr, value): self.locks[attr] = value def get_frame(self): return self.frame def toggle_hidden(self): if self.frame.isHidden(): self.frame.setHidden(False) else: self.frame.setHidden(True) def evaluate_filter(self, entry): cond = True for key, arg in self.locks.items(): if not hasattr(entry, key) or getattr(entry, key) != arg: return False for key, arg in self.filter.items(): if not hasattr(entry, key): # print("Wrong filter key passed to entry in SearchableTable") return False attr = getattr(entry, key) if type(arg) is str and type( attr ) is str: # single attribute, single argument. Easy as pie p = re.compile('{}'.format(arg), re.IGNORECASE) cond = cond and (p.match(attr)) elif type( attr ) is list: # single argument, multiple attributes, eval individually for each element p = re.compile('{}'.format(arg), re.IGNORECASE) cond = cond and any([p.match(_attr) for _attr in attr]) elif type( arg ) is list: # numerical range, must be inbetween two values if attr is None or None in arg: continue attr = eval("float({})".format(attr)) cond = cond and (arg[0] <= attr <= arg[1]) return cond def clear_filters(self): for i in reversed(range(self.layout.count())): self.layout.removeItem(self.layout.itemAt(i)) self.filter = dict()
class kdDesktopAssistant(QMainWindow): def __init__(self): super().__init__() loadUi(get_file_realpath("kdDesktopAssistant.ui"), self) icon = QIcon(get_file_realpath('data/image/logo.png')) self.setWindowIcon(icon) self.setWindowFlag(Qt.FramelessWindowHint) # self.setStyleSheet("#MainWindow{border-image:url("+get_file_realpath("data/image/S60922-232113.jpg").replace("\\","/") +");}") self.gl_apps.setAlignment(Qt.AlignTop) # 右键菜单设置 self.pop_menu = QMenu() # TODO 待实现的右键功能:QAction("导出配置"),QAction("导入配置") self.pop_menu_item = [QAction("新增启动项"),QAction("新增桌面"),QAction("设置背景图片"),QAction("修改桌面"),QAction("删除桌面"),QAction("设置为主桌面"),QAction("小程序模式"),QAction("退出")] self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested[QPoint].connect(self.handle_pop_menu) # 系统托盘 sys_tray = QSystemTrayIcon(self) self.sys_tray = sys_tray sys_tray.setIcon(icon) sys_tray.activated.connect(self.sys_tray_handler) sys_tray_menu = QMenu() sys_tray_menu.menu_items =[ QAction("退出",self,triggered=sys.exit)] sys_tray.setContextMenu(sys_tray_menu) sys_tray.show() self.session_bt_size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.session_btn_size = QSize(100, 25) # 初始化桌面 print(QDesktopWidget().screenGeometry().height()) print(self.gl_apps.geometry().height()) self.vetical_widget_number = math.floor((QDesktopWidget().screenGeometry().height() - 150) / get_option_int("item_size")) self.home_session = get_option_value("home_session") self.init_session() # 初始化对话框对象 self.dl_launch_item_detail = dl_launch_item_detail() self.session = session() self.wg_catelog = QFrame() self.wg_catelog.setWindowFlags(Qt.Popup) self.gl_catelog = QGridLayout() self.wg_catelog.setLayout(self.gl_catelog) # self.gl_catelog.setWindowOpacity(0.5) # self.wg_catelog.setAttribute(Qt.WA_TranslucentBackground,True ) self.wg_catelog.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool) self.wg_catelog.setAutoFillBackground(True) # self.wg_catelog.setStyleSheet("background-image:url("+get_file_realpath("data/image/bg_catelog.gif") + ");background-size:cover;}") p = QPalette() p.setBrush(QPalette.Background, QBrush(QPixmap(get_file_realpath("data/image/bg_catelog.jpg")))) self.wg_catelog.setPalette(p) # op = QGraphicsOpacityEffect() # op.setOpacity(0.9) # self.wg_catelog.setGraphicsEffect(op) # self.wg_catelog.setStyleSheet("QWidget#wg_catelog{background-color:#99cc99;}") def remove_item(self,item): item.setParent(None) self.gl_apps.removeWidget(item) del item def edit_lauchn_item(self,item, widget): self.dl_launch_item_detail.set_item(item) promt_info = "修改" if self.dl_launch_item_detail.exec_(): if not item: item = {} item["id"] = None promt_info = "新增" item["ico"] = self.dl_launch_item_detail.ico_path item["name"] = self.dl_launch_item_detail.le_name.text() item["url"] = self.dl_launch_item_detail.le_url.text().strip() item["type"] = self.dl_launch_item_detail.bg_type.checkedButton().type item["session_id"] = self.cur_session["id"] try : if item["id"]: app_data.update_launch_item(item) widget.update_item(item) else: app_data.insert_launch_item(item) self.add_launch_item(item) QMessageBox.information(None, promt_info + "启动项", promt_info + "启动项成功" , QMessageBox.Yes) except Exception as e: QMessageBox.warning(None, promt_info + "启动项失败", str(e), QMessageBox.Yes) raise e def edit_session(self,item): self.session.set_session(item) promt_info = "修改" if self.session.exec_(): if not item: item = {} item["id"] = None promt_info = "新增" item["type"] = 0 item["color"] = None item["name"] = self.session.le_name.text() item["picture"] = self.session.le_picture.text() try : if item["id"]: app_data.update_session(item) self.setStyleSheet("#MainWindow{border-image:url("+item["picture"] + ");}") # widget.update_item(item) else: app_data.insert_session_item(item) self.add_session(item) QMessageBox.information(None, promt_info + "桌面", promt_info + "桌面成功" , QMessageBox.Yes) except Exception as e: QMessageBox.warning(None, promt_info + "桌面失败", str(e), QMessageBox.Yes) raise e def add_launch_item(self,item): li = launch_item(item) li.del_item_signal.connect(self.remove_item) li.edit_item_signal.connect(self.edit_lauchn_item) li.click_catelog_signal.connect(self.on_catelog_clicked) li.repaint_session_signal.connect(self.repaint_session) self.gl_apps.addWidget(li, self.row, self.col, 1, 1) self.row += 1 if self.row >= self.vetical_widget_number: self.row = 0 self.col += 1 def repaint_session(self): self.init_launch_items(self.cur_session) def on_catelog_clicked(self,catelog_id): # 清空layout上的组件 if not self.wg_catelog.isHidden(): self.wg_catelog.hide() return count = self.gl_catelog.count() for i in reversed(range(count)) : self.gl_catelog.itemAt(i).widget().setParent(None) item_list = app_data.get_launch_item_list_by_catelog(catelog_id) if not item_list : return max_column = math.floor(len(item_list) ** 0.5) row, col = 1, 0 for i in item_list : item_dict = app_data.tuple2dict_launch_item(i) li = launch_item(item_dict) li.del_item_signal.connect(self.remove_item) li.edit_item_signal.connect(self.edit_lauchn_item) li.click_catelog_signal.connect(self.on_catelog_clicked) self.gl_apps.addWidget(li, self.row, self.col, 1, 1) self.gl_catelog.addWidget(li) # p = QCursor.pos() # if p.y() > self.gl_apps.geometry().height() /2 : # self.wg_catelog.move(p.x() + 10,p.y() - self.wg_catelog.geometry().height()) # else : # self.wg_catelog.move(p.x() + 10,p.y()+10) self.wg_catelog.show() # self.wg_catelog.active() def init_launch_items(self,item): # 高亮当前桌面的按钮 count = self.hl_session.count() for i in reversed(range(count)) : self.hl_session.itemAt(i).widget().setStyleSheet("QPushButton{background-color:#FFFFFF}") self.cur_pb_session.setStyleSheet("QPushButton{background-color:#FF9900}") self.row = 0 self.col = 0 # 清空gridlayout上的所有组件 count = self.gl_apps.count() print("gl_apps:" , count) # 一个老外给的方法,很棒。https://stackoverflow.com/questions/4528347/clear-all-widgets-in-a-layout-in-pyqt for i in reversed(range(count)) : self.gl_apps.itemAt(i).widget().setParent(None) # 设置背景图片 session_id = item["id"] picture = item["picture"] if picture : self.setStyleSheet("#MainWindow{background-image:url("+picture + ");background-size:cover;}") if not session_id : return # 初始化启动项 self.item_list = app_data.get_launch_item_list(session_id) for item in self.item_list: item_dict = app_data.tuple2dict_launch_item(item) self.add_launch_item(item_dict) def init_session(self): # 清空旧的桌面按钮 count = self.hl_session.count() for i in reversed(range(count)) : self.hl_session.itemAt(i).widget().setParent(None) self.session_list = app_data.get_session_list() initedSession = False first_pb_session = None for item in self.session_list: item_dict = app_data.tuple2dict_session(item) pb_session = self.add_session(item_dict) if not first_pb_session: first_pb_session = pb_session if item_dict["name"] == self.home_session : initedSession = True self.cur_pb_session = pb_session self.init_launch_items(item_dict) self.cur_session = item_dict # 默认桌面未初始化,默认初始化第一个桌面 if not initedSession and len(self.session_list) > 0: item = self.session_list[0] item_dict = app_data.tuple2dict_session(item) self.cur_pb_session = first_pb_session self.init_launch_items(item_dict) self.cur_session = item_dict def add_session(self,item): # 新建桌面按钮 pb_session = QPushButton(item["name"]) pb_session.setSizePolicy(self.session_bt_size_policy) pb_session.setMaximumSize(self.session_btn_size) # 设置桌面按钮为半透明 op = QGraphicsOpacityEffect() op.setOpacity(0.9) pb_session.setGraphicsEffect(op) pb_session.setAutoFillBackground(True) # TODO 待删除 # pb_session.session_id = item["id"] # pb_session.picture = item["picture"] pb_session.item = item pb_session.clicked.connect(self.on_pb_session_clicked) self.hl_session.addWidget(pb_session) return pb_session def on_pb_session_clicked(self): sender = self.sender() self.cur_session = sender.item self.cur_pb_session = sender self.init_launch_items(self.cur_session) def handle_pop_menu(self): action = self.pop_menu.exec_(self.pop_menu_item,QCursor.pos()) if action: action_text = action.text() if action_text == "新增启动项" : self.edit_lauchn_item(None,None) elif action_text == "新增桌面" : if self.session.exec_() : session_item = {} session_item["name"] = self.session.le_name.text() session_item["picture"] = self.session.le_picture.text() session_item["type"] = 0 session_item["color"] = None session_item["id"] = app_data.insert_session_item(session_item) print(session_item) pb_session = self.add_session(session_item) self.init_launch_items(session_item) self.cur_session = session_item QMessageBox.information(self, "新增桌面", "新增桌面成功") elif action_text in ( "设置背景图片","修改桌面") : self.edit_session(self.cur_session) elif action_text == "设置为主桌面" : set_home_session(self.cur_session["name"]) QMessageBox.information(self, "主桌面设置", "设置为主桌面成功") elif action_text == "小程序模式" : web_as_application = get_option_boolean("web_as_application") set_web_mode(not web_as_application) if not web_as_application: QMessageBox.information(self, "启动小程序模式", "每个网页将作为一个应用程序打开") else : QMessageBox.information(self, "关闭小程序模式", "每个网页将作为一个标签也打开") QMessageBox.information(self, "关闭小程序模式", "每个网页将作为一个标签也打开") elif action_text == "退出" : sys.exit() elif action_text == "删除桌面" : reply = QMessageBox.warning(self, "确认删除桌面?", "该桌面的所有启动项都会被删除,并不可还原,确认删除?",QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes : app_data.delete_session(self.cur_session["id"]) self.init_session() QMessageBox.information(self, "删除桌面", "删除桌面成功",QMessageBox.Ok) def toggle_window_status(self): if self.isHidden() : self.showMaximized() self.activateWindow() else : self.hide() def sys_tray_handler(self,reason): if reason == 2 or reason == 3 : self.toggle_window_status() elif reason == 1 : menu = self.sender().contextMenu() menu.exec_(menu.menu_items,QCursor.pos()) else : sys.exit() def closeEvent(self,event): self.hide() self.sys_tray.show() event.ignore() def mouseReleaseEvent(self,event): print("on mouseReleaseEvent") self.wg_catelog.hide() def keyReleaseEvent(self,event): key = event.key() if event.modifiers()== Qt.ControlModifier and key == Qt.Key_V : clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasText(): print("准备粘贴:" + clipboard.text()) path = clipboard.text() item = {} if not path.startswith("file") : item = self.dl_launch_item_detail.get_url_info(path) item["session_id"] = self.cur_session["id"] item["id"] = app_data.insert_launch_item(item) self.add_launch_item(item) return path = path.replace("file:///","") if os.name == "posix" : path = "/" + path provider = QFileIconProvider() fi = QFileInfo(path) icon = provider.icon(fi) # t = icon.pixmap().toImage().text() save_path = join(config_dir ,"data/image/sysico",splitext(path)[0]+".ico") print("save_path:" + save_path) icon.pixmap(48).save(save_path) t = icon.name() t1 = icon.themeName() t2 = icon.themeSearchPaths() print("icon path:" + t+"," + t1,t2) if not icon.isNull() : item["ico"] = save_path item["name"] = basename(path) item["url"] = path item["type"] = 2 if not self.cur_session["id"]: QMessageBox.information(self, "新桌面", "新桌面需重启后才能添加启动项") return item["session_id"] = self.cur_session["id"] print("add other launch item:" ,item) item["id"] = app_data.insert_launch_item(item) print(item) self.add_launch_item(item)
class DMTool(QMainWindow): SEARCH_BOX_WIDTH = 200 def __init__(self, db_path, validate_views=False): super().__init__() self._database_path = db_path sys.excepthook = self.excepthook self.settings = dict({"query_srd": True}) self.load_meta() self._setup_ui() self._setup_menu() self.bind_signals() self.load_session() self._display_ui() if validate_views: self.validate_views() def _setup_ui(self): """ Layout is a windowLayout with a horizontal box on the left and a tab widget on the right :return: """ self.setStyleSheet( open(os.path.join("assets", "styles", "default.css")).read()) self.setWindowIcon(QIcon(os.path.join('assets', 'tear.png'))) self.setWindowTitle("RainyDM") self.setGeometry(100, 100, 1280, 720) self.window_frame = QFrame() self.window_layout = QHBoxLayout() # Left side tab self.tab_widget = QTabWidget() # Viewers # - monster viewer monster_button_bar = QFrame() monster_button_bar_layout = QHBoxLayout() monster_button_bar.setLayout(monster_button_bar_layout) self.monster_viewer = MonsterViewer(monster_button_bar, self.system) monster_viewer_frame_layout = QVBoxLayout() self.monster_viewer_frame = QFrame() self.monster_viewer_frame.setLayout(monster_viewer_frame_layout) self.monster_viewer_frame.layout().setContentsMargins(0, 0, 0, 0) self.monster_viewer_frame.setFrameStyle(0) # - spell viewer self.spell_viewer = SpellViewer(self.system) # - item viewer self.item_viewer = ItemViewer(self.system) # Text box self.text_box = QTextEdit() self.text_box.setObjectName("OutputField") self.text_box.setReadOnly(True) self.text_box.setFontPointSize(10) ## Tables # Spell Table self.spell_table_widget = SpellTableWidget(self, self.spell_viewer) # Monster table self.monster_table_widget = MonsterTableWidget(self, self.monster_viewer) # Item table self.item_table_widget = ItemTableWidget(self, self.item_viewer) self.item_table_widget.layout().addWidget(self.item_viewer) self.load_resources(self._database_path) # Loot Generator Widget self.lootViewer = ItemViewer(self.system) self.loot_widget = TreasureHoardTab(self, self.item_viewer, self.item_table_widget) # Initiative list self.encounterWidget = EncounterWidget(self.monster_viewer) # bookmark self.bookmark_widget = BookmarkWidget(self.monster_table_widget, self.monster_viewer, self.spell_table_widget, self.spell_viewer) encounter_frame = QFrame() encounter_layout = QVBoxLayout() encounter_layout.addWidget(self.encounterWidget) encounter_layout.addWidget(self.bookmark_widget) encounter_frame.setLayout(encounter_layout) # player tab player_table_frame = QFrame() player_table_layout = QVBoxLayout() encounter_button_layout = QHBoxLayout() self.add_player_button = QPushButton("Add Player") self.add_player_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.playerWidget = PlayerTable() encounter_button_layout.addWidget(self.add_player_button) encounter_button_layout.addStretch(0) player_table_layout.addWidget(self.playerWidget) player_table_layout.addLayout(encounter_button_layout) player_table_frame.setLayout(player_table_layout) self.monster_viewer_bar = QFrame() self.monster_viewer_bar.setContentsMargins(0, 0, 0, 0) monster_viewer_frame_layout.addWidget(self.monster_viewer) monster_viewer_frame_layout.addWidget(monster_button_bar) monster_viewer_frame_layout.addWidget(self.monster_viewer_bar) monster_viewer_frame_layout.setStretch(3, 1) monster_viewer_frame_layout.setStretch(0, 2) middle_frame_layout = QVBoxLayout() self.middle_frame = QTabWidget() self.middle_frame.setLayout(middle_frame_layout) self.middle_frame.setContentsMargins(0, 0, 0, 0) layout = QHBoxLayout() monster_plaintext_button = QPushButton("Copy plaintext to clipboard") monster_plaintext_button.clicked.connect( self.copy_plaintext_monster_to_clipboard) layout.addWidget(monster_plaintext_button) self.monster_viewer_bar.setLayout(layout) # middle_frame_layout.addWidget(self.spell_viewer) middle_frame_layout.setStretch(0, 2) middle_frame_layout.setContentsMargins(0, 0, 0, 0) # Leftmost tab self.tab_widget.addTab(encounter_frame, "Encounter") self.tab_widget.addTab(player_table_frame, "Players") self.tab_widget.addTab(self.loot_widget, "Loot") # Center tab self.middle_frame.addTab(self.monster_table_widget, "Monster") self.middle_frame.addTab(self.spell_table_widget, "Spell") self.middle_frame.addTab(self.item_table_widget, "Item") self.middle_frame.addTab(self.text_box, "Text Box") # Right frame self.right_tab = QTabWidget() self.right_tab.addTab(self.monster_viewer_frame, "Monster") self.right_tab.addTab(self.spell_viewer, "Spell") self.right_tab.addTab(self.item_viewer, "Item") self.window_layout.addWidget(self.tab_widget) self.window_layout.addWidget(self.middle_frame) self.window_layout.addWidget(self.right_tab) self._set_widget_stretch(GlobalParameters.MAIN_TOOL_POSITION, GlobalParameters.MAIN_TOOL_STRETCH) self._set_widget_stretch(GlobalParameters.MIDDLE_FRAME_POSITION, 0) self._set_widget_stretch(GlobalParameters.RIGHT_FRAME_POSITION, GlobalParameters.RIGHT_FRAME_STRETCH) self.monster_viewer_bar.setHidden(True) self.window_frame.setLayout(self.window_layout) def _setup_menu(self): ### Menubar menu = self.menuBar() version = menu.addMenu("Version") # button_3_5 = QAction("3.5 Edition", self) # button_3_5.setStatusTip("3.5 Edition") # version.addAction(button_3_5) button_5 = QAction("5th Edition", self) button_5.setStatusTip("5th Edition") version.addAction(button_5) button_5.triggered.connect(lambda: self.change_version(System.DnD5e)) button_sw5e = QAction("SW 5th Edition", self) button_sw5e.setStatusTip("SW 5th Edition") version.addAction(button_sw5e) button_sw5e.triggered.connect(lambda: self.change_version(System.SW5e)) # button_3_5.triggered.connect(lambda: self.change_version("3.5")) experimental = menu.addMenu("Experimental") button_plain_text = QAction("Plain text monsters", self, checkable=True) button_plain_text.setStatusTip("Plain text monsters") button_plain_text.triggered.connect(self.toggle_monster_bar) # raise_exception = QAction("Raise Exception", self) # raise_exception.setStatusTip("Raise an Exception") # raise_exception.triggered.connect(self.raise_exception) self.edit_entries_action = QAction("Edit Entries", self, checkable=True) self.edit_entries_action.setStatusTip("Enable edit data entries") # development self.edit_entries_action.setChecked(True) # default ON self.enable_edit_data_entries() ## self.edit_entries_action.triggered.connect( self.enable_edit_data_entries) experimental.addAction(button_plain_text) experimental.addAction(self.edit_entries_action) # experimental.addAction(raise_exception) self.window_frame.setLayout(self.window_layout) # def raise_exception(self): # raise EnvironmentError("Forced an exception") def enable_edit_data_entries(self): cond = self.edit_entries_action.isChecked() self.monster_table_widget.EDITABLE = False # not for monsters self.spell_table_widget.EDITABLE = cond self.item_table_widget.EDITABLE = cond def bind_signals(self): self.encounterWidget.add_players_button.clicked.connect( self.addPlayersToCombat) self.encounterWidget.sort_init_button.clicked.connect( self.sort_init_handle) self.encounterWidget.roll_init_button.clicked.connect( self.roll_init_handle) self.encounterWidget.save_encounter_button.clicked.connect( self.encounterWidget.save) self.encounterWidget.load_encounter_button.clicked.connect( lambda: self.encounterWidget.load(self.monster_table_widget)) self.encounterWidget.clear_encounter_button.clicked.connect( self.clear_encounter_handle) self.bookmark_widget.clear_bookmark_button.clicked.connect( self.clear_bookmark_handle) self.bookmark_widget.toggle_bookmark_button.clicked.connect( self.toggle_bookmark_handle) self.add_player_button.clicked.connect(self.add_player) sNexus.attackSignal.connect(self.attackSlot) sNexus.addSpellsSignal.connect(self.addSpellsToBookmark) sNexus.printSignal.connect(self.print) sNexus.addMonstersToEncounter.connect( self.encounterWidget.addMonsterToEncounter) sNexus.setWidgetStretch.connect(self._set_widget_stretch) sNexus.viewerSelectChanged.connect(self.viewer_select_changed) def _display_ui(self): self.setCentralWidget(self.window_frame) def _set_widget_stretch(self, widget, stretch): self.window_layout.setStretch(widget, stretch) def toggle_monster_bar(self): if self.monster_viewer_bar.isHidden(): self.monster_viewer_bar.setHidden(False) else: self.monster_viewer_bar.setHidden(True) def copy_plaintext_monster_to_clipboard(self): pyperclip.copy(html2text.html2text(self.monster_viewer.html)) def viewer_select_changed(self, idx): self.right_tab.setCurrentIndex(idx) def change_version(self, version): if self.system == version: return self.system = version self.clear_bookmark_handle() self.clear_encounter_handle() self.monster_table_widget.table.clear() self.spell_table_widget.table.clear() self.item_table_widget.table.clear() self.monster_table_widget.filter.clear_filters() self.load_resources(self._database_path) def addMonsterToBookmark(self, monster): row_position = self.bookmark_widget.monster_bookmark.rowCount() self.bookmark_widget.monster_bookmark.insertRow(row_position) if type(monster) == list: for itt, value in enumerate(monster): self.bookmark_widget.monster_bookmark.setItem( row_position, itt, QTableWidgetItem(str(value))) else: self.bookmark_widget.monster_bookmark.setItem( row_position, 0, QTableWidgetItem(str(monster.name))) self.bookmark_widget.monster_bookmark.setItem( row_position, 1, QTableWidgetItem(str(monster.index))) def addSpellsToBookmark(self, spells): for spell in spells: _spell = self.spell_table_widget.find_entry('name', spell) self.add_to_bookmark_spell(_spell) def add_to_bookmark_spell(self, spell): row_position = self.bookmark_widget.spell_bookmark.rowCount() self.bookmark_widget.spell_bookmark.insertRow(row_position) if type(spell) == list: for itt, value in enumerate(spell): self.bookmark_widget.spell_bookmark.setItem( row_position, itt, QTableWidgetItem(str(value))) elif spell is not None: self.bookmark_widget.spell_bookmark.setItem( row_position, 0, QTableWidgetItem(str(spell.name))) self.bookmark_widget.spell_bookmark.setItem( row_position, 1, QTableWidgetItem(str(spell.index))) self.bookmark_widget.spell_bookmark.setItem( row_position, 2, QTableWidgetItem(str(spell.level))) def load_resources(self, database_path): self.db = RainyDatabase(database_path, system=self.system) self.item_table_widget.set_entries(self.db.get_items()) self.item_table_widget.fill_table() self.item_table_widget.define_filters(self.system) self.monster_table_widget.set_entries(self.db.get_monsters()) self.monster_table_widget.fill_table() self.monster_table_widget.define_filters(self.system) # self.spell_table_widget.load_all("./spell", "{}/{}/Spells/".format(resource_path, self.version), spell_cls) self.spell_table_widget.set_entries(self.db.get_spells()) self.spell_table_widget.fill_table() self.spell_table_widget.define_filters(self.system) def add_player(self, player=None): self.playerWidget.add(PlayerFrame(self.playerWidget)) def addPlayersToCombat(self): encounterWidget = self.encounterWidget characterNames = encounterWidget.getCharacterNames() # Get active players for entry in self.playerWidget.m_widgetList: # character in encounter, and should be if entry.getCharacter().getCharName( ) in characterNames and entry.isEnabled(): # print("Character in encounter, and should be") encounterWidget.update_character(entry.getCharacter()) # character in encounter, but shouldn't be elif entry.getCharacter().getCharName( ) in characterNames and not entry.isEnabled(): # print("Character in enocunter, shouldn't be") # print(entry.getCharacter().getCharName(), entry.isEnabled()) encounterWidget.remove_character(entry.getCharacter()) # character not in encounter, but should be elif entry.getCharacter().getCharName( ) not in characterNames and entry.isEnabled(): # print("Character not in encounter, should be") encounterWidget.addPlayerToEncounter(entry.getCharacter()) # character not in encounter, and shouldn't be else: pass def sort_init_handle(self): self.encounterWidget.sortInitiative() def roll_init_handle(self): self.encounterWidget.rollInitiative() def clear_encounter_handle(self): self.encounterWidget.clear() def clear_bookmark_handle(self): self.bookmark_widget.monster_bookmark.clear() self.bookmark_widget.monster_bookmark.setRowCount(0) self.bookmark_widget.spell_bookmark.clear() self.bookmark_widget.spell_bookmark.setRowCount(0) def toggle_bookmark_handle(self): self.bookmark_widget.toggle_hide() def print(self, s): self.middle_frame.setCurrentIndex(GlobalParameters.TEXT_BOX_INDEX) self.text_box.append(s) def print_attack(self, monster_name, attack): attack = attack.strip(" ") comp = attack.split("|") if attack == "": s = "{} used an action".format(monster_name) else: s = "{} uses {} -- ".format(monster_name, comp[0]) if comp[1] not in [ "", " " ]: # this means there's an attack roll and a damage roll attack_roll = roll_function("1d20+" + comp[1]) s = s + "{}({}) to hit".format(attack_roll, attack_roll - int(comp[1])) damage_roll = roll_function(comp[2]) if damage_roll is not None: if type(damage_roll) is list: halved = [max(1, int(dr / 2)) for dr in damage_roll] else: # print("\trainydm - print_attack: \"{}\"".format(damage_roll)) halved = max(1, int(damage_roll / 2)) s = s + " -- for {} ({} halved)".format( str(damage_roll), str(halved)) self.print(s) def extract_and_add_spellbook(self, monster): spells = monster.extract_spellbook() if spells is not None: for spell in spells: spell_entry = self.spell_table_widget.find_entry("name", spell) if spell_entry is not None: self.add_to_bookmark_spell(spell_entry) else: print("Failed to locate spell for", monster.name, "with spellname {}".format(spell)) def load_meta(self): if not os.path.exists("metadata/"): os.mkdir("metadata") meta_path = os.path.join("metadata", "meta.txt") if os.path.exists(meta_path): with open(meta_path, "r") as f: meta_dict = json.load(f) self.system = System.from_plaintext(meta_dict['system']) self.settings = meta_dict['settings'] else: self.system = System.DnD5e def load_session(self): if not os.path.exists("metadata/"): os.mkdir("metadata") if os.path.exists("metadata/session.txt"): with open("metadata/session.txt", "r") as f: meta_dict = eval(f.read()) for monster_tuple in meta_dict['bookmark_meta']: self.addMonsterToBookmark(monster_tuple) for player in meta_dict['player_meta']: player_dict = json.loads(player) self.playerWidget.add( PlayerFrame(self.playerWidget, charName=player_dict["characterName"], playerName=player_dict["playerName"], init=player_dict["initiative"], perception=player_dict["perception"], insight=player_dict["insight"], isEnabled=player_dict["isEnabled"])) for entry in meta_dict['initiative_meta']: entry_dict = json.loads(entry) if entry_dict["type"] == "Monster": self.encounterWidget.add( MonsterWidget(self.monster_table_widget.find_entry( "name", entry_dict["monster"]), self.encounterWidget, viewer=self.monster_viewer, init=entry_dict["init"], hp=entry_dict["hp"])) elif entry_dict["type"] == "Player": self.encounterWidget.add( PlayerWidget( self.encounterWidget, self.playerWidget.findCharacterByName( entry_dict["name"]))) def closeEvent(self, event): bookmark_meta = self.bookmark_widget.monster_bookmark.jsonlify() initiative_meta = self.encounterWidget.jsonlify() player_meta = self.playerWidget.jsonlify() with open("metadata/session.txt", "w") as f: json.dump( dict(bookmark_meta=bookmark_meta, initiative_meta=initiative_meta, player_meta=player_meta), f) with open("metadata/meta.txt", "w") as f: json.dump( dict(system=System.to_plaintext(self.system), settings=self.settings), f) # SLOTS @pyqtSlot(str, str) def attackSlot(self, name, attack): self.print_attack(name, attack) self.middle_frame.setCurrentIndex(GlobalParameters.TEXT_BOX_INDEX) def excepthook(self, type, value, tb): box = QMessageBox() box.setWindowTitle("Oof!") box.setText("RainyDM has crashed!") box.setIcon(QMessageBox.Critical) details = "".join(traceback.format_exception(type, value, tb)) box.setDetailedText(details) spacer = QSpacerItem(500, 0, QSizePolicy.Minimum, QSizePolicy.Minimum) box.layout().addItem(spacer, box.layout().rowCount(), 0, 1, box.layout().columnCount()) box.exec_() old_excepthook(type, value, tb) self.close() def validate_views(self): for spell in self.db.get_spells().values(): self.spell_viewer.draw_view(spell) for monster in self.db.get_monsters().values(): self.monster_viewer.draw_view(monster) for item in self.db.get_items().values(): self.item_viewer.draw_view(item)
class UpdaterDialog(JDialog): def __init__(self): super().__init__() self.setInitialSize(800, 800) self.initUi() self.setActions() def initUi(self): self.setWindowTitle("Update Jinn") self.windowLayout = QVBoxLayout(self) self.topBar = QHBoxLayout() self.middleLayout = QHBoxLayout() self.advancedOptionsLayout = QVBoxLayout() self.advancedButtonLayout = QHBoxLayout() self.buttonBtm = QHBoxLayout() self.advancedOptionsFrame = QFrame() self.advancedOptionsFrame.setHidden(True) self.updateLog = JTextEdit( "Use this on your work PC. Press 'Update' to copy the Jinn code on this USB stick to " "this PC. The old code will be saved incase it is needed.") self.updateLog.setReadOnly(True) self.btnAdvancedOptions = JPushButton("Show advanced options") self.btnLocateJinn = JPushButton("Locate Jinn installation") self.btnUpdate = JPushButton("Update") self.btnCreateShortcut = JPushButton("Create desktop shortcut") self.btnCancel = JCancelButton("Cancel") self.codeDir = DirectorySelector(caption="Select Code directory") self.codeDir.leDirname.setText("C:/Jinn/Code") self.advancedOptionsLayout.addWidget(self.codeDir) self.advancedButtonLayout.addWidget(self.btnCreateShortcut) self.advancedButtonLayout.addWidget(self.btnLocateJinn) self.advancedOptionsLayout.addLayout(self.advancedButtonLayout) self.advancedOptionsFrame.setLayout(self.advancedOptionsLayout) self.buttonBtm.addWidget(self.btnUpdate) self.buttonBtm.addWidget(self.btnCancel) self.topBar.addWidget(self.btnAdvancedOptions) self.middleLayout.addWidget(self.advancedOptionsFrame) self.windowLayout.addLayout(self.topBar) self.windowLayout.addLayout(self.middleLayout) self.windowLayout.addStretch() self.windowLayout.addWidget(self.updateLog) self.windowLayout.addLayout(self.buttonBtm) def setActions(self): self.btnLocateJinn.clicked.connect(self.locateCodeFolder) self.btnUpdate.clicked.connect(self.updateCode) self.btnCreateShortcut.clicked.connect(self.createShortcut) self.btnCancel.clicked.connect(self.reject) self.btnAdvancedOptions.clicked.connect(self.showAdvancedOptions) def showAdvancedOptions(self): if self.advancedOptionsFrame.isHidden(): self.advancedOptionsFrame.show() self.btnAdvancedOptions.setText("Hide advanced options") else: self.advancedOptionsFrame.hide() self.btnAdvancedOptions.setText("Show advanced options") def createShortcut(self): path = self.codeDir.leDirname.text() try: print("Attempting shortcut to path variable 'python3'") createShortcut(path, 'python3') except: print("'python3' failed using 'python'") createShortcut(path, 'python') def locateCodeFolder(self): root = "C:\\" pattern = '..\Jinn\Code\home.py' # Want to find all Jinn\Code paths # Performs a walk through operating system paths and looks for the match "Jinn\Code" # Potential for failure if user has multiple Jinns for some reason try: possibleDirs = [] for path, dirs, files in os.walk(os.path.abspath(root)): if pathlib.PurePath(path).match('*/Jinn/Code'): possibleDirs.append(path) print(possibleDirs) except Exception as ex: raise ex if len(possibleDirs) > 1: multipleJinnDialog = MultipleJinnDialog(possibleDirs) with multipleJinnDialog.delayedDeleteOnClose(): multipleJinnDialog.exec() path = multipleJinnDialog.getPath() else: path = possibleDirs[0] self.codeDir.leDirname.setText(str(path)) def updateCode(self): updateVariables = UpdateVariables() # Rename old code directory and then move new code and rename it codeDirPath = self.codeDir.leDirname.text() codeDirPath = os.path.abspath(codeDirPath) oldCodeFolder = updateVariables.getOldCodeDirWithDateTime() jinnDirPath = os.path.join(codeDirPath, "..") codeDirAlreadyExists = os.path.exists(codeDirPath) if codeDirAlreadyExists: updateJTextEdit( self.updateLog, "Rename existing \"{}\" directory to \"{}\", and ".format( codeDirPath, oldCodeFolder)) updateJTextEdit( self.updateLog, "Rename \"{}\" to \"{}\"\n".format(codeDirPath, updateVariables.codeDir)) # rename existing `Code` directory to `OldCode` if codeDirAlreadyExists: oldCodeDirPath = os.path.join(jinnDirPath, oldCodeFolder) updateJTextEdit( self.updateLog, "Renaming \"{}\" to \"{}\"\n".format(codeDirPath, oldCodeDirPath)) os.rename(codeDirPath, oldCodeDirPath) # rename directory from extracted zip file to `Code` updateJTextEdit( self.updateLog, "Renaming \"{}\" to \"{}\"\n".format(codeDirPath, codeDirPath)) # Old code folder is renamed. Now we need to move the newCode and rename it # New code is in UpdateVariables.getPath(newCode), unfortunately there is an unknown folder name here from Git # First work out the Git hub zip extracted name gitFolder = getImmediateSubdirectories( updateVariables.getPath(updateVariables.codeDir))[0] print(gitFolder) newCode = os.path.join( updateVariables.getPath(updateVariables.codeDir), gitFolder) try: os.chdir(newCode) shutil.copytree(newCode, codeDirPath) dialog = InfoMsgBox( "Update Finished", "Update finished with no faults. Code has been updated to the USB " "copy", "Finished") dialog.exec() except Exception as e: raise e
class Tablewidget(QTableWidget): def __init__(self,parent): super().__init__() self.setParent(parent) self.frontandnextFlag = [] self.pat = re.compile(r"<font style='background-color:red;'>(.*?)</font>") self.searchbox = QFrame(self) self.searchbox.setGeometry(10,50,330,30) # self.searchbox.setFrameRect(QRect(10,10,10,10)) self.searchbox.setStyleSheet("border-radius:2px;background-color:rgb(200,200,200)") self.searchbox.setHidden(True) self.searchtextbox = QLineEdit(self.searchbox) self.searchtextbox.setGeometry(0,0,210,30) self.searchtextbox.setStyleSheet("border:none") #文本显示框 self.textwindow = QTextEdit(self) self.textwindow.setGeometry(0,0, self.width() - 100, self.height() - 100) self.textwindow.setVisible(False) self.textwindow_text = None #搜索窗口 self.search = QPushButton("", self.searchbox) self.search.setIcon(QIcon("./pic/search.ico")) self.search.setGeometry(210, 0, 30, 30) self.search.setStyleSheet("border:none") self.search.clicked.connect(self.searchdata) self.front = QPushButton("",self.searchbox) self.front.setIcon(QIcon("./pic/front.ico")) self.front.setGeometry(240,0,30,30) self.front.setStyleSheet("border:none") self.front.clicked.connect(lambda: self.frontandnextpress(-1)) self.next = QPushButton("", self.searchbox) self.next.setIcon(QIcon("./pic/next.ico")) self.next.setGeometry(270,0,30,30) self.next.setStyleSheet("border:none") self.next.clicked.connect(lambda: self.frontandnextpress(1)) self.clo = QPushButton("", self.searchbox) self.clo.setIcon(QIcon("./pic/close.ico")) self.clo.setGeometry(300, 0, 30, 30) self.clo.setStyleSheet("border:none") self.clo.clicked.connect(self.searchboxclo) self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) # 设置垂直方向滑块像素移动 self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) # 设置水平方向滑块像素移动 self.setEditTriggers(QAbstractItemView.NoEditTriggers | QAbstractItemView.DoubleClicked) # 设置表格不可编辑 self.setContextMenuPolicy(Qt.CustomContextMenu) # 设置启用右键策略 def showdata(self, data): self.setRowCount(len(data[0]) - 1) self.setColumnCount(len(data)) for i in range(0, len(data)): # 总列数,显示所有数据 self.setHorizontalHeaderItem(i, QTableWidgetItem(data[i][0])) for j in range(1, len(data[0])): # 总数据行数 ss = QTableWidgetItem(data[i][j]) self.setItem(j - 1, i, ss) ss.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) # 设置所有单元格对齐方式 def keyPressEvent(self, QkeyEvent): if QkeyEvent.key() == Qt.Key_F: if QApplication.keyboardModifiers() == Qt.ControlModifier: self.searchbox.show() self.searchbox.setHidden(False) self.searchtextbox.setFocus() elif QkeyEvent.key() == Qt.Key_Escape: if self.searchbox.isHidden(): self.textwindow.setHidden(True) else: self.searchbox.setHidden(True) self.textwindow.setHidden(True) elif QkeyEvent.key() == Qt.Key_F2: self.textwindow.setGeometry(0,0, self.width() - 100, self.height() - 100) detail = JSONEncoder().encode(self.textwindow_text) detail = json.loads(detail) self.textwindow.setText(json.dumps(detail, indent=5, ensure_ascii=False)) self.textwindow.setHidden(False) def searchdata(self): self.frontandnextFlag = [] global pressFlag pressFlag = -1 findtext = "" # if self.searchtextbox.text() == "": # return # else: try: findtext = self.searchtextbox.text().split()[0] except IndexError: findtext = "$@##$$@!!" #如果为空,标记特殊寻找字符,找不到将Qlabel替换为字符串 for a in range(self.rowCount()): for b in range(self.columnCount()): if isinstance(type(self.item(a,b)),type(None)) and isinstance(type(self.cellWidget(a,b)),type(None)): pass else: if isinstance(type(self.cellWidget(a,b)),type(QLabel)): if "<font style" in self.cellWidget(a,b).text(): d = self.cancelCssFormat(self.cellWidget(a,b).text()) celltext = self.cellWidget(a,b).text().replace( "<font style='background-color:red;'>{}</font>".format(d), d) if findtext in celltext: self.cellWidget(a,b).setText(self.setStrkeyColor(celltext,findtext)) self.frontandnextFlag.append([a,b]) else: self.removeCellWidget(a,b) celltext = celltext.replace("<br>", "\n") self.setItem(a,b,QTableWidgetItem(celltext)) else: celltext = self.cellWidget(a,b).text() celltext = celltext.replace("<br>", "\n") self.removeCellWidget(a,b) self.setItem(a,b,QTableWidgetItem(celltext)) elif isinstance(type(self.item(a, b)), type(QTableWidgetItem)): if findtext in self.item(a,b).text(): celltext = self.item(a,b).text().replace("\n","<br>") celltext = self.setStrkeyColor(celltext,findtext) lab = QLabel(celltext,self) self.setCellWidget(a,b,lab) self.setItem(a,b,QTableWidgetItem("")) self.frontandnextFlag.append([a, b]) # print(a,b,type(self.cellWidget(a,b)),type(self.item(a,b)),"\n",lab.text()) else: pass else: pass def setStrkeyColor(self,strdata,key): needstr = strdata.replace(key,"<font style='background-color:red;'>{}</font>".format(key)) return needstr def cancelCssFormat(self,strdata): return self.pat.search(strdata).group(1) def stecellbackcolor(self,a,b,color=QColor(200,200,200)): self.item(a,b).setBackground(QColor(color)) def searchboxclo(self): self.searchtextbox.clear() self.searchbox.close() def frontandnextpress(self,k): global pressFlag pressFlag += k if pressFlag >= 0 and pressFlag < len(self.frontandnextFlag): self.setCurrentCell(self.frontandnextFlag[pressFlag][0],self.frontandnextFlag[pressFlag][1]) else: pressFlag = -1