def __init__(self, parent): super(EditSourceCodeDialog, self).__init__(parent) # create UI self.main_grid_layout = QGridLayout(self) main_vertical_splitter = QSplitter() main_vertical_splitter.setOrientation(Qt.Vertical) # instructions and actions layout header_widget = QWidget() header_layout = QHBoxLayout() instructions_text_edit = QTextEdit() instructions_text_edit.setText(instructions_text) instructions_text_edit.setEnabled(False) reset_button = QPushButton() reset_button.setText('reset') reset_button.clicked.connect(self.reset) header_layout.addWidget(instructions_text_edit) header_layout.addWidget(reset_button) header_widget.setLayout(header_layout) main_vertical_splitter.addWidget(header_widget) # code text edit self.code_text_edit = CodeEditor() self.code_text_edit.static_elements = ['def update(self, input_called=-1, token=None):', 'self.data_outputs_updated()', 'def get_data(self):', 'def set_data(self, data):', ''' def __init__(self, parent_node: Node, flow, configuration=None): super(%NODE_TITLE%_NodeInstance, self).__init__(parent_node, flow, configuration)''', 'class %NODE_TITLE%_NodeInstance'] self.code_text_edit.components = ['self.main_widget', 'self.outputs', 'self.input', 'self.special_actions'] main_vertical_splitter.addWidget(self.code_text_edit) self.main_grid_layout.addWidget(main_vertical_splitter) button_box = QDialogButtonBox() button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) self.main_grid_layout.addWidget(button_box) self.setWindowTitle('Edit Source Code') self.resize(1300, 950) self.reset()
def setup_ui(self): vertical_layout = QVBoxLayout(self) vertical_layout.setSpacing(6) vertical_layout.setContentsMargins(11, 11, 11, 11) splitter_v = QSplitter(self) splitter_v.setOrientation(PySide2.QtCore.Qt.Vertical) size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( splitter_v.sizePolicy().hasHeightForWidth()) splitter_v.setSizePolicy(size_policy) splitter_h = QSplitter(splitter_v) splitter_h.setOrientation(PySide2.QtCore.Qt.Horizontal) vertical_layout_widget = QWidget(splitter_h) self.vertical_layout_left = QVBoxLayout(vertical_layout_widget) self.vertical_layout_left.setSpacing(6) self.vertical_layout_left.setSizeConstraint( QLayout.SetDefaultConstraint) self.vertical_layout_left.setContentsMargins(0, 0, 0, 0) splitter_h.addWidget(vertical_layout_widget) vertical_layout_widget2 = QWidget(splitter_h) self.vertical_layout_right = QVBoxLayout(vertical_layout_widget2) self.vertical_layout_right.setContentsMargins(0, 0, 0, 0) splitter_h.addWidget(vertical_layout_widget2) splitter_v.addWidget(splitter_h) vertical_layout_widget3 = QWidget(splitter_v) vertical_layout_bottom = QVBoxLayout(vertical_layout_widget3) vertical_layout_bottom.setSpacing(6) vertical_layout_bottom.setSizeConstraint(QLayout.SetDefaultConstraint) vertical_layout_bottom.setContentsMargins(11, 0, 11, 11) text_browser = QTextBrowser(vertical_layout_widget3) vertical_layout_bottom.addWidget(text_browser) splitter_v.addWidget(vertical_layout_widget3) vertical_layout.addWidget(splitter_v)
def __init__(self, parent=None): QWidget.__init__(self, parent) self.scene = None self.populateScene() self.h1Splitter = QSplitter() self.h2Splitter = QSplitter() vSplitter = QSplitter() vSplitter.setOrientation(Qt.Vertical) vSplitter.addWidget(self.h1Splitter) vSplitter.addWidget(self.h2Splitter) view = View("Top left view", self) view.view().setScene(self.scene) self.h1Splitter.addWidget(view) view = View("Top right view", self) view.view().setScene(self.scene) self.h1Splitter.addWidget(view) view = View("Bottom left view", self) view.view().setScene(self.scene) self.h2Splitter.addWidget(view) view = View("Bottom right view", self) view.view().setScene(self.scene) self.h2Splitter.addWidget(view) layout = QHBoxLayout() layout.addWidget(vSplitter) self.setLayout(layout) self.setWindowTitle(self.tr("Chip Example"))
def __init__(self, model): super(_ControlTab, self).__init__() self._model = model layout = QGridLayout() splitter = QSplitter() self._bot_table = _BotTable() self._responses_tab = _ResponsesTab() self._execute_tab = _ExecuteTab(self._responses_tab, model) self._tab_widget = QTabWidget() self._tab_widget.addTab(self._execute_tab, "Execute") self._tab_widget.addTab(self._responses_tab, "Responses") splitter.setOrientation(Qt.Vertical) splitter.addWidget(self._bot_table) splitter.addWidget(self._tab_widget) splitter.setSizes([50, 100]) layout.addWidget(splitter) self.setLayout(layout) self._register_listeners()
def _init_widgets(self): # Subclass QPlainTextEdit class SmartPlainTextEdit(QPlainTextEdit): def __init__(self, parent, callback): super(SmartPlainTextEdit, self).__init__(parent) self._callback = callback def keyPressEvent(self, event): super(SmartPlainTextEdit, self).keyPressEvent(event) if event.key() == Qt.Key_Return: if not (event.modifiers() == Qt.ShiftModifier): self._callback() # Gui stuff splitter = QSplitter(self) output_wid = QWidget(splitter) output_wid.setLayout(QVBoxLayout(output_wid)) self._history_text = QTextEdit(output_wid) self._history_text.setCurrentFont(QFont('Times', 10)) self._history_text.setFontFamily('Source Code Pro') output_wid.layout().addWidget(QLabel("History")) output_wid.layout().addWidget(self._history_text) input_wid = QWidget(splitter) input_wid.setLayout(QVBoxLayout(input_wid)) self._command = SmartPlainTextEdit(input_wid, self._send_command) input_wid.layout().addWidget(QLabel("Command")) input_wid.layout().addWidget( QLabel( "Press Enter to send the command. Press Shift + Enter to add a newline." )) input_wid.layout().addWidget(self._command) splitter.setOrientation(Qt.Vertical) splitter.addWidget(output_wid) splitter.addWidget(input_wid) splitter.setSizes([300, 100]) send_button = QPushButton(self, text="Send") clear_history_button = QPushButton(self, text="Clear History") interact_button = QPushButton(self, text="Interact") send_button.clicked.connect(self._send_command) clear_history_button.clicked.connect(self._history_text.clear) interact_button.clicked.connect( lambda: self.initialize(self.workspace.instance.img_name)) buttons = QSplitter(self) buttons.addWidget(interact_button) buttons.addWidget(clear_history_button) buttons.addWidget(send_button) buttons.setSizes([100, 200, 600]) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(splitter) self.layout().addWidget(buttons)
def __init__(self, parent): super(EditInputWidgetDialog, self).__init__(parent) # create UI self.main_grid_layout = QGridLayout(self) main_vertical_splitter = QSplitter() main_vertical_splitter.setOrientation(Qt.Vertical) # instructions and actions layout header_widget = QWidget() header_layout = QHBoxLayout() instructions_text_edit = QTextEdit() instructions_text_edit.setText(instructions_text) instructions_text_edit.setEnabled(False) reset_button = QPushButton() reset_button.setText('reset') reset_button.clicked.connect(self.reset) header_layout.addWidget(instructions_text_edit) header_layout.addWidget(reset_button) header_widget.setLayout(header_layout) main_vertical_splitter.addWidget(header_widget) # code text edit self.code_text_edit = CodeEditor() self.code_text_edit.static_elements = [ 'def get_data(self):', 'def set_data(self, data):' ] self.code_text_edit.components = [ 'self.parent_node_instance', 'self.parent_node_instance.update_shape()', 'self.parent_port_instance' ] main_vertical_splitter.addWidget(self.code_text_edit) self.main_grid_layout.addWidget(main_vertical_splitter) button_box = QDialogButtonBox() button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) self.main_grid_layout.addWidget(button_box) self.setWindowTitle('Edit Source Code') self.resize(1300, 950) self.reset()
def home(self): """ Add the GUI elements to the window that represent the home state of the application. """ toolbar = self.addToolBar("File") save = QAction(QIcon("res/icon_save.png"), "Save", self) save.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) toolbar.addAction(save) load = QAction(QIcon("res/icon_load.png"), "Load", self) load.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) toolbar.addAction(load) toolbar.addSeparator() undo = QAction(QIcon("res/icon_undo.png"), "Undo", self) undo.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Z)) toolbar.addAction(undo) redo = QAction(QIcon("res/icon_redo.png"), "Redo", self) redo.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Y)) toolbar.addAction(redo) toolbar.addSeparator() zoom_in = QAction(QIcon("res/icon_zoom_in.png"), "Zoom In", self) toolbar.addAction(zoom_in) zoom_out = QAction(QIcon("res/icon_zoom_out.png"), "Zoom Out", self) toolbar.addAction(zoom_out) toolbar.addSeparator() clear = QAction(QIcon("res/icon_clear.png"), "Clear", self) toolbar.addAction(clear) toolbar.addSeparator() grid = QAction(QIcon("res/icon_grid.png"), "Grid", self) toolbar.addAction(grid) toolbar.actionTriggered[QAction].connect(self.toolbar_pressed) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.setHandleWidth(16) self.tile_ed = TileEd(self.size, self.tile_size, self.pixmap, self) scroll_area_tile_ed = QScrollArea() scroll_area_tile_ed.setBackgroundRole(QPalette.Dark) scroll_area_tile_ed.setWidgetResizable(True) scroll_area_tile_ed.setWidget(self.tile_ed) splitter.addWidget(scroll_area_tile_ed) self.tile_sel = TileSelector(self.tile_size, self.pixmap, self) scroll_area_tile_sel = QScrollArea() scroll_area_tile_sel.setBackgroundRole(QPalette.Dark) scroll_area_tile_sel.setWidgetResizable(True) scroll_area_tile_sel.setWidget(self.tile_sel) splitter.addWidget(scroll_area_tile_sel) self.setCentralWidget(splitter)
def get_menu(self): scroll = QScrollArea(self) self.forum_view.setStyleSheet("QListView{font: bold 12px;}") self.forum_view.clicked.connect(self.listViewClick) self.forum_view.setModel(self.forum_model) self.date_view.setStyleSheet("QListView{font: bold 12px;}") self.date_view.clicked.connect(self.listViewClick) self.date_view.setModel(self.day_model) menu_splitter = QSplitter(self) menu_splitter.setOrientation(Qt.Vertical) menu_splitter.addWidget(self.forum_view) menu_splitter.addWidget(self.date_view) scroll.setWidget(menu_splitter) scroll.setWidgetResizable(True) self.splitter.addWidget(scroll)
def add_horizontal_splitter(self, layout): splitter = QSplitter() splitter.setOrientation(Qt.Orientation.Vertical) layout.addWidget(splitter) # ADD TOP top_widget = QWidget(splitter) top = QVBoxLayout(top_widget) for i in range(3): btn = FillPushButton(f'top_{i}') top.layout().addWidget(btn) # ADD BOTTOM bottom_widget = QWidget(splitter) right = QGridLayout(bottom_widget) btn = FillPushButton('bottom') right.addWidget(btn)
def _setup(self): self.serial_input = SerialInputGui() self.serial_device = SerialDevicesGui() self.serial_monitor = SerialMonitorGui(self) hsplitter = QSplitter() hsplitter.setRubberBand(-1) hsplitter.setHandleWidth(10) hsplitter.setOrientation(Qt.Horizontal) hsplitter.addWidget(self.serial_monitor) hsplitter.addWidget(self.serial_device) vsplitter = QSplitter() vsplitter.setRubberBand(-1) vsplitter.setHandleWidth(10) vsplitter.setOrientation(Qt.Vertical) vsplitter.addWidget(hsplitter) vsplitter.addWidget(self.serial_input) self.layout.addWidget(vsplitter, 0, 0, 1, 1)
class FE14ChapterConfigTab(QScrollArea): def __init__(self): super().__init__() self.chapter_data = None module_service = locator.get_scoped("ModuleService") config_module = module_service.get_common_module_template("Map Config") self.module = module_service.get_module("Chapters") self.text_data_widget = FE14ChapterTextDataWidget() self.header_scroll, self.header_property_form = PropertyForm.create_with_scroll( self.module.element_template) self.config_scroll, self.config_property_form = PropertyForm.create_with_scroll( config_module.element_template) self.header_property_form.editors["CID"].setEnabled(False) self.header_property_form.editors["Key (CID)"].setEnabled(False) self.vertical_layout = QVBoxLayout(parent=self) self.splitter = QSplitter(parent=self) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter2 = QSplitter(parent=self) self.splitter2.setOrientation(QtCore.Qt.Horizontal) self.splitter.addWidget(self.text_data_widget) self.splitter.addWidget(self.splitter2) self.splitter2.addWidget(self.header_scroll) self.splitter2.addWidget(self.config_scroll) self.vertical_layout.addWidget(self.splitter) self.scroll_content = QWidget() self.scroll_content.setLayout(self.vertical_layout) self.setWidget(self.scroll_content) self.setWidgetResizable(True) def update_chapter_data(self, chapter_data): self.chapter_data = chapter_data chapter_header = chapter_data.chapter if chapter_data else None config = chapter_data.config.element if chapter_data else None self.text_data_widget.update_chapter_data(chapter_data) self.header_property_form.update_target(chapter_header) self.config_property_form.update_target(config) self.header_scroll.setEnabled(chapter_header is not None) self.config_scroll.setEnabled(config is not None)
def add_horizontal_splitter(self, layout): splitter = QSplitter() splitter.setOrientation(Qt.Orientation.Vertical) layout.addWidget(splitter) # ADD TOP top_widget = QWidget(splitter) top = QVBoxLayout(top_widget) # NORMAL btn = FillPushButton('Push Button') top.layout().addWidget(btn) #DISABLED btn = FillPushButton('Push Button Disabled') btn.setEnabled(False) top.layout().addWidget(btn) # ADD BOTTOM bottom_widget = QWidget(splitter) right = QGridLayout(bottom_widget) # NORMAL cbx = QCheckBox('Check Box') right.addWidget(cbx) #DISABLED cbx = QCheckBox('Check Box Disabled') cbx.setEnabled(False) right.addWidget(cbx) rob = QRadioButton('Radio Button') right.addWidget(rob) rob = QRadioButton('Radio Button Disabled') rob.setEnabled(False) right.addWidget(rob)
def _initCentralWidget(self): centralView = QWidget() centralView.setLayout(QHBoxLayout()) centralView.layout().setSpacing(0) centralView.layout().setContentsMargins(0, 0, 0, 0) self.browser = Browser(self.document, self.subject, self.powermode) centralView.layout().addWidget(self.browser) canvas = QSplitter() canvas.setFrameStyle(QFrame.NoFrame | QFrame.Plain) canvas.setOrientation(Qt.Vertical) canvas.setRubberBand(-1) self.editor = Editor(self.subject, self.powermode) self.console = Console(self.subject) canvas.addWidget(self.editor) canvas.addWidget(self.console) centralView.layout().addWidget(canvas) canvas.setSizes([4, 4, 1, 1]) self.setCentralWidget(centralView)
# --------------------------- # SplitterでTextEditを垂直に2つ表示する # --------------------------- import sys from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit from PySide2.QtCore import Qt app = QApplication(sys.argv) qw_text_edit_top = QTextEdit() qw_text_edit_top.append('top') qw_text_edit_bottom = QTextEdit() qw_text_edit_bottom.append('bottom') qw_splitter = QSplitter() qw_splitter.setOrientation(Qt.Orientation.Vertical) # Splitterを垂直に設定 print(qw_splitter.orientation()) qw_splitter.addWidget(qw_text_edit_top) qw_splitter.addWidget(qw_text_edit_bottom) qw_splitter.show() sys.exit(app.exec_())
class main(QWidget): def __init__(self, parent=None): super(main, self).__init__(parent) self.setup() # connections, widgets, layouts, etc. self.blksize = 2**20 # 1 MB; must be divisible by 16 self.ext = '.enc' # extension is appended to encrypted files self.path = '' self.encrypted = [] # to highlight done files in list self.decrypted = [] self.clipboard = QApplication.clipboard() self.timeout = None # to clear message label, see setMessage # this program was just an excuse to play with QprogressBar if not hash(os.urandom(11)) % 11: QTimer().singleShot(50, self.windDown) # various random hints hints = [ 'Freshly encrypted files can be renamed in the table!', 'Clipboard is always cleared on program close!', 'Keys can contain emoji if you <em>really</em> want: \U0001f4e6', 'Keys can contain emoji if you <em>really</em> want: \U0001F511', 'This isn\'t a tip, I just wanted to say hello!', 'Keys can be anywhere from 8 to 4096 characters long!', 'This program was just an excuse to play with the progress bars!', 'Select \'Party\' in the hash button for progress bar fun!', ('Did you know you can donate one or all of your vital organs to ' 'the Aperture Science Self-Esteem Fund for Girls? It\'s true!'), ('It\'s been {:,} days since Half-Life 2: Episode ' 'Two'.format(int((time.time() - 1191988800) / 86400))), 'I\'m version {}!'.format(VERSION), 'I\'m version {}.whatever!'.format(VERSION.split('.')[0]), ('Brought to you by me, I\'m <a href="https://orthallelous.word' 'press.com/">Orthallelous!</a>'), #'Brought to you by me, I\'m Htom Sirveaux!', 'I wonder if there\'s beer on the sun', 'Raspberry World: For all your raspberry needs. Off the beltline', #'I\'ve plummented to my death and I can\'t get up', '<em>NOT</em> compatible with the older version!', ('Hello there, fellow space travellers! Until somebody gives me ' 'some new lines in KAS, that is all I can say. - Bentusi Exchange' ) ] if not hash(os.urandom(9)) % 4: self.extraLabel.setText(random.choice(hints)) def genKey(self): "generate a random key" n = self.keySizeSB.value() char = string.printable.rstrip() #map(chr, range(256)) while len(char) < n: char += char key = ''.join(random.sample(char, n)) self.keyInput.setText(key) def showKey(self, state=None): "hide/show key characters" if state is None: state = bool(self.showKeyCB.checkState()) else: state = bool(state) if state: self.keyInput.setEchoMode(QLineEdit.Normal) else: self.keyInput.setEchoMode(QLineEdit.PasswordEchoOnEdit) def getFolder(self): "open file dialog and fill file table" path = QFileDialog(directory=self.path).getExistingDirectory() if not path: return self.path = str(path) self.populateTable(self.path) self.encrypted, self.decrypted = [], [] return def resizeEvent(self, event): self.showFolder(self.path) # update how the folder is shown def splitterChanged(self, pos): self.showFolder(self.path) # likewise def showFolder(self, path): "displays current path, truncating as needed" if not path: return ell, sl = '\u2026', os.path.sep # ellipsis, slash chars lfg, rfg = Qt.ElideLeft, Qt.ElideRight lst, wdh = os.path.basename(path), self.folderLabel.width() path = path.replace(os.path.altsep or '\\', sl) self.folderLabel.setToolTip(path) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short self.folderLabel.setText('\u22ee' if txt != sl else txt) return # but when would this happen? # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = ell + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt[2:]) < len(lst) + 2: txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh)) # you'd think len(txt) < len(lst) would work, but no; you'd be wrong self.folderLabel.setText(txt) def populateTable(self, path): "fill file table with file names" self.showFolder(path) names = [] for n in os.listdir(path): if os.path.isdir(os.path.join(path, n)): continue # folder names.append(n) self.folderTable.clearContents() self.folderTable.setRowCount(len(names)) self.folderTable.setColumnCount(1) if not names: # no files in this folder, inform user self.setMessage('This folder has no files') return self.folderTable.blockSignals(True) selEnab = Qt.ItemIsSelectable | Qt.ItemIsEnabled for i, n in enumerate(names): item = QTableWidgetItem() item.setText(n) item.setToolTip(n) item.setFlags(selEnab) # color code encrypted/decrypted files if n in self.encrypted: item.setTextColor(QColor(211, 70, 0)) # allowed encrypted filenames to be changed item.setFlags(selEnab | Qt.ItemIsEditable) if n in self.decrypted: item.setForeground(QColor(0, 170, 255)) self.folderTable.setItem(i, 0, item) if len(names) > 5: self.setMessage('{:,} files'.format(len(names)), 7) self.folderTable.blockSignals(False) return def editFileName(self, item): "change file name" new, old = str(item.text()), str(item.toolTip()) result = QMessageBox.question( self, 'Renaming?', ("<p align='center'>Do you wish to rename<br>" + '<span style="color:#d34600;">{}</span>'.format(old) + "<br>to<br>" + '<span style="color:#ef4b00;">{}</span>'.format(new) + '<br>?</p>')) self.folderTable.blockSignals(True) if any(i in new for i in '/?<>:*|"^'): self.setMessage('Invalid character in name', 7) item.setText(old) elif result == QMessageBox.Yes: oold = os.path.join(self.path, old) try: os.rename(oold, os.path.join(self.path, new)) self.encrypted.remove(old) self.encrypted.append(new) item.setToolTip(new) except Exception as err: self.setMessage(str(err), 9) item.setText(old) item.setToolTip(old) self.encrypted.remove(new) self.encrypted.append(old) else: item.setText(old) self.folderTable.blockSignals(False) def setMessage(self, message, secs=4, col=None): "show a message for a few seconds - col must be rgb triplet tuple" if self.timeout: # https://stackoverflow.com/a/21081371 self.timeout.stop() self.timeout.deleteLater() if col is None: color = 'rgb(255, 170, 127)' else: try: color = 'rgb({}, {}, {})'.format(*col) except: color = 'rgb(255, 170, 127)' self.messageLabel.setStyleSheet('background-color: {};'.format(color)) self.messageLabel.setText(message) self.messageLabel.setToolTip(message) self.timeout = QTimer() self.timeout.timeout.connect(self.clearMessage) self.timeout.setSingleShot(True) self.timeout.start(secs * 1000) def clearMessage(self): self.messageLabel.setStyleSheet('') self.messageLabel.setToolTip('') self.messageLabel.setText('') def getName(self): "return file name of selected" items = self.folderTable.selectedItems() names = [str(i.text()) for i in items] if names: return names[0] # only the first selected file else: return '' def showKeyLen(self, string): "displays a tooltip showing length of key" s = len(string) note = '{:,} character{}'.format(s, '' if s == 1 else 's') tip = QToolTip pos = self.genKeyButton.mapToGlobal(QPoint(0, 0)) if s < self.minKeyLen: note = '<span style="color:#c80000;">{}</span>'.format(note) else: note = '<span style="color:#258f22;">{}</span>'.format(note) tip.showText(pos, note) def lock(self, flag=True): "locks buttons if True" stuff = [ self.openButton, self.encryptButton, self.decryptButton, self.genKeyButton, self.hashButton, self.showKeyCB, self.copyButton, self.keyInput, self.keySizeSB, self.folderTable, ] for i in stuff: i.blockSignals(flag) i.setEnabled(not flag) return def _lerp(self, v1, v2, numPts=10): "linearly interpolate from v1 to v2\nFrom Orthallelous" if len(v1) != len(v2): raise ValueError("different dimensions") D, V, n = [], [], abs(numPts) for i, u in enumerate(v1): D.append(v2[i] - u) for i in range(n + 1): vn = [] for j, u in enumerate(v1): vn.append(u + D[j] / float(n + 2) * i) V.append(tuple(vn)) return V def weeeeeee(self): "party time" self.lock() self.setMessage('Party time!', 2.5) a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar process, sleep = app.processEvents, time.sleep am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((am, bm, cm), (ax, bx, cx), 100) ivops = loops[::-1] # up and up! for i in range(3): for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) process() sleep(0.01) a.setValue(ax) b.setValue(bx) c.setValue(cx) sleep(0.25) a.setValue(am) b.setValue(bm) c.setValue(cm) # snake! self.setMessage('Snake time!') self.messageLabel.setStyleSheet('background-color: rgb(127,170,255);') for i in range(2): for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) for j, k, l in loops: c.setValue(int(l)) process() sleep(0.002) process() c.setInvertedAppearance(True) process() for j, k, l in ivops: c.setValue(int(l)) process() sleep(0.002) process() b.setInvertedAppearance(True) process() for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) process() a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) # bars sleep(0.5) self.setMessage('Bars!') process() self.messageLabel.setStyleSheet('background-color: rgb(127,255,170);') for i in range(2): a.setValue(ax) time.sleep(0.65) a.setValue(am) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() c.setValue(cx) time.sleep(0.65) c.setValue(cm) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() # okay, enough process() a.setValue(ax) b.setValue(bx) c.setValue(cx) #a.setValue(am); b.setValue(bm); c.setValue(cm) a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) self.lock(False) return def windDown(self, note=None): "silly deload on load" if note is None: note = 'Loading...' self.lock() self.setMessage(note) self.messageLabel.setStyleSheet('background-color: rgb(9, 190, 130);') a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((ax, bx, cx), (am, bm, cm), 100) for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) app.processEvents() time.sleep(0.02) a.reset() b.reset() c.reset() self.lock(False) self.clearMessage() def genHash(self, action): "generate hash of selected file and display it" name, t0 = self.getName(), time.perf_counter() # mark what hash was used in the drop-down menu for i in self.hashButton.menu().actions(): if i == action: i.setIconVisibleInMenu(True) else: i.setIconVisibleInMenu(False) if str(action.text()) == 'Party': self.weeeeeee() self.windDown('Winding down...') return if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return self.lock() hsh = self.hashFile(os.path.join(self.path, name), getattr(hashlib, str(action.text()))) self.lock(False) #hsh = str(action.text()) + ': ' + hsh self.hashLabel.setText(hsh) self.hashLabel.setToolTip(hsh) self.extraLabel.setText( str(action.text()) + ' hash took ' + self.secs_fmt(time.perf_counter() - t0)) def setCancel(self): "cancel operation" self._requestStop = True def showCancelButton(self, state=False): "show/hide cancel button" self.cancelButton.blockSignals(not state) self.cancelButton.setEnabled(state) if state: self.cancelButton.show() self.keyInput.hide() self.genKeyButton.hide() self.keySizeSB.hide() else: self.cancelButton.hide() self.keyInput.show() self.genKeyButton.show() self.keySizeSB.show() def hashFile(self, fn, hasher): "returns the hash value of a file" hsh, blksize = hasher(), self.blksize fsz, csz = os.path.getsize(fn), 0.0 self.hashPbar.reset() self.showCancelButton(True) prog, title = '(# {:.02%}) {}', self.windowTitle() with open(fn, 'rb') as f: while 1: blk = f.read(blksize) if not blk: break hsh.update(blk) csz += blksize self.hashPbar.setValue(int(round(csz * 100.0 / fsz))) app.processEvents() self.setWindowTitle(prog.format(csz / fsz, title)) if self._requestStop: break self.hashPbar.setValue(self.hashPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Hashing canceled!') self.hashPbar.setValue(self.hashPbar.minimum()) self._requestStop = False return return hsh.hexdigest() def hashKey(self, key, salt=b''): "hashes a key for encrypting/decrypting file" salt = salt.encode() if type(salt) != bytes else salt key = key.encode() if type(key) != bytes else key p = app.processEvents self.setMessage('Key Hashing...', col=(226, 182, 249)) p() key = hashlib.pbkdf2_hmac('sha512', key, salt, 444401) p() self.clearMessage() p() return hashlib.sha3_256(key).digest() # AES requires a 32 char key def encrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.encryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.encrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Encrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Encrypting took ' + self.secs_fmt(tt)) def encryptFile(self, key, fn): "encrypts a file using AES (MODE_GCM)" chars = ''.join(map(chr, range(256))).encode() chk = AES.block_size sample = random.sample iv = bytes(sample(chars, chk * 2)) salt = bytes(sample(chars * 2, 256)) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) fsz = os.path.getsize(fn) del key blksize = self.blksize gn = fn + self.ext fne = os.path.basename(fn).encode() fnz = len(fne) if len(fne) % chk: fne += bytes(sample(chars, chk - len(fne) % chk)) csz = 0.0 # current processed value self.encryptPbar.reset() prog, title = '({:.02%}) {}', self.windowTitle() self.showCancelButton(True) with open(fn, 'rb') as src, open(gn, 'wb') as dst: dst.write(bytes([0] * 16)) # spacer for MAC written at end dst.write(iv) dst.write(salt) # store iv, salt # is it safe to store MAC, iv, salt plain right in file? # can't really store them encrypted, # or elsewhere in this model of single file encryption? # can't have another file for the file to lug around # store file size, file name length dst.write(vault.encrypt(struct.pack('<2Q', fsz, fnz))) dst.write(vault.encrypt(fne)) # store filename while 1: dat = src.read(blksize) if not dat: break elif len(dat) % chk: # add padding fil = chk - len(dat) % chk dat += bytes(sample(chars, fil)) dst.write(vault.encrypt(dat)) csz += blksize # show progress self.encryptPbar.setValue(int(round(csz * 100.0 / fsz))) self.setWindowTitle(prog.format(csz / fsz, title)) app.processEvents() if self._requestStop: break if not self._requestStop: stuf = random.randrange(23) # pack in more stuffing fing = b''.join(bytes(sample(chars, 16)) for i in range(stuf)) dst.write(vault.encrypt(fing)) # and for annoyance dst.seek(0) dst.write(vault.digest()) # write MAC self.hashLabel.setText('MAC: ' + vault.hexdigest()) self.encryptPbar.setValue(self.encryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Encryption canceled!') self.encryptPbar.setValue(self.encryptPbar.minimum()) self._requestStop = False os.remove(gn) return return gn def decrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.decryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.decrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Decrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Decrypting took ' + self.secs_fmt(tt)) def decryptFile(self, key, fn): "decrypts a file using AES (MODE_GCM)" blksize = self.blksize gn = hashlib.md5(os.path.basename(fn).encode()).hexdigest() gn = os.path.join(self.path, gn) # temporary name if os.path.exists(gn): self.setMessage('file already exists') return self.decryptPbar.reset() csz = 0.0 # current processed value chk, fnsz = AES.block_size, os.path.getsize(fn) prog, title = '({:.02%}) {}', self.windowTitle() try: with open(fn, 'rb') as src, open(gn, 'wb') as dst: # extract iv, salt MAC = src.read(16) iv = src.read(AES.block_size * 2) salt = src.read(256) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) self.showCancelButton(True) # extract file size, file name length sizes = src.read(struct.calcsize('<2Q')) fsz, fnz = struct.unpack('<2Q', vault.decrypt(sizes)) # extract filename; round up fnz to nearest chk rnz = fnz if not fnz % chk else fnz + chk - fnz % chk rfn = vault.decrypt(src.read(rnz))[:fnz].decode() self.setMessage('Found "{}"'.format(rfn), 13, (255, 211, 127)) while 1: dat = src.read(blksize) if not dat: break dst.write(vault.decrypt(dat)) csz += blksize # show progress self.decryptPbar.setValue(int(round(csz * 100.0 / fnsz))) self.setWindowTitle(prog.format(1 - (csz / fnsz), title)) app.processEvents() if self._requestStop: break if not self._requestStop: dst.truncate(fsz) # remove padding if not self._requestStop: vault.verify(MAC) self.hashLabel.setText('') except (ValueError, KeyError) as err: os.remove(gn) self.setMessage('Invalid decryption!') self.setWindowTitle(title) self.showCancelButton(False) return except Exception as err: os.remove(gn) self.setMessage('Invalid key or file!') self.setWindowTitle(title) self.showCancelButton(False) return self.decryptPbar.setValue(self.decryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Decryption canceled!') self.decryptPbar.setValue(self.decryptPbar.minimum()) self._requestStop = False os.remove(gn) return # restore original file name name, ext = os.path.splitext(rfn) count = 1 fn = os.path.join(self.path, name + ext) while os.path.exists(fn): fn = os.path.join(self.path, name + '_{}'.format(count) + ext) count += 1 os.rename(gn, fn) # restore original name return fn # saved name def copyKeyHash(self, action): "copies either the key or the hash to clipboard" act = str(action.text()).lower() if 'key' in act: txt = str(self.keyInput.text()) elif 'hash' in act: txt = str(self.hashLabel.text()) else: self.setMessage('Invalid copy selection') return if not txt: self.setMessage('Empty text; Nothing to copy') return if 'key' in act: self.setMessage('Key copied to clipboard') elif 'hash' in act: self.setMessage('Hash copied to clipboard') else: self.setMessage('Invalid copy selection') return self.clipboard.clear() self.clipboard.setText(txt) def secs_fmt(self, s): "6357 -> '1h 45m 57s'" Y, D, H, M = 31556952, 86400, 3600, 60 y = int(s // Y) s -= y * Y d = int(s // D) s -= d * D h = int(s // H) s -= h * H m = int(s // M) s -= m * M r = (str(int(s)) if int(s) == s else str(round(s, 3))) + 's' if m: r = str(m) + 'm ' + r if h: r = str(h) + 'h ' + r if d: r = str(d) + 'd ' + r if y: r = str(y) + 'y ' + r return r.strip() def closeEvent(self, event): self.clipboard.clear() def setup(self): "constructs the gui" Fixed = QSizePolicy() MinimumExpanding = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.minKeyLen = 8 self.maxKeyLen = 4096 self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Horizontal) self.splitter.splitterMoved.connect(self.splitterChanged) # left column self.leftColumn = QWidget() self.vl01 = QVBoxLayout() # left column - first item (0; horizonal layout 0) self.hl00 = QHBoxLayout() self.hl00.setSpacing(5) self.openButton = QPushButton('&Open') self.openButton.setToolTip('Open folder') self.openButton.setMinimumSize(60, 20) self.openButton.setMaximumSize(60, 20) self.openButton.setSizePolicy(Fixed) self.openButton.clicked.connect(self.getFolder) #ico = self.style().standardIcon(QStyle.SP_DirIcon) #self.openButton.setIcon(ico) self.folderLabel = QLabel() self.folderLabel.setMinimumSize(135, 20) self.folderLabel.setMaximumSize(16777215, 20) self.folderLabel.setSizePolicy(MinimumExpanding) self.hl00.insertWidget(0, self.openButton) self.hl00.insertWidget(1, self.folderLabel) # left column - second item (1) self.folderTable = QTableWidget() self.folderTable.setMinimumSize(200, 32) self.folderTable.horizontalHeader().setVisible(False) self.folderTable.horizontalHeader().setStretchLastSection(True) self.folderTable.verticalHeader().setVisible(False) self.folderTable.verticalHeader().setDefaultSectionSize(15) self.folderTable.itemChanged.connect(self.editFileName) # left column - third item (2) self.extraLabel = QLabel() self.extraLabel.setMinimumSize(200, 20) self.extraLabel.setMaximumSize(16777215, 20) self.extraLabel.setSizePolicy(MinimumExpanding) self.extraLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) # finalize left column self.vl01.insertLayout(0, self.hl00) self.vl01.insertWidget(1, self.folderTable) self.vl01.insertWidget(2, self.extraLabel) self.leftColumn.setLayout(self.vl01) # right column self.rightColumn = QWidget() self.vl02 = QVBoxLayout() # right column - first item (0) self.messageLabel = QLabel() self.messageLabel.setMinimumSize(290, 20) self.messageLabel.setMaximumSize(16777215, 20) self.messageLabel.setSizePolicy(MinimumExpanding) self.messageLabel.setAlignment(Qt.AlignCenter) # right column - second item (2; horizontal layout 1) self.hl01 = QHBoxLayout() self.hl01.setSpacing(5) self.encryptButton = QPushButton('&Encrypt') #\U0001F512 self.encryptButton.setToolTip('Encrypt selected file') self.encryptButton.setMinimumSize(60, 20) self.encryptButton.setMaximumSize(60, 20) self.encryptButton.setSizePolicy(Fixed) self.encryptButton.clicked.connect(self.encrypt) self.encryptPbar = QProgressBar() self.encryptPbar.setMinimumSize(225, 20) self.encryptPbar.setMaximumSize(16777215, 20) self.encryptPbar.setSizePolicy(MinimumExpanding) self.encryptPbar.setTextVisible(False) palette = self.encryptPbar.palette() # color of progress bar color = QColor(211, 70, 0) palette.setColor(QPalette.Highlight, color) self.encryptPbar.setPalette(palette) self.hl01.insertWidget(0, self.encryptButton) self.hl01.insertWidget(1, self.encryptPbar) # right column - third item (3; horizontal layout 2) self.hl02 = QHBoxLayout() self.hl02.setSpacing(5) self.cancelButton = QPushButton('C&ANCEL') self.cancelButton.setToolTip('Cancels current operation') self.cancelButton.setMinimumSize(70, 24) self.cancelButton.setMaximumSize(70, 24) self.cancelButton.setSizePolicy(Fixed) self.cancelButton.clicked.connect(self.setCancel) font = self.cancelButton.font() font.setBold(True) self.cancelButton.setFont(font) self.cancelButton.blockSignals(True) self.cancelButton.setEnabled(False) self.cancelButton.hide() self._requestStop = False self.keyInput = QLineEdit() self.keyInput.setMinimumSize(225, 20) self.keyInput.setMaximumSize(16777215, 20) self.keyInput.setSizePolicy(MinimumExpanding) self.keyInput.setPlaceholderText('key') self.keyInput.setMaxLength(self.maxKeyLen) self.keyInput.setAlignment(Qt.AlignCenter) self.keyInput.textEdited.connect(self.showKeyLen) self.genKeyButton = QPushButton('&Gen Key') #\U0001F511 self.genKeyButton.setToolTip('Generate a random key') self.genKeyButton.setMinimumSize(60, 20) self.genKeyButton.setMaximumSize(60, 20) self.genKeyButton.setSizePolicy(Fixed) self.genKeyButton.clicked.connect(self.genKey) self.keySizeSB = QSpinBox() self.keySizeSB.setToolTip('Length of key to generate') self.keySizeSB.setRange(32, 1024) self.keySizeSB.setMinimumSize(40, 20) self.keySizeSB.setMaximumSize(40, 20) self.keySizeSB.setSizePolicy(Fixed) self.keySizeSB.setAlignment(Qt.AlignCenter) self.keySizeSB.setButtonSymbols(QSpinBox.NoButtons) self.keySizeSB.setWrapping(True) self.hl02.insertWidget(0, self.cancelButton) self.hl02.insertWidget(1, self.keyInput) self.hl02.insertWidget(2, self.genKeyButton) self.hl02.insertWidget(3, self.keySizeSB) # right column - fourth item (4; horizontal layout 3) self.hl03 = QHBoxLayout() self.hl03.setSpacing(5) self.decryptButton = QPushButton('&Decrypt') #\U0001F513 self.decryptButton.setToolTip('Decrypt selected file') self.decryptButton.setMinimumSize(60, 20) self.decryptButton.setMaximumSize(60, 20) self.decryptButton.setSizePolicy(Fixed) self.decryptButton.clicked.connect(self.decrypt) self.decryptPbar = QProgressBar() self.decryptPbar.setMinimumSize(225, 20) self.decryptPbar.setMaximumSize(16777215, 20) self.decryptPbar.setSizePolicy(MinimumExpanding) self.decryptPbar.setTextVisible(False) self.decryptPbar.setInvertedAppearance(True) palette = self.decryptPbar.palette() # color of progress bar color = QColor(0, 170, 255) palette.setColor(QPalette.Highlight, color) self.decryptPbar.setPalette(palette) self.hl03.insertWidget(0, self.decryptButton) self.hl03.insertWidget(1, self.decryptPbar) # right column - fifth item (7; horizontal layout 4) self.hl04 = QHBoxLayout() self.hl04.setSpacing(5) self.showKeyCB = QCheckBox('&Show Key') self.showKeyCB.setToolTip('Show/Hide key value') self.showKeyCB.setMinimumSize(75, 20) self.showKeyCB.setMaximumSize(75, 20) self.showKeyCB.setSizePolicy(Fixed) self.showKeyCB.clicked.connect(self.showKey) self.showKeyCB.setChecked(True) self.hashPbar = QProgressBar() self.hashPbar.setMinimumSize(150, 20) self.hashPbar.setMaximumSize(16777215, 20) self.hashPbar.setSizePolicy(MinimumExpanding) self.hashPbar.setTextVisible(False) palette = self.hashPbar.palette() # color of progress bar color = QColor(31, 120, 73) palette.setColor(QPalette.Highlight, color) self.hashPbar.setPalette(palette) self.hashButton = QPushButton('&Hash') self.hashButton.setToolTip('Determine file hash') self.hashButton.setMinimumSize(60, 20) self.hashButton.setMaximumSize(60, 20) self.hashButton.setSizePolicy(Fixed) menu = QMenu(self.hashButton) ico = self.style().standardIcon(QStyle.SP_DialogYesButton) for alg in sorted( filter(lambda x: 'shake' not in x, hashlib.algorithms_guaranteed), key=lambda n: (len(n), sorted(hashlib.algorithms_guaranteed).index(n))): menu.addAction( ico, alg ) # drop shake algs as their .hexdigest requires an argument - the rest don't menu.addAction(ico, 'Party') for i in menu.actions(): i.setIconVisibleInMenu(False) self.hashButton.setMenu(menu) menu.triggered.connect(self.genHash) self.hl04.insertWidget(0, self.showKeyCB) self.hl04.insertWidget(1, self.hashPbar) self.hl04.insertWidget(2, self.hashButton) # right column - sixth item (8; horizontal layout 5) self.hl05 = QHBoxLayout() self.hl05.setSpacing(5) self.copyButton = QPushButton('&Copy') #\U0001F4CB self.copyButton.setToolTip('Copy key or hash to clipboard') self.copyButton.setMinimumSize(60, 20) self.copyButton.setMaximumSize(60, 20) self.copyButton.setSizePolicy(Fixed) menu2 = QMenu(self.copyButton) menu2.addAction('Copy Key') menu2.addAction('Copy Hash') self.copyButton.setMenu(menu2) menu2.triggered.connect(self.copyKeyHash) self.hashLabel = QLabel() self.hashLabel.setMinimumSize(225, 20) self.hashLabel.setMaximumSize(16777215, 20) self.hashLabel.setSizePolicy(MinimumExpanding) self.hashLabel.setTextFormat(Qt.PlainText) self.hashLabel.setAlignment(Qt.AlignCenter) self.hashLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) self.hl05.insertWidget(0, self.copyButton) self.hl05.insertWidget(1, self.hashLabel) # finalize right column self.vl02.insertWidget(0, self.messageLabel) self.vl02.insertSpacerItem(1, QSpacerItem(0, 0)) self.vl02.insertLayout(2, self.hl01) self.vl02.insertLayout(3, self.hl02) self.vl02.insertLayout(4, self.hl03) self.vl02.insertSpacerItem(5, QSpacerItem(0, 0)) self.vl02.insertWidget(6, QFrame()) self.vl02.insertLayout(7, self.hl04) self.vl02.insertLayout(8, self.hl05) self.rightColumn.setLayout(self.vl02) # finalize main window self.splitter.insertWidget(0, self.leftColumn) self.splitter.insertWidget(1, self.rightColumn) layout = QHBoxLayout(self) layout.addWidget(self.splitter) self.setLayout(layout) self.setWindowTitle('Simple File Encryptor/Decryptor') self.resize(self.sizeHint())
class KaitaiView(QScrollArea, View): def __init__(self, parent, binaryView): QScrollArea.__init__(self, parent) View.__init__(self) View.setBinaryDataNavigable(self, True) self.setupView(self) # BinaryViewType self.binaryView = binaryView self.rootSelectionStart = 0 self.rootSelectionEnd = 1 self.ioRoot = None self.ioCurrent = None # top half = treeWidget + structPath self.treeWidget = MyQTreeWidget() self.treeWidget.setColumnCount(4) self.treeWidget.setHeaderLabels(['label', 'value', 'start', 'end']) self.treeWidget.itemSelectionChanged.connect(self.onTreeSelect) self.structPath = QLineEdit("root") self.structPath.setReadOnly(True) topHalf = QWidget(self) layout = QVBoxLayout() layout.addWidget(self.treeWidget) layout.addWidget(self.structPath) topHalf.setLayout(layout) # bottom half = hexWidget self.hexWidget = HexEditor(binaryView, ViewFrame.viewFrameForWidget(self), 0) # splitter = top half, bottom half self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Vertical) self.splitter.addWidget(topHalf) self.splitter.addWidget(self.hexWidget) self.setWidgetResizable(True) self.setWidget(self.splitter) self.kaitaiParse() # parse the file using Kaitai, construct the TreeWidget def kaitaiParse(self, ksModuleName=None): log.log_debug('kaitaiParse() with len(bv)=%d and bv.file.filename=%s' % (len(self.binaryView), self.binaryView.file.filename)) if len(self.binaryView) == 0: return kaitaiIO = kshelpers.KaitaiBinaryViewIO(self.binaryView) parsed = kshelpers.parseIo(kaitaiIO, ksModuleName) if not parsed: return # it SEEMS as if parsing is finished at this moment, but some parsing # is postponed until attributes are accessed, so we must try/catch here tree = None if True: try: tree = kshelpers.buildQtree(parsed) except Exception as e: log.log_error( 'kaitai module %s threw exception, check file type' % ksModuleName) tree = None else: tree = kshelpers.buildQtree(parsed) if not tree: return self.ioRoot = tree.ksobj._io self.ioCurrent = tree.ksobj._io self.treeWidget.clear() self.treeWidget.setSortingEnabled(False) # temporarily, for efficiency # two options with how we create the hierarchy if False: # treat root as top level "file" container tree.setLabel('file') tree.setValue(None) tree.setStart(0) tree.setEnd(0) self.treeWidget.insertTopLevelItem(0, tree) else: # add root's children as top level items self.treeWidget.insertTopLevelItems(0, tree.takeChildren()) # enable sorting self.treeWidget.setSortingEnabled(True) self.treeWidget.sortByColumn(2, Qt.AscendingOrder) # TODO: select first item, maybe expand a few things self.rootSelectionStart = 0 self.rootSelectionEnd = 1 self.treeWidget.setUniformRowHeights(True) self.treeWidget.queueInitialPresentation = True # binja callbacks def getData(self): return self.binaryView def getStart(self): result = self.binaryView.start #log.log_debug('getStart() returning '+str(result)) return result def getEnd(self): result = self.binaryView.end #log.log_debug('getEnd() returning '+str(result)) return result def getLength(self): result = len(self.binaryView) #log.log_debug('getLength() returning '+str(result)) return result def getCurrentOffset(self): result = self.rootSelectionStart + int( (self.rootSelectionEnd - self.rootSelectionStart) / 2) #result = self.rootSelectionStart #log.log_debug('getCurrentOffset() returning '+str(result)) return result def getSelectionOffsets(self): result = None if self.hexWidget: result = self.hexWidget.getSelectionOffsets() else: result = (self.rootSelectionStart, self.rootSelectionStart) #log.log_debug('getSelectionOffsets() returning '+str(result)) return result def setCurrentOffset(self, offset): #log.log_debug('setCurrentOffset(0x%X)' % offset) self.rootSelectionStart = offset UIContext.updateStatus(True) def getFont(self): return binaryninjaui.getMonospaceFont(self) def navigate(self, addr): self.rootSelectionStart = addr self.rootSelectionEnd = addr + 1 self.hexWidget.setSelectionRange(addr, addr + 1) return True def navigateToFileOffset(self, offset): #log.log_debug('navigateToFileOffset()') return False def onTreeSelect(self, wtf=None): # get KaitaiTreeWidgetItem items = self.treeWidget.selectedItems() if not items or len(items) < 1: return item = items[0] # build path, inform user structPath = item.label itemTmp = item while itemTmp.parent(): itemTmp = itemTmp.parent() label = itemTmp.label if label.startswith('_m_'): label = label[3:] structPath = label + '.' + structPath self.structPath.setText('root.' + structPath) # (start, end) = (item.start, item.end) if start == None or end == None: return # determine current IO we're in (the Kaitai input/output abstraction) _io = None # if the tree item is linked to a KaitaiNode, simply read the IO if item.ksobj: _io = item.ksobj._parent._io else: # else we're a leaf parent = item.parent() if parent: # a leaf with a parent -> read parent's IO _io = parent.ksobj._io else: # a leaf without a parent -> we must be at root -> use root IO _io = self.ioRoot # if the selection is in the root view, store the interval so that upon # getCurrentOffset() callback, we return the middle and feature map is # updated if _io == self.ioRoot: self.rootSelectionStart = start self.rootSelectionEnd = end # current kaitai object is on a different io? then swap HexEditor if _io != self.ioCurrent: # delete old view self.hexWidget.hide() self.hexWidget.setParent(None) self.hexWidget.deleteLater() self.hexWidget = None # if it's the original file IO, wrap the already-open file binary view if _io == self.ioRoot: self.hexWidget = HexEditor(self.binaryView, ViewFrame.viewFrameForWidget(self), 0) # otherwise delete old view, create a temporary view else: # create new view length = _io.size() _io.seek(0) data = _io.read_bytes(length) bv = binaryview.BinaryView.new(data) self.hexWidget = HexEditor(bv, ViewFrame.viewFrameForWidget(self), 0) self.splitter.addWidget(self.hexWidget) self.ioCurrent = _io # now position selection in whatever HexEditor is current #log.log_debug('selecting to [0x%X, 0x%X)' % (start, end)) self.hexWidget.setSelectionRange(start, end) # set hex group title to reflect current selection #self.hexGroup.setTitle('Hex View @ [0x%X, 0x%X)' % (start, end)) def getStatusBarWidget(self): return menu.KaitaiStatusBarWidget(self)
class Widget: def __init__(self, instance: QObject = None): self._layout = QVBoxLayout() if instance is not None: self._instance = instance self._enabled_dependencies = [] self._state_saver = None self._layouts = [QVBoxLayout()] self._cur_tab_widget = None self._cur_splitter = None def set_state_saver(self, saver: StateSaver): self._state_saver = saver def get_layout(self): """ Return layout of widget :return: layout, contains Widget instance @:rtype: QLayout """ self._layout.addLayout(self._layouts[-1]) return self._layout def get_instance(self): """ Get Qt instance of widget :return: instance of widget (PySide object) :rtype QWidget """ return self._instance def _may_be_enabled(self, is_enabled): if is_enabled: for depends in self._enabled_dependencies: if not depends.get_value(): return False return is_enabled def set_enabled(self, is_enabled: bool = True): """ Set widget enabled :param is_enabled: state of widget: enabled or not :return: Widget object (self) :rtype: Widget """ self._instance.setEnabled(self._may_be_enabled(is_enabled)) return self def add_enabled_dependency(self, dependency: Checkable): self.set_enabled(dependency.get_value()) self._enabled_dependencies.append(dependency) dependency.add_clicked_callback(self.set_enabled) def add_widget(self, widget: "Widget instance", need_store=False, need_stretch=True): """ Add widget to window layout :param widget: Widget unit :param need_store: is need to store state of specified widget :param need_stretch: is need to insert stretch around widget :return: widget instance """ if need_stretch: self.get_current_layout().addStretch() self.get_current_layout().addLayout(widget.get_layout()) if need_stretch: self.get_current_layout().addStretch() if need_store and self._state_saver is not None: self._state_saver.add_widget(widget) return widget def add_widgets(self, widgets: "list of Widget instances"): """ Add list of widgets to window layout :param widgets: Widget units :return: None """ for widget in widgets: self.get_current_layout().addLayout(widget.get_layout()) def start_horizontal(self): """ Start horizontal components insertion :return: None """ if isinstance(self.get_current_layout(), QHBoxLayout): return layout = QHBoxLayout() self.get_current_layout().addLayout(layout) self._layouts.append(layout) def start_vertical(self): """ Start vertical components insertion :return: None """ if isinstance(self.get_current_layout(), QVBoxLayout): return layout = QVBoxLayout() self.get_current_layout().addLayout(layout) self._layouts.append(layout) def group_horizontal(self, widgets: list): """ Place list of widgets horizontal :param widgets: list of widgets :return: None """ self.start_horizontal() self.add_widgets(widgets) self.cancel() def group_vertical(self, widgets: "list of Widget instances"): """ Place list of widgets vertical :param widgets: list of widgets :return: None """ self.start_vertical() self.add_widgets(widgets) self.cancel() def start_splitter(self, orientation='vertical'): self._cur_splitter = QSplitter() self._cur_splitter.setOrientation( Qt.Orientation.Horizontal if orientation == 'horizontal' else Qt.Orientation.Vertical) self.get_current_layout().addWidget(self._cur_splitter) def add_splitter_space(self): self._layouts.append(QVBoxLayout()) widget = QWidget() widget.setLayout(self.get_current_layout()) self._cur_splitter.addWidget(widget) def get_current_layout(self): """ Return current layout :return: layout of type QLayout """ return self._layouts[-1] def add_to_group_box(self, group_name: str, widgets: "list of Widget instances"): """ Place layout to group box :param group_name: name of group :param widgets: list of widgets, that been placed to group :return: None """ self.start_group_box(group_name) for widget in widgets: self.add_widget(widget) self.cancel() def start_group_box(self, name: str): """ Start group box :return: """ group_box = QGroupBox(name) group_box_layout = QVBoxLayout() group_box.setLayout(group_box_layout) self.get_current_layout().addWidget(group_box) self._layouts.append(group_box_layout) def cancel(self): """ Cnacel last format append :return: None """ del self._layouts[-1] def insert_text_label(self, text, is_link=False): widget = QLabel(text) widget.setOpenExternalLinks(is_link) self.get_current_layout().addWidget(widget) def insert_tab_space(self): self._cur_tab_widget = QTabWidget() self.get_current_layout().addWidget(self._cur_tab_widget) def add_tab(self, name): self._layouts.append(QVBoxLayout()) widget = QWidget() widget.setLayout(self.get_current_layout()) self._cur_tab_widget.addTab(widget, name)
class Ui_FE14MapEditor(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.toolbar = QToolBar() self.toggle_coordinate_type_action = QAction("Toggle Coordinate Type") self.refresh_action = QAction("Refresh") self.refresh_action.setShortcut(QKeySequence("Ctrl+R")) self.copy_spawn_action = QAction("Copy Spawn") self.copy_spawn_action.setShortcut(QKeySequence("Ctrl+C")) self.paste_spawn_action = QAction("Paste Spawn") self.paste_spawn_action.setShortcut(QKeySequence("Ctrl+V")) self.add_spawn_action = QAction("Add Spawn") self.delete_spawn_action = QAction("Delete Spawn") self.add_group_action = QAction("Add Group") self.delete_group_action = QAction("Delete Group") self.add_tile_action = QAction("Add Tile") self.toggle_mode_action = QAction("Toggle Mode") self.undo_action = QAction("Undo") self.undo_action.setShortcut(QKeySequence("Ctrl+Z")) self.redo_action = QAction("Redo") self.redo_action.setShortcut(QKeySequence("Ctrl+Shift+Z")) self.toolbar.addActions( [self.toggle_coordinate_type_action, self.refresh_action]) self.toolbar.addSeparator() self.toolbar.addActions([ self.copy_spawn_action, self.paste_spawn_action, self.add_spawn_action, self.delete_spawn_action, self.add_group_action, self.delete_group_action ]) self.toolbar.addSeparator() self.toolbar.addAction(self.add_tile_action) self.toolbar.addSeparator() self.toolbar.addAction(self.toggle_mode_action) self.toolbar.addSeparator() self.toolbar.addActions([self.undo_action, self.redo_action]) self.addToolBar(self.toolbar) self.model_view = QTreeView() self.model_view.setHeaderHidden(True) self.grid = FE14MapGrid() self.grid_scroll = QScrollArea() self.grid_scroll.setWidgetResizable(True) self.grid_scroll.setWidget(self.grid) self.tile_list = QListView() self.terrain_pane = FE14TerrainEditorPane() self.spawn_pane = FE14SpawnEditorPane() self.model_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.model_view_context_menu = QMenu() self.model_view_context_menu.addActions( [self.toggle_coordinate_type_action, self.refresh_action]) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addActions([ self.copy_spawn_action, self.paste_spawn_action, self.add_spawn_action, self.delete_spawn_action, self.add_group_action, self.delete_group_action ]) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addAction(self.add_tile_action) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addAction(self.toggle_mode_action) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addActions( [self.undo_action, self.redo_action]) self.status_bar = QStatusBar() self.coordinate_type_label = QLabel() self.status_bar.addPermanentWidget(self.coordinate_type_label) self.setStatusBar(self.status_bar) self.main_widget = QSplitter() self.main_widget.setOrientation(QtCore.Qt.Horizontal) self.main_widget.addWidget(self.model_view) self.main_widget.addWidget(self.grid_scroll) self.main_widget.addWidget(self.spawn_pane) self.main_widget.addWidget(self.terrain_pane) self.setCentralWidget(self.main_widget)
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setWindowTitle("Lab-Data-Converter") self.create_actions() self.setup_menu() self.settings = QSettings("UCB", "Lab-Data-Converter") self.data_sets = {} self.output_widget = OutputWidget(self) self.data_table = DataTabel(self) self.splitter1 = QSplitter() self.splitter1.setOrientation(Qt.Vertical) self.splitter1.addWidget(self.data_table) self.splitter1.addWidget(self.output_widget) self.splitter1.setSizes([200, 100]) #self.splitter1.setStretchFactor(0, 8) #self.splitter1.setStretchFactor(1, 4) self.setCentralWidget(self.splitter1) QDir.setCurrent(QStandardPaths.standardLocations( QStandardPaths.DocumentsLocation)[-1]) if (self.settings.value("work_dir")): try: QDir.setCurrent(self.settings.value("work_dir")) except: pass def create_actions(self): self.open_act = QAction("Open", self) self.open_act.setShortcuts(QKeySequence.Open) self.open_act.setStatusTip("Open one or multiple files"); self.open_act.triggered.connect(self.open_file) self.close_act = QAction("Close", self) self.close_act.setShortcuts(QKeySequence.Close) self.close_act.setStatusTip("Close selected files"); self.close_act.triggered.connect(self.close_file) def setup_menu(self): self.menuBar().addAction(self.open_act) self.menuBar().addAction(self.close_act) def open_file(self): files = QFileDialog.getOpenFileNames(self, "Open file", QDir.currentPath(), "Files (*.csv *.txt)") for file in files[0]: if (file not in self.data_sets): data = read_file(file) if (data is not None): self.data_sets[file] = data self.settings.setValue("work_dir", QFileInfo(file).absolutePath()) self.output_widget.add_to_list(file) else: QMessageBox.critical(self, "Main window", "File " + file[0] + " has an unknown format!") self.data_table.initialize(self.data_sets) def close_file(self): files_to_close = self.output_widget.files_selected() for file_to_close in files_to_close: self.data_sets.pop(file_to_close, None) if (not self.output_widget.remove_from_list(file_to_close)): raise Exception("{} doesn't exist in checkboxes!".format(file_to_close)) self.data_table.initialize(self.data_sets) def on_convert(self): export_dialog = QFileDialog() outfile = export_dialog.getSaveFileName(self, "Export to csv file", QDir.currentPath() + "/preview.csv", "csv (*.csv)") info = self.output_widget.output_info() min_rows = min([data.shape[0] for data in self.data_sets.values()]) if (info["first_line"] < 1 or info["first_line"] > min_rows or info["last_line"] < 1 or info["last_line"] > min_rows or info["first_line"] > info["last_line"]): QMessageBox.critical(self, "Row range", "Invalid row range!") else: self.write_file(outfile[0], info["files"], info["first_line"], info["last_line"]) def write_file(self, out_file, in_files, first, last): res = pd.DataFrame() for in_file in in_files: data = self.data_sets[in_file] base_name = QFileInfo(in_file).baseName() if base_name in res: QMessageBox.warning(self, "Export", "Identical file name! Use full path instead.") base_name = in_file for col in data.columns: res[base_name+":"+col] = data[col][first-1:last] res.to_csv(out_file, index=False) QMessageBox.information(self, "Export", "Selected rows have been written to {}.".format(out_file))
def __init__(self, path_to_rom=""): super(MainWindow, self).__init__() self.setWindowIcon(icon("foundry.ico")) file_menu = QMenu("File") open_rom_action = file_menu.addAction("&Open ROM") open_rom_action.triggered.connect(self.on_open_rom) self.open_m3l_action = file_menu.addAction("&Open M3L") self.open_m3l_action.triggered.connect(self.on_open_m3l) file_menu.addSeparator() self.save_rom_action = file_menu.addAction("&Save ROM") self.save_rom_action.triggered.connect(self.on_save_rom) self.save_rom_as_action = file_menu.addAction("&Save ROM as ...") self.save_rom_as_action.triggered.connect(self.on_save_rom_as) """ file_menu.AppendSeparator() """ self.save_m3l_action = file_menu.addAction("&Save M3L") self.save_m3l_action.triggered.connect(self.on_save_m3l) """ file_menu.Append(ID_SAVE_LEVEL_TO, "&Save Level to", "") file_menu.AppendSeparator() file_menu.Append(ID_APPLY_IPS_PATCH, "&Apply IPS Patch", "") file_menu.AppendSeparator() file_menu.Append(ID_ROM_PRESET, "&ROM Preset", "") """ file_menu.addSeparator() settings_action = file_menu.addAction("&Settings") settings_action.triggered.connect(show_settings) file_menu.addSeparator() exit_action = file_menu.addAction("&Exit") exit_action.triggered.connect(lambda _: self.close()) self.menuBar().addMenu(file_menu) """ edit_menu = wx.Menu() edit_menu.Append(ID_EDIT_LEVEL, "&Edit Level", "") edit_menu.Append(ID_EDIT_OBJ_DEFS, "&Edit Object Definitions", "") edit_menu.Append(ID_EDIT_PALETTE, "&Edit Palette", "") edit_menu.Append(ID_EDIT_GRAPHICS, "&Edit Graphics", "") edit_menu.Append(ID_EDIT_MISC, "&Edit Miscellaneous", "") edit_menu.AppendSeparator() edit_menu.Append(ID_FREE_FORM_MODE, "&Free form Mode", "") edit_menu.Append(ID_LIMIT_SIZE, "&Limit Size", "") """ self.level_menu = QMenu("Level") self.select_level_action = self.level_menu.addAction("&Select Level") self.select_level_action.triggered.connect(self.open_level_selector) self.reload_action = self.level_menu.addAction("&Reload Level") self.reload_action.triggered.connect(self.reload_level) self.level_menu.addSeparator() self.edit_header_action = self.level_menu.addAction("&Edit Header") self.edit_header_action.triggered.connect(self.on_header_editor) self.edit_autoscroll = self.level_menu.addAction("Edit Autoscrolling") self.edit_autoscroll.triggered.connect(self.on_edit_autoscroll) self.menuBar().addMenu(self.level_menu) self.object_menu = QMenu("Objects") view_blocks_action = self.object_menu.addAction("&View Blocks") view_blocks_action.triggered.connect(self.on_block_viewer) view_objects_action = self.object_menu.addAction("&View Objects") view_objects_action.triggered.connect(self.on_object_viewer) self.object_menu.addSeparator() view_palettes_action = self.object_menu.addAction( "View Object Palettes") view_palettes_action.triggered.connect(self.on_palette_viewer) self.menuBar().addMenu(self.object_menu) self.view_menu = QMenu("View") self.view_menu.triggered.connect(self.on_menu) action = self.view_menu.addAction("Mario") action.setProperty(ID_PROP, ID_MARIO) action.setCheckable(True) action.setChecked(SETTINGS["draw_mario"]) action = self.view_menu.addAction("&Jumps on objects") action.setProperty(ID_PROP, ID_JUMP_OBJECTS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jump_on_objects"]) action = self.view_menu.addAction("Items in blocks") action.setProperty(ID_PROP, ID_ITEM_BLOCKS) action.setCheckable(True) action.setChecked(SETTINGS["draw_items_in_blocks"]) action = self.view_menu.addAction("Invisible items") action.setProperty(ID_PROP, ID_INVISIBLE_ITEMS) action.setCheckable(True) action.setChecked(SETTINGS["draw_invisible_items"]) action = self.view_menu.addAction("Autoscroll Path") action.setProperty(ID_PROP, ID_AUTOSCROLL) action.setCheckable(True) action.setChecked(SETTINGS["draw_autoscroll"]) self.view_menu.addSeparator() action = self.view_menu.addAction("Jump Zones") action.setProperty(ID_PROP, ID_JUMPS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jumps"]) action = self.view_menu.addAction("&Grid lines") action.setProperty(ID_PROP, ID_GRID_LINES) action.setCheckable(True) action.setChecked(SETTINGS["draw_grid"]) action = self.view_menu.addAction("Resize Type") action.setProperty(ID_PROP, ID_RESIZE_TYPE) action.setCheckable(True) action.setChecked(SETTINGS["draw_expansion"]) self.view_menu.addSeparator() action = self.view_menu.addAction("&Block Transparency") action.setProperty(ID_PROP, ID_TRANSPARENCY) action.setCheckable(True) action.setChecked(SETTINGS["block_transparency"]) self.view_menu.addSeparator() self.view_menu.addAction( "&Save Screenshot of Level").triggered.connect(self.on_screenshot) """ self.view_menu.Append(ID_BACKGROUND_FLOOR, "&Background & Floor", "") self.view_menu.Append(ID_TOOLBAR, "&Toolbar", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_ZOOM, "&Zoom", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_USE_ROM_GRAPHICS, "&Use ROM Graphics", "") self.view_menu.Append(ID_PALETTE, "&Palette", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_MORE, "&More", "") """ self.menuBar().addMenu(self.view_menu) help_menu = QMenu("Help") """ help_menu.Append(ID_ENEMY_COMPATIBILITY, "&Enemy Compatibility", "") help_menu.Append(ID_TROUBLESHOOTING, "&Troubleshooting", "") help_menu.AppendSeparator() help_menu.Append(ID_PROGRAM_WEBSITE, "&Program Website", "") help_menu.Append(ID_MAKE_A_DONATION, "&Make a Donation", "") help_menu.AppendSeparator() """ update_action = help_menu.addAction("Check for updates") update_action.triggered.connect(self.on_check_for_update) help_menu.addSeparator() video_action = help_menu.addAction("Feature Video on YouTube") video_action.triggered.connect(lambda: open_url(feature_video_link)) github_action = help_menu.addAction("Github Repository") github_action.triggered.connect(lambda: open_url(github_link)) discord_action = help_menu.addAction("SMB3 Rom Hacking Discord") discord_action.triggered.connect(lambda: open_url(discord_link)) help_menu.addSeparator() about_action = help_menu.addAction("&About") about_action.triggered.connect(self.on_about) self.menuBar().addMenu(help_menu) self.block_viewer = None self.object_viewer = None self.level_ref = LevelRef() self.level_ref.data_changed.connect(self._on_level_data_changed) self.context_menu = ContextMenu(self.level_ref) self.context_menu.triggered.connect(self.on_menu) self.level_view = LevelView(self, self.level_ref, self.context_menu) self.scroll_panel = QScrollArea() self.scroll_panel.setWidgetResizable(True) self.scroll_panel.setWidget(self.level_view) self.setCentralWidget(self.scroll_panel) self.spinner_panel = SpinnerPanel(self, self.level_ref) self.spinner_panel.zoom_in_triggered.connect(self.level_view.zoom_in) self.spinner_panel.zoom_out_triggered.connect(self.level_view.zoom_out) self.spinner_panel.object_change.connect(self.on_spin) self.object_list = ObjectList(self, self.level_ref, self.context_menu) self.object_dropdown = ObjectDropdown(self) self.object_dropdown.object_selected.connect( self._on_placeable_object_selected) self.level_size_bar = LevelSizeBar(self, self.level_ref) self.enemy_size_bar = EnemySizeBar(self, self.level_ref) self.jump_list = JumpList(self, self.level_ref) self.jump_list.add_jump.connect(self.on_jump_added) self.jump_list.edit_jump.connect(self.on_jump_edit) self.jump_list.remove_jump.connect(self.on_jump_removed) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.object_list) splitter.setStretchFactor(0, 1) splitter.addWidget(self.jump_list) splitter.setChildrenCollapsible(False) level_toolbar = QToolBar("Level Info Toolbar", self) level_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) level_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) level_toolbar.setOrientation(Qt.Horizontal) level_toolbar.setFloatable(False) level_toolbar.addWidget(self.spinner_panel) level_toolbar.addWidget(self.object_dropdown) level_toolbar.addWidget(self.level_size_bar) level_toolbar.addWidget(self.enemy_size_bar) level_toolbar.addWidget(splitter) level_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.RightToolBarArea, level_toolbar) self.object_toolbar = ObjectToolBar(self) self.object_toolbar.object_selected.connect( self._on_placeable_object_selected) object_toolbar = QToolBar("Object Toolbar", self) object_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) object_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) object_toolbar.setFloatable(False) object_toolbar.addWidget(self.object_toolbar) object_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.LeftToolBarArea, object_toolbar) self.menu_toolbar = QToolBar("Menu Toolbar", self) self.menu_toolbar.setOrientation(Qt.Horizontal) self.menu_toolbar.setIconSize(QSize(20, 20)) self.menu_toolbar.addAction( icon("settings.svg"), "Editor Settings").triggered.connect(show_settings) self.menu_toolbar.addSeparator() self.menu_toolbar.addAction( icon("folder.svg"), "Open ROM").triggered.connect(self.on_open_rom) self.menu_toolbar.addAction( icon("save.svg"), "Save Level").triggered.connect(self.on_save_rom) self.menu_toolbar.addSeparator() self.undo_action = self.menu_toolbar.addAction(icon("rotate-ccw.svg"), "Undo Action") self.undo_action.triggered.connect(self.level_ref.undo) self.undo_action.setEnabled(False) self.redo_action = self.menu_toolbar.addAction(icon("rotate-cw.svg"), "Redo Action") self.redo_action.triggered.connect(self.level_ref.redo) self.redo_action.setEnabled(False) self.menu_toolbar.addSeparator() play_action = self.menu_toolbar.addAction(icon("play-circle.svg"), "Play Level") play_action.triggered.connect(self.on_play) play_action.setWhatsThis( "Opens an emulator with the current Level set to 1-1.\nSee Settings." ) self.menu_toolbar.addSeparator() self.menu_toolbar.addAction(icon("zoom-out.svg"), "Zoom Out").triggered.connect( self.level_view.zoom_out) self.menu_toolbar.addAction(icon("zoom-in.svg"), "Zoom In").triggered.connect( self.level_view.zoom_in) self.menu_toolbar.addSeparator() header_action = self.menu_toolbar.addAction(icon("tool.svg"), "Edit Level Header") header_action.triggered.connect(self.on_header_editor) header_action.setWhatsThis( "<b>Header Editor</b><br/>" "Many configurations regarding the level are done in its header, like the length of " "the timer, or where and how Mario enters the level.<br/>") self.jump_destination_action = self.menu_toolbar.addAction( icon("arrow-right-circle.svg"), "Go to Jump Destination") self.jump_destination_action.triggered.connect( self._go_to_jump_destination) self.jump_destination_action.setWhatsThis( "Opens the level, that can be reached from this one, e.g. by entering a pipe." ) self.menu_toolbar.addSeparator() whats_this_action = QWhatsThis.createAction() whats_this_action.setWhatsThis( "Click on parts of the editor, to receive help information.") whats_this_action.setIcon(icon("help-circle.svg")) whats_this_action.setText("Starts 'What's this?' mode") self.menu_toolbar.addAction(whats_this_action) self.menu_toolbar.addSeparator() self.warning_list = WarningList(self, self.level_ref) warning_action = self.menu_toolbar.addAction( icon("alert-triangle.svg"), "Warning Panel") warning_action.setWhatsThis("Shows a list of warnings.") warning_action.triggered.connect(self.warning_list.show) warning_action.setDisabled(True) self.warning_list.warnings_updated.connect(warning_action.setEnabled) self.addToolBar(Qt.TopToolBarArea, self.menu_toolbar) self.status_bar = ObjectStatusBar(self, self.level_ref) self.setStatusBar(self.status_bar) self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self, self.remove_selected_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_X), self, self._cut_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_C), self, self._copy_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_V), self, self._paste_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Z), self, self.level_ref.undo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Y), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Z), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Plus), self, self.level_view.zoom_in) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Minus), self, self.level_view.zoom_out) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_A), self, self.level_view.select_all) self.on_open_rom(path_to_rom) self.showMaximized()
class MainWindow(QtWidgets.QMainWindow): def __init__(self, appctxt=None): super().__init__() self.appcontext = appctxt self.BASE_TITLE = 'PyMarkup' self.REFRESH_DELAY_MS = 200 self.modified = False self.refresh_timer = QtCore.QTimer() self.refresh_timer.setSingleShot(True) self.refresh_timer.setInterval(self.REFRESH_DELAY_MS) self.refresh_timer.timeout.connect(self.Refresh) self.settings = self.LoadSettings() self.init_widgets() self.init_layout() self.init_mainmenu() self.python_path = None self.save_html = True # Always true unless disabled in debug mode try: self.css = load_css(self.GetPath('template_css')) except KeyError: self.css = '' self.LoadResources() # print('Singles:',len(self.singles.keys())) # self.singles = {} # print('Macros:',len(self.macros.keys())) self.macros = {} # print('Images:',len(self.images.keys())) self.images = {} self.subitems = {} try: self.LoadFileFromPath(self.settings['paths']['lastopened']) except KeyError: pass self.Refresh() def GetPath(self,key): default_names = { 'template_css': 'template.css', 'template_html': 'template.html', 'singles': 'singles.csv', 'table_products': 'singles.csv', 'table_macros': 'macros.csv', 'img_folder': 'img', } try: path = self.settings['paths'][key] if os.path.exists(path): # print('Returning path for {}: {}'.format(key,self.settings['paths'][key])) return path except KeyError: pass try: return os.path.join(self.settings['paths']['sysfolder'],default_names[key]) except KeyError: pass return None # Load two tables and images def LoadResources(self): self.singles = read_csv(self.GetPath('table_products')) return self.subitems = get_subitems(self.singles) self.macros = read_csv_adv(self.GetPath('table_macros')) self.images = get_images(self.GetPath('img_folder')) self.ConsoleLog('Database aggiornato.') def ScheduleRefresh(self): # print('Refresh timer started for 100 ms') self.modified = True self.UpdateTitle() self.refresh_timer.start() def init_widgets(self): if ADD_WEBENGINE: self.browser = Browser(self) # self.browser.setZoomFactor(1.5) # Valid values: 0.25 to 5.0 self.browser.setZoomFactor(self.settings['browserzoomfactor']) # self.texteditor = QtWidgets.QPlainTextEdit() self.texteditor = QCodeEditor() self.texteditor.setPlainText('cliente = "Asd"') # self.texteditor.setPlainText(self.settings['texteditor']) self.texteditor.zoomIn(self.settings['zoom_texteditor']) self.texteditor.textChanged.connect(self.ScheduleRefresh) self.console = QtWidgets.QPlainTextEdit() self.console.setReadOnly(True) self.console.zoomIn(self.settings['zoom_console']) def init_mainmenu(self): menubar = self.menuBar() # filemenu = menubar.addMenu('File') # viewmenu = menubar.addMenu('Visualizza') menus = { 'File': [ ['Nuovo file', 'Ctrl+N', self.NewFile], ['Apri...', 'Ctrl+O', self.ChooseFile], ['Salva', 'Ctrl+S', self.Save], ['Salva con nome...', 'Ctrl+Shift+S', self.SaveAs], [], ['Esporta PDF (preventivo)', 'Ctrl+E', self.RenderPDF_estimate], ['Esporta PDF (proforma)', 'Ctrl+T', self.RenderPDF_proforma], [], ['Chiudi', 'Ctrl+Shift+Q', self.close], ], 'Visualizza': [ ['Ingrandisci editor',None,self.ZoomInEditor], ['Riduci editor',None,self.ZoomOutEditor], [], ['Ingrandisci anteprima',None,self.ZoomInBrowser], ['Riduci anteprima',None,self.ZoomOutBrowser], ], 'Strumenti': [ ['Aggiorna anteprima', 'Ctrl+R', self.Refresh], ['Aggiorna database', 'Ctrl+L', self.LoadResources], ['Impostazioni...', 'Ctrl+I', self.OpenSettingsDialog], ] } if DEBUG: menus['Debug'] = [ # ['Print pwd', 'Ctrl+P', self.PrintPwd], ['Which Python', None, self.RunSubprocess], ['Print this folder', None, self.PrintThisFolder], ['Save html on', None, self.EnableSaveHTML], ['Save html off', None, self.DisableSaveHTML], ['Print settings', None, self.PrintSettings], ] for menu_name,entries in menus.items(): menu = menubar.addMenu(menu_name) for data in entries: if len(data)>0: label,shortcut,function = data # label,shortcut,function = item new_action = QAction(label,self) if shortcut: new_action.setShortcut(shortcut) new_action.triggered.connect(function) menu.addAction(new_action) else: menu.addSeparator() def EnableSaveHTML(self): self.save_html = True; def DisableSaveHTML(self): self.save_html = False; def PrintSettings(self): settings = json.dumps(self.settings,indent=4) self.ConsoleLog(settings) paths = [ 'template_css', 'template_html', 'singles', 'table_products', 'table_macros', 'img_folder', ] print('Paths:') for path in paths: print(self.GetPath(path)) print('\nSettings:') print(settings) def PrintPwd(self): path = os.path.abspath('./') self.ConsoleLog(path) def PrintThisFolder(self): self.ConsoleLog(THIS_FOLDER); def ZoomInEditor(self): self.texteditor.zoomIn() def ZoomOutEditor(self): self.texteditor.zoomOut() def ZoomInBrowser(self): self.browser.setZoomFactor(self.browser.zoomFactor() + 0.1) def ZoomOutBrowser(self): self.browser.setZoomFactor(self.browser.zoomFactor() - 0.1) def Dummy(self): return def GetPythonPath(self): cmd = 'which python3' response = run(cmd, shell=True, stdout=PIPE, stderr=PIPE) return response.stdout.decode('utf-8') def RunSubprocess(self): self.ConsoleLog(self.GetPythonPath()) ''' cmd1 = '/Users/pc/Dev/pymarkup_installer_redux/local/sys_folder/xhtml2pdf.command' cmd2 = 'pwd > /Users/pc/Dev/pymarkup_installer_redux/local/sys_folder/pwd.txt' cmd = 'which python3' response = run(cmd, shell=True, stdout=PIPE, stderr=PIPE) #self.ConsoleLog(str(response)) log = [cmd, response.returncode, response.stdout.decode('utf-8'), response.stderr] #log = [cmd, response] self.ConsoleLog('\n'.join([str(x) for x in log])) ''' ''' process = os.popen("which python") result = process.read() print(result) self.ConsoleLog(result) ''' def ConfirmClose(self): # print('ConfirmClose') if self.modified: dialog = QMessageBox() # dialog.setIcon(QMessageBox.Question) dialog.setWindowTitle('Modifiche non salvate') dialog.setInformativeText(u"Abbandonare le modifiche in corso?") dialog.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) choice = dialog.exec_() return choice == QMessageBox.Ok return True def ChooseFile(self): if self.ConfirmClose(): chosen_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Apri file', '', '*.toml',) if chosen_path[0] != '': self.settings['paths']['lastopened'] = chosen_path[0] self.LoadFileFromPath(chosen_path[0]) self.modified = False self.UpdateTitle() def LoadFileFromPath(self,path): if os.path.exists(path): with open(path, 'r+') as file: self.texteditor.setPlainText(file.read()) self.modified = False self.UpdateTitle() def NewFile(self): if self.ConfirmClose(): self.modified = False self.settings['paths']['lastopened'] = '' self.setWindowTitle(self.BASE_TITLE+' - senza titolo') self.texteditor.setPlainText('') self.Refresh() self.DisplayHTML('') self.ConsoleLog('Nuovo file creato.') def Save(self): path = self.settings['paths']['lastopened'] if os.path.exists(path): try: with open(path,'w+') as file: file.write(self.texteditor.toPlainText()) self.ConsoleLog('File salvato:\n'+path) self.modified = False self.UpdateTitle() except Exception as e: self.ConsoleLog(e) def SaveAs(self): path,_ = QtWidgets.QFileDialog().getSaveFileName(self, "Salva file", '', "Toml (*.toml)") if path != '': try: with open(path,'w+') as file: file.write(self.texteditor.toPlainText()) self.settings['paths']['lastopened'] = path self.modified = False self.ConsoleLog('File salvato:\n'+path) self.UpdateTitle() except Exception as e: print(str(e)) self.ConsoleLog(str(e)) def UpdateTitle(self): path = self.settings['paths']['lastopened'] new_title = '{} - {}'.format(self.BASE_TITLE,path) if self.modified: new_title += ' (modificato)' self.setWindowTitle(new_title) def init_layout(self): self.setWindowTitle(self.BASE_TITLE) ''' left_layout = QtWidgets.QVBoxLayout() left_layout.addWidget(self.texteditor) left_layout.addWidget(self.console) left_layout_widget = QtWidgets.QWidget() left_layout_widget.setLayout(left_layout) ''' self.layout_left_splitter = QSplitter() self.layout_left_splitter.setOrientation(QtCore.Qt.Vertical) self.layout_left_splitter.addWidget(self.texteditor) self.layout_left_splitter.addWidget(self.console) self.layout_panes = QSplitter() self.layout_panes.addWidget(self.layout_left_splitter) if ADD_WEBENGINE: self.layout_panes.addWidget(self.browser) layout = QtWidgets.QVBoxLayout() layout.addWidget(self.layout_panes) # Set size for left splitter for settings_key,splitter in [ ['left_splitter_sizes', self.layout_left_splitter], ['splitter_sizes', self.layout_panes], ]: splitter_size = self.settings.get(settings_key) if splitter_size is not None: splitter.setSizes(splitter_size) # Set size for main splitter # splitter_sizes = self.settings.get('splitter_sizes') # if splitter_sizes is not None: # self.layout_panes.setSizes(splitter_sizes) centralWidget = QtWidgets.QWidget() centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.setGeometry(*self.settings['geometry']) def RenderHTML(self, **kwargs): toml_text = self.texteditor.toPlainText() toml_model = read_toml(toml_text, css=self.css) order_model = merge(toml_model, self.singles, self.subitems, self.macros, self.images) # print('TOML merge OK') template_path = 'template.html' # print('Template path:',template_path) path_html = self.GetPath('template_html') path_css = self.GetPath('template_css') return render_html(order_model, path_html=path_html, path_css=path_css, **kwargs) def RenderPDF(self, **kwargs): folder,filename = os.path.split(self.settings['paths']['lastopened']) name,_ = os.path.splitext(filename) defaultpath = os.path.join(folder,name+'.pdf') # print('Last opened folder/filename:',folder) savepath,_ = QtWidgets.QFileDialog().getSaveFileName(self, "Salva PDF", defaultpath, "PDF (*.pdf)") if savepath == '': return path_sys = self.settings['paths']['sysfolder'] html = self.RenderHTML(**kwargs) htmlpath = os.path.join(path_sys,'tmp.html') # Save 'tmp.html' # MAY BE DISABLED FOR DEBUGGING if self.save_html: with open(htmlpath, 'w+', encoding='UTF-8') as file: file.write(html) sys_folder = self.settings['paths']['sysfolder'] #path_script = os.path.join(self.settings['paths']['renderpdf'], 'Contents/MacOS/renderpdf') path_script = os.path.join(sys_folder, 'renderpdf.py') #self.ConsoleLog(html) print('-----') print('RenderPDF paths:') print(path_script, os.path.exists(path_script)) print(htmlpath, os.path.exists(htmlpath)) print(savepath) print('-----') #render_pdf(path_script,htmlpath,savepath) #python_path = '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3' python_path = self.settings['paths']['python'] command = f'{python_path} "{path_script}" --args "{savepath}"' #command = f'xhtml2pdf {htmlpath} {savepath}' ''' cmd_lines = [ 'from xhtml2pdf import pisa', f'resultFile = open("{savepath}", "w+b")', f'htmlFile = open("{htmlpath}","r+")', f'pisa.CreatePDF(htmlFile.read(),dest=resultFile)', 'resultFile.close()', 'htmlFile.close()', ] command = "python3 -c '" + ';'.join(cmd_lines) + "'" ''' #command = f'python3 "{path_script}" "{savepath}"' # Path to renderpdf.command (as a proxy to execute renderpdf.py) #command = os.path.join(sys_folder, 'xhtml2pdf.command') print('Calling command:') print(command) print('-----') try: #result = call(command,shell=True) # shell=True is needed because subprocess.call is just a wrapper for Popen result = call(command, shell=True) self.ConsoleLog(command) #self.ConsoleLog('Xhtml2 call result: '+str(result)) #self.ConsoleLog('PDF salvato: '+savepath) except FileNotFoundError as e: print('Subprocess.call raised an exception:') print(e) self.ConsoleLog(str(e)) except Exception as e: self.ConsoleLog(str(e)) with open(os.path.join(sys_folder,'applog.txt'),'r+') as file: file.write(str(e)) # os.startfile('rendered.pdf') def RenderPDF_estimate(self): self.RenderPDF(preview=False,render_estimate=True) def RenderPDF_proforma(self): self.RenderPDF(preview=False,render_proforma=True) def DisplayHTML(self, html): # print('Display HTML') self.browser.SetHTML(html) def Refresh(self): # print('Refresh') try: html = self.RenderHTML(preview=True, render_estimate=True) self.DisplayHTML(html) self.ConsoleLog('OK') # self.ConsoleLog('Render HTML OK. Html length: {}\n{}'.format(len(html),html)) except Exception as e: print(e) self.ConsoleLog(str(e)) def ConsoleLog(self,msg): self.console.setPlainText(msg) def LoadSettings(self): default_settings = { 'geometry': [500,300,1400,1000], # 'texteditor': '', 'splitter_sizes': None, 'left_splitter_sizes': None, 'browserzoomfactor': 1, 'paths': { 'lastopened': '', 'python': '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3', }, 'zoom_texteditor': 2, 'zoom_console': 2, } qsettings = QtCore.QSettings('Company','Appname') jsontext = qsettings.value('settings',None) # print('Json settings from QSettings:',jsontext) settings = json.loads(jsontext) if jsontext else {} for key in default_settings.keys(): if key in settings: default_settings[key] = settings[key] # Check all paths and delete them if they are broken for key,path in default_settings['paths'].items(): if not os.path.exists(path): default_settings['paths'][key] = '' return default_settings def SaveSettings(self): # print('Saving settings') g = self.geometry() self.settings['geometry'] = [g.x(), g.y() ,g.width(), g.height()] # self.settings['texteditor'] = self.texteditor.toPlainText() self.settings['splitter_sizes'] = self.layout_panes.sizes() self.settings['left_splitter_sizes'] = self.layout_left_splitter.sizes() self.settings['browserzoomfactor'] = self.browser.zoomFactor() qsettings = QtCore.QSettings('Company','Appname') qsettings.setValue('settings', json.dumps(self.settings)) def OpenSettingsDialog(self): dialog = SettingsDialog(self) dialog.exec_() def closeEvent(self,event): # print('closeEvent') if self.ConfirmClose(): event.ignore() self.SaveSettings() # print('Closing program (settings saved)') event.accept() else: event.ignore() '''
def __init__(self, path_to_rom=""): super(MainWindow, self).__init__() self.setWindowIcon(icon("foundry.ico")) file_menu = QMenu("File") open_rom_action = file_menu.addAction("&Open ROM") open_rom_action.triggered.connect(self.on_open_rom) self.open_m3l_action = file_menu.addAction("&Open M3L") self.open_m3l_action.triggered.connect(self.on_open_m3l) file_menu.addSeparator() save_rom_action = file_menu.addAction("&Save ROM") save_rom_action.triggered.connect(self.on_save_rom) save_rom_as_action = file_menu.addAction("&Save ROM as ...") save_rom_as_action.triggered.connect(self.on_save_rom_as) """ file_menu.AppendSeparator() """ self.save_m3l_action = file_menu.addAction("&Save M3L") self.save_m3l_action.triggered.connect(self.on_save_m3l) """ file_menu.Append(ID_SAVE_LEVEL_TO, "&Save Level to", "") file_menu.AppendSeparator() file_menu.Append(ID_APPLY_IPS_PATCH, "&Apply IPS Patch", "") file_menu.AppendSeparator() file_menu.Append(ID_ROM_PRESET, "&ROM Preset", "") """ file_menu.addSeparator() settings_action = file_menu.addAction("&Settings") settings_action.triggered.connect(show_settings) file_menu.addSeparator() exit_action = file_menu.addAction("&Exit") exit_action.triggered.connect(lambda _: self.close()) self.menuBar().addMenu(file_menu) """ edit_menu = wx.Menu() edit_menu.Append(ID_EDIT_LEVEL, "&Edit Level", "") edit_menu.Append(ID_EDIT_OBJ_DEFS, "&Edit Object Definitions", "") edit_menu.Append(ID_EDIT_PALETTE, "&Edit Palette", "") edit_menu.Append(ID_EDIT_GRAPHICS, "&Edit Graphics", "") edit_menu.Append(ID_EDIT_MISC, "&Edit Miscellaneous", "") edit_menu.AppendSeparator() edit_menu.Append(ID_FREE_FORM_MODE, "&Free form Mode", "") edit_menu.Append(ID_LIMIT_SIZE, "&Limit Size", "") """ level_menu = QMenu("Level") select_level_action = level_menu.addAction("&Select Level") select_level_action.triggered.connect(self.open_level_selector) """ level_menu.Append(ID_GOTO_NEXT_AREA, "&Go to next Area", "") level_menu.AppendSeparator() """ self.reload_action = level_menu.addAction("&Reload Level") self.reload_action.triggered.connect(self.reload_level) level_menu.addSeparator() self.edit_header_action = level_menu.addAction("&Edit Header") self.edit_header_action.triggered.connect(self.on_header_editor) """ level_menu.Append(ID_EDIT_POINTERS, "&Edit Pointers", "") """ self.menuBar().addMenu(level_menu) object_menu = QMenu("Objects") view_blocks_action = object_menu.addAction("&View Blocks") view_blocks_action.triggered.connect(self.on_block_viewer) view_objects_action = object_menu.addAction("&View Objects") view_objects_action.triggered.connect(self.on_object_viewer) """ object_menu.AppendSeparator() object_menu.Append(ID_CLONE_OBJECT_ENEMY, "&Clone Object/Enemy", "") object_menu.AppendSeparator() object_menu.Append(ID_ADD_3_BYTE_OBJECT, "&Add 3 Byte Object", "") object_menu.Append(ID_ADD_4_BYTE_OBJECT, "&Add 4 Byte Object", "") object_menu.Append(ID_ADD_ENEMY, "&Add Enemy", "") object_menu.AppendSeparator() object_menu.Append(ID_DELETE_OBJECT_ENEMY, "&Delete Object/Enemy", "") object_menu.Append(ID_DELETE_ALL, "&Delete All", "") """ self.menuBar().addMenu(object_menu) view_menu = QMenu("View") view_menu.triggered.connect(self.on_menu) action = view_menu.addAction("Mario") action.setProperty(ID_PROP, ID_MARIO) action.setCheckable(True) action.setChecked(SETTINGS["draw_mario"]) action = view_menu.addAction("&Jumps on objects") action.setProperty(ID_PROP, ID_JUMP_OBJECTS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jump_on_objects"]) action = view_menu.addAction("Items in blocks") action.setProperty(ID_PROP, ID_ITEM_BLOCKS) action.setCheckable(True) action.setChecked(SETTINGS["draw_items_in_blocks"]) action = view_menu.addAction("Invisible items") action.setProperty(ID_PROP, ID_INVISIBLE_ITEMS) action.setCheckable(True) action.setChecked(SETTINGS["draw_invisible_items"]) view_menu.addSeparator() action = view_menu.addAction("Jump Zones") action.setProperty(ID_PROP, ID_JUMPS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jumps"]) action = view_menu.addAction("&Grid lines") action.setProperty(ID_PROP, ID_GRID_LINES) action.setCheckable(True) action.setChecked(SETTINGS["draw_grid"]) action = view_menu.addAction("Resize Type") action.setProperty(ID_PROP, ID_RESIZE_TYPE) action.setCheckable(True) action.setChecked(SETTINGS["draw_expansion"]) view_menu.addSeparator() action = view_menu.addAction("&Block Transparency") action.setProperty(ID_PROP, ID_TRANSPARENCY) action.setCheckable(True) action.setChecked(SETTINGS["block_transparency"]) view_menu.addSeparator() view_menu.addAction("&Save Screenshot of Level").triggered.connect( self.on_screenshot) """ view_menu.Append(ID_BACKGROUND_FLOOR, "&Background & Floor", "") view_menu.Append(ID_TOOLBAR, "&Toolbar", "") view_menu.AppendSeparator() view_menu.Append(ID_ZOOM, "&Zoom", "") view_menu.AppendSeparator() view_menu.Append(ID_USE_ROM_GRAPHICS, "&Use ROM Graphics", "") view_menu.Append(ID_PALETTE, "&Palette", "") view_menu.AppendSeparator() view_menu.Append(ID_MORE, "&More", "") """ self.menuBar().addMenu(view_menu) help_menu = QMenu("Help") """ help_menu.Append(ID_ENEMY_COMPATIBILITY, "&Enemy Compatibility", "") help_menu.Append(ID_TROUBLESHOOTING, "&Troubleshooting", "") help_menu.AppendSeparator() help_menu.Append(ID_PROGRAM_WEBSITE, "&Program Website", "") help_menu.Append(ID_MAKE_A_DONATION, "&Make a Donation", "") help_menu.AppendSeparator() """ update_action = help_menu.addAction("Check for updates") update_action.triggered.connect(self.on_check_for_update) help_menu.addSeparator() video_action = help_menu.addAction("Feature Video on YouTube") video_action.triggered.connect(lambda: open_url(feature_video_link)) discord_action = help_menu.addAction("SMB3 Rom Hacking Discord") discord_action.triggered.connect(lambda: open_url(discord_link)) help_menu.addSeparator() about_action = help_menu.addAction("&About") about_action.triggered.connect(self.on_about) self.menuBar().addMenu(help_menu) self.level_selector = LevelSelector(parent=self) self.block_viewer = None self.object_viewer = None self.level_ref = LevelRef() self.level_ref.data_changed.connect(self._on_level_data_changed) self.context_menu = ContextMenu(self.level_ref) self.context_menu.triggered.connect(self.on_menu) self.level_view = LevelView(self, self.level_ref, self.context_menu) self.scroll_panel = QScrollArea() self.scroll_panel.setWidgetResizable(True) self.scroll_panel.setWidget(self.level_view) self.setCentralWidget(self.scroll_panel) self.spinner_panel = SpinnerPanel(self, self.level_ref) self.spinner_panel.zoom_in_triggered.connect(self.level_view.zoom_in) self.spinner_panel.zoom_out_triggered.connect(self.level_view.zoom_out) self.spinner_panel.object_change.connect(self.on_spin) self.object_list = ObjectList(self, self.level_ref, self.context_menu) self.object_dropdown = ObjectDropdown(self) self.object_dropdown.object_selected.connect( self._on_placeable_object_selected) self.level_size_bar = LevelSizeBar(self, self.level_ref) self.enemy_size_bar = EnemySizeBar(self, self.level_ref) self.jump_list = JumpList(self, self.level_ref) self.jump_list.add_jump.connect(self.on_jump_added) self.jump_list.edit_jump.connect(self.on_jump_edit) self.jump_list.remove_jump.connect(self.on_jump_removed) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.object_list) splitter.setStretchFactor(0, 1) splitter.addWidget(self.jump_list) splitter.setChildrenCollapsible(False) level_toolbar = QToolBar("Level Info Toolbar", self) level_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) level_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) level_toolbar.setOrientation(Qt.Horizontal) level_toolbar.setFloatable(False) level_toolbar.addWidget(self.spinner_panel) level_toolbar.addWidget(self.object_dropdown) level_toolbar.addWidget(self.level_size_bar) level_toolbar.addWidget(self.enemy_size_bar) level_toolbar.addWidget(splitter) level_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.RightToolBarArea, level_toolbar) self.object_toolbar = ObjectToolBar(self) self.object_toolbar.object_selected.connect( self._on_placeable_object_selected) object_toolbar = QToolBar("Object Toolbar", self) object_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) object_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) object_toolbar.setFloatable(False) object_toolbar.addWidget(self.object_toolbar) object_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.LeftToolBarArea, object_toolbar) menu_toolbar = QToolBar("Menu Toolbar", self) menu_toolbar.setOrientation(Qt.Horizontal) menu_toolbar.setIconSize(QSize(20, 20)) menu_toolbar.addAction( icon("settings.svg"), "Editor Settings").triggered.connect(show_settings) menu_toolbar.addSeparator() menu_toolbar.addAction(icon("folder.svg"), "Open ROM").triggered.connect(self.on_open_rom) menu_toolbar.addAction( icon("save.svg"), "Save Level").triggered.connect(self.on_save_rom) menu_toolbar.addSeparator() self.undo_action = menu_toolbar.addAction(icon("rotate-ccw.svg"), "Undo Action") self.undo_action.triggered.connect(self.level_ref.undo) self.undo_action.setEnabled(False) self.redo_action = menu_toolbar.addAction(icon("rotate-cw.svg"), "Redo Action") self.redo_action.triggered.connect(self.level_ref.redo) self.redo_action.setEnabled(False) menu_toolbar.addSeparator() menu_toolbar.addAction(icon("play-circle.svg"), "Play Level").triggered.connect(self.on_play) menu_toolbar.addSeparator() menu_toolbar.addAction(icon("zoom-out.svg"), "Zoom Out").triggered.connect( self.level_view.zoom_out) menu_toolbar.addAction(icon("zoom-in.svg"), "Zoom In").triggered.connect( self.level_view.zoom_in) menu_toolbar.addSeparator() menu_toolbar.addAction(icon("tool.svg"), "Edit Level Header").triggered.connect( self.on_header_editor) self.jump_destination_action = menu_toolbar.addAction( icon("arrow-right-circle.svg"), "Go to Jump Destination") self.jump_destination_action.triggered.connect( self._go_to_jump_destination) menu_toolbar.addSeparator() # menu_toolbar.addAction(icon("help-circle.svg"), "What's this?") self.addToolBar(Qt.TopToolBarArea, menu_toolbar) self.status_bar = ObjectStatusBar(self, self.level_ref) self.setStatusBar(self.status_bar) self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self, self.remove_selected_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_X), self, self._cut_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_C), self, self._copy_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_V), self, self._paste_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Z), self, self.level_ref.undo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Y), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Z), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Plus), self, self.level_view.zoom_in) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Minus), self, self.level_view.zoom_out) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_A), self, self.level_view.select_all) if not self.on_open_rom(path_to_rom): self.deleteLater() self.showMaximized()
webView = MyWebView(tabWdgt) webView.load(QUrl("http://www.ifeng.com")) tabWdgt.createTabItem(webView, '首页') # 嵌入surpac界面 pname = 'surpac2' cmd = "C:/Program Files (x86)/GEOVIA/Surpac/69/nt_i386/bin/surpac2.exe" spTitle = 'GEOVIA Surpac' pids = MySurpac.getPidsFromPName(pname) MySurpac.killProcess(pids) pid = MySurpac.startProcess(cmd) hwnd = MySurpac.getTheMainWindow(pid, spTitle) ports = MySurpac.getPortsFromPid(pid) surpacWdgt = MySurpac.convertWndToWidget(hwnd) # 嵌入Tree界面 treeWdgt = MyTreeWidget(ports[0]) # 分割窗口 splitter = QSplitter() splitter.setOrientation(Qt.Horizontal) splitter.addWidget(surpacWdgt) splitter.addWidget(treeWdgt) tabWdgt.createTabItem(splitter, '三维设计') mainWindow.setCentralWidget(tabWdgt) mainWindow.showMaximized() # 退出应用 sys.exit(app.exec_())