def actPaste(self): c = [] text = QtGui.QGuiApplication.clipboard().text() bad = False try: l = json.loads(text) if not isinstance(l, list): c = filterBS(parseBS(text)) else: for s in l: d = MapDataElement.fromdict(s) if d and d.src and d.src.svgId: c.append(d) else: bad = True except JSONDecodeError: c = filterBS(parseBS(text)) if bad or len(c) == 0: QMessageBox( QMessageBox.Icon.Warning, TR("Paste"), c and TR("__paste_filter_invalid_blocks__").format(len(c)) or TR("__paste_no_valid_blocks__")).exec_() len(c) and self.ghostHold(c)
def mousePress(self, a0: QMouseEvent) -> bool: d, _ = self.parent.findCellUnder(a0) if not d: return False shift = a0.modifiers() & QtCore.Qt.KeyboardModifier.ShiftModifier ctrl = a0.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier if a0.buttons() & QtCore.Qt.MouseButton.LeftButton: if a0.x() < Ruler.Width: # quick row selection self.parent.selector.addRowCol(self.parent.data, shift or ctrl, ctrl, y=d.y) return True if a0.y() < Ruler.Width: # quick column selection self.parent.selector.addRowCol(self.parent.data, shift or ctrl, ctrl, x=d.x) return True if a0.buttons() & QtCore.Qt.MouseButton.RightButton: if not (a0.x() < Ruler.Width or a0.y() < Ruler.Width): return False m = QMenu(self.parent) if a0.x() < Ruler.Width: # add hline if d.y in self.hlines: a = m.addAction(TR('Remove Line')) a.triggered.connect(lambda v: self.hlines.remove(d.y)) else: a = m.addAction(TR('Add Line')) a.triggered.connect(lambda v: self.hlines.add(d.y)) else: if d.x in self.vlines: a = m.addAction(TR('Remove Line')) a.triggered.connect(lambda v: self.vlines.remove(d.x)) else: a = m.addAction(TR('Add Line')) a.triggered.connect(lambda v: self.vlines.add(d.x)) m.popup(self.parent.mapToGlobal(a0.pos())) return True return False
def __init__(self, parent: typing.Optional[QWidget], logfile) -> None: super().__init__(parent=parent) self.logfile = logfile self.setWindowTitle(APP_NAME) box = QVBoxLayout(self) self.log = QListWidget(self) self.log.addItems(LOGS) self.log.setFont( QtGui.QFontDatabase.systemFont( QtGui.QFontDatabase.SystemFont.FixedFont)) self.log.doubleClicked.connect(self.open) box.addWidget(self.log) w = QWidget(self) hbox = QHBoxLayout(w) btn = QPushButton(TR('OK'), self) btn.clicked.connect(lambda: self.close()) btn.setFixedSize(btn.sizeHint()) hbox.addWidget(btn) btn = QPushButton( TR('Clear Logs') + ' ({:.2f}K)'.format(os.stat('logs.txt').st_size / 1024), self) btn.clicked.connect(lambda: self.clearLog()) btn.setFixedSize(btn.sizeHint()) hbox.addWidget(btn) btn = QPushButton(TR('Hide Debugs'), self) btn.clicked.connect(lambda: self.nonDebugLog()) btn.setFixedSize(btn.sizeHint()) hbox.addWidget(btn) box.addWidget(w) # self.setFixedSize(self.sizeHint()) self.showMaximized() self.log.scrollToBottom()
def __init__(self, parent: typing.Optional[QWidget]) -> None: super().__init__(parent=parent) box = self.box box.addWidget(QLabel('{} (v{})'.format(APP_NAME, APP_VERSION))) box.addWidget(QLabel(TR('__about__'))) def _link(url, text=None): l = QLabel(self) l.setText("<a href=\"{}\">{}<a>".format(url, text or url)) l.setTextFormat(QtCore.Qt.TextFormat.RichText) l.setTextInteractionFlags( QtCore.Qt.TextInteractionFlag.TextBrowserInteraction) l.setOpenExternalLinks(True) return l box.addWidget(_link("https://commons.wikimedia.org/wiki/BSicon/Guide")) box.addWidget( _link("https://github.com/coyove/RouteMaster", "{} on Github".format(APP_NAME))) box.addWidget(_link("https://github.com/wisaly/qtbase_zh")) box.addWidget(_link("mailto:[email protected]", TR("Send Feedbacks"))) box.addWidget( _link("https://github.com/coyove/RouteMaster/issues", TR("File Issues on Github"))) btn = QPushButton(TR('OK'), self) btn.clicked.connect(lambda: self.close()) btn.setFixedSize(btn.sizeHint()) box.addWidget(btn) # alignment=QtCore.Qt.AlignmentFlag.AlignRight) self.setFixedSize(self.sizeHint())
def actSelectByText(self): x, ok = QInputDialog.getText(self, TR('Select by Text'), TR('Text:')) if not ok or not x: return self.selector.clear() for k in self.data.data: d = self.data.data[k] if d.text.lower().count(x.lower()) > 0: self.selector.addSelection(d, propertyPanel=False) self.findMainWin().propertyPanel.update()
def _askSave(self, thenQuit=False): if self.mapview.data.historyCap: ans = QMessageBox.question(self, TR("Save"), TR("Save current file?"), buttons=QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel) if ans == QMessageBox.StandardButton.Cancel: return False if ans == QMessageBox.StandardButton.Yes: self.doSave(True) thenQuit and QApplication.quit() return True
def offsetChanged(self, t, v): if t == 'x' or t == 'y': self._foreach(lambda x: x.set(t == 'x' and 'textX' or 'textY', v)) else: # xo self.startXOffsetLabel.setText( TR('Overlay: Start X Percentage') + ' ' + str(int(v * 100)) + "%") self._foreach(lambda x: x.set("startXOffset", v))
def doExportPngSvg(self, v=True, png=True): d = QFileDialog(self) fn, _ = d.getSaveFileName( filter='PNG File (*.png)' if png else 'SVG File (*.svg)') if not fn: return try: if png: exportMapDataPng(self, fn, self.mapview.data) else: exportMapDataSvg(self, fn, self.mapview.data) QMessageBox.information(self, TR('Export'), TR('__export_success__').format(fn)) except Exception as e: QMessageBox(QMessageBox.Icon.Warning, TR('Export'), TR('__export_fail__').format(fn)).exec_() QtCore.qDebug("{}".format(e).encode("utf-8"))
def load(self, fn: str): d: MapData = self.mapview.data crashfn = fn.removesuffix(".bsm") + ".crash.bsm" delcrash = False if os.path.exists(crashfn): ans = QMessageBox.question(self, TR('Open'), TR('__open_crash__').format(crashfn)) if ans == QMessageBox.StandardButton.Yes: if fn == "": fn = crashfn else: filename = os.path.basename(fn) dir = os.path.dirname(fn) shutil.copy2(fn, os.path.join(dir, "." + filename + ".old")) shutil.copy2(crashfn, fn) delcrash = True else: os.remove(crashfn) if not os.path.exists(fn): return with open(fn, 'rb') as f: try: fd = json.load(f) except JSONDecodeError as e: QMessageBox(QMessageBox.Icon.Critical, TR('Open'), TR('__open_fail__').format(fn)).exec_() QtCore.qDebug("{}".format(e).encode("utf-8")) return d.clearHistory() d.data = {} for c in fd['data']: el = MapDataElement.fromdict(c) if el: d.data[(el.x, el.y)] = el # self.mapview.pan(0, 0) self.mapview.center() self._updateCurrentFile(fn) self.fileMeta["author"] = fd.get("author") self.fileMeta["desc"] = fd.get("desc") self.mapview.ruler.fromdict(fd.get("rulers")) if delcrash: os.remove(crashfn)
def doSaveAs(self, v): d = QFileDialog(self) fn, _ = d.getSaveFileName(filter='BSM Files (*.bsm)') if not fn: return try: self.save(fn) except Exception as e: QMessageBox(QMessageBox.Icon.Warning, TR('Save'), 'Failed to save {}: {}'.format(fn, e)).exec_()
def doOpen(self, v): if not self._askSave(): return d = QFileDialog(self) fn, _ = d.getOpenFileName(filter='BSM Files (*.bsm)') if not fn: return try: self.load(fn) except Exception as e: QMessageBox(QMessageBox.Icon.Warning, TR('Open'), 'Failed to open {}: {}'.format(fn, e)).exec_()
def __init__(self): super().__init__(flags=QtCore.Qt.WindowType.WindowCloseButtonHint | QtCore.Qt.WindowType.WindowStaysOnTopHint) self.setWindowTitle(APP_NAME) self.setMaximumWidth(WIN_WIDTH * 2) self.show() w = QWidget(self) box = QVBoxLayout() w.setLayout(box) self.bar = QProgressBar(w) self.bar.show() self.bar.setRange(0, 0) box.addWidget(QLabel(TR("__download_icons__")), 1) self.progress = QLabel('0') box.addWidget(self.progress) box.addWidget(self.bar, 1) self.setCentralWidget(w) self.setVisible(False) self.tasks = set() Loader.Single = self
def __init__(self, parent: typing.Optional[QWidget], meta: dict) -> None: super().__init__(parent=parent) self.setWindowTitle(APP_NAME) self.meta = meta box = QVBoxLayout(self) box.addWidget(QLabel(TR('Author'))) self.author = QLineEdit(self) self.author.setText(meta.get("author")) box.addWidget(self.author) self.desc = QTextEdit(self) self.desc.setText(meta.get("desc")) box.addWidget(QLabel(TR('Description'))) box.addWidget(self.desc, 5) data: MapData = parent.mapview.data total, dedup, missings, polyfills = 0, set(), set(), set() for v in data.data.values(): if not v.src.svgId.lower() in SvgSource.Search.files: missings.add(v.src.svgId) if ispngployfill(v.src.svgId): polyfills.add(v.src.svgId) dedup.add(v.src.svgId) total = total + 1 for s in v.cascades: if not s.svgId.lower() in SvgSource.Search.files: missings.add(s.svgId) if ispngployfill(s.svgId): polyfills.add(s.svgId) dedup.add(s.svgId) total = total + 1 tabs = QTabWidget(self) def rectStr(r: QtCore.QRect): return "({}, {})-({}, {})".format(r.x(), r.y(), r.width() + r.x(), r.height() + r.y()) overview = QListWidget(self) overview.addItem(TR('Total Blocks') + ": " + str(total)) overview.addItem(TR('Bounding') + ": " + rectStr(data.bbox())) overview.addItem( TR('Text Bounding') + ": " + rectStr(data.bbox(includeText=True))) tabs.addTab(overview, TR('Overview')) self.all = QTextEdit(self) self.all.setText('\n'.join(dedup)) self.all.setReadOnly(True) tabs.addTab(self.all, TR('All Blocks')) self.missing = QTextEdit(self) self.missing.setText('\n'.join(missings)) self.missing.setReadOnly(True) tabs.addTab(self.missing, TR('Missings')) self.polyfills = QTextEdit(self) self.polyfills.setText('\n'.join(polyfills)) self.missing.setReadOnly(True) tabs.addTab(self.polyfills, TR('Polyfills')) box.addWidget(tabs, 5) self.ok = QPushButton(TR("OK"), self) self.ok.clicked.connect(self.onOK) self.ok.setFixedSize(self.ok.sizeHint()) box.addWidget(self.ok) self.setFixedWidth(WIN_WIDTH)
def __init__(self, parent: typing.Optional['QWidget']): super().__init__(parent=parent) self.scrollView = QScrollArea(self) self.scrollView.setWidgetResizable(True) self.scrollView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.textAttr = QWidget(self) self.textAttr.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) self.textAttrBox = QVBoxLayout() self.textAttr.setLayout(self.textAttrBox) self.cascades = SvgBar(self) self.cascades.setVisible(False) self.cascades.onDelete = self.deleteCascade self.cascades.onDrag = self.resortCascade self.cascades.onCopy = self.onCopy self.textAttrBox.addWidget(self.cascades) self.svgId = QLabel('N/A', self) self.textAttrBox.addWidget(self._title(TR('Type'))) self.textAttrBox.addWidget(self.svgId) self.startXOffsetLabel = self._title(TR('Overlay: Start X Percentage')) self.textAttrBox.addWidget(self.startXOffsetLabel) self.startXOffset = QSlider(self) self.startXOffset.setOrientation(QtCore.Qt.Orientation.Horizontal) self.startXOffset.setMinimum(0) self.startXOffset.setMaximum(3) self.startXOffset.valueChanged.connect( lambda e: self.offsetChanged('xo', self.startXOffset.value() * 0.25)) self.textAttrBox.addWidget(self.startXOffset) self.textAttrBox.addWidget(self._title(TR('Text'))) self.text = QTextEdit(self) self.text.textChanged.connect(self.textChanged) self.text.installEventFilter(self) self.textAttrBox.addWidget(self.text) self.textFont = QComboBox(self) fontFamilies = QFontDatabase() for s in fontFamilies.families(): self.textFont.addItem(s) self.textFont.currentIndexChanged.connect(self.fontChanged) self.textFont.setEditable(True) self.textSize = QComboBox(self) for i in range(8, 150, 1): if i <= 32: self.textSize.addItem(str(i)) elif i <= 80 and i % 2 == 0: self.textSize.addItem(str(i)) elif i % 10 == 0: self.textSize.addItem(str(i)) self.textSize.currentIndexChanged.connect(self.sizeChanged) self.textSize.setEditable(True) self._addBiBoxInTextAttrBox(self._title(TR("Font Family")), self.textFont, self._title(TR("Font Size")), self.textSize) self.textAlign = QComboBox(self) self.textPlace = QComboBox(self) for c in [self.textAlign, self.textPlace]: c.addItem(TR('Center'), 'c') c.addItem(TR('Top'), 't') c.addItem(TR('Bottom'), 'b') c.addItem(TR('Left'), 'l') c.addItem(TR('Right'), 'r') self.textAlign.currentIndexChanged.connect(self.alignChanged) self.textPlace.currentIndexChanged.connect(self.placeChanged) self._addBiBoxInTextAttrBox(self._title(TR("Alignment")), self.textAlign, self._title(TR("Placement")), self.textPlace) self.textX = QSpinBox(self) self.textY = QSpinBox(self) for c in [self.textX, self.textY]: c.setValue(0) c.setMinimum(-1e5) c.setMaximum(1e5) self.textX.valueChanged.connect(lambda e: self.offsetChanged('x', e)) self.textY.valueChanged.connect(lambda e: self.offsetChanged('y', e)) self._addBiBoxInTextAttrBox(self._title(TR("Offset X")), self.textX, self._title(TR("Offset Y")), self.textY) # self.setLayout(self.textAttrBox) self.scrollView.setWidget(self.textAttr) box = QVBoxLayout() box.addWidget(self.scrollView) box.setContentsMargins(0, 0, 0, 0) self.setLayout(box) self.show() self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) self.updating = False
def actDownloadBlocks(self, v): ss, _ = QInputDialog.getText(self, APP_NAME, TR('Block Name:')) for s in ss.split(): s = s.strip() s and self.loader.addTask(s)
def _updateCurrentFile(self, fn): self.currentFile = fn self.setWindowTitle(APP_NAME + " - " + (self.currentFile or TR('[Untitled]')))
def __init__(self): super().__init__() self.searcher = SvgSearch(BLOCK_DIR) SvgSource.Search = self.searcher SvgSource.Parent = self self.loader = Loader() # self.loader.addTask("MFADEf") self.setWindowTitle(APP_NAME) self.setGeometry(0, 0, 800, 500) bar = QStatusBar(self) self.barPosition = QLabel(bar) bar.addWidget(self.barPosition, 1) self.barSelection = QLabel(bar) bar.addWidget(self.barSelection, 1) self.barZoom = QLabel(bar) bar.addWidget(self.barZoom, 1) self.barCursor = QLabel(bar) bar.addWidget(self.barCursor, 1) self.barHistory = QLabel(bar) bar.addWidget(self.barHistory, 1) self.setStatusBar(bar) main = QWidget(self) vbox = QVBoxLayout() vbox.setContentsMargins(0, 0, 0, 0) self.propertyPanel = Property(main) self.searchBox = QLineEdit(self) self.searchBox.setPlaceholderText(TR('Search blocks')) self.searchBox.textChanged.connect(self.updateSearches) # self.searchBox.returnPressed.connect(self.searchBlocks) self.searchResults = SvgBar(self) vbox.addWidget( Property._genVBox(self, self.searchBox, self.searchResults, 8)) main.setLayout(vbox) self.setCentralWidget(main) splitter = QSplitter(QtCore.Qt.Orientation.Horizontal) splitter.addWidget(self.propertyPanel) self.mapview = Map(main) self._updateCurrentFile('') splitter.addWidget(self.mapview) splitter.setStretchFactor(1, 2) splitter.setSizes([100, 100]) vbox.addWidget(splitter, 255) self.topMenus = {} self._addMenu(TR('&File'), TR('&New'), 'Ctrl+N', self.doNew) self._addMenu(TR('&File'), TR('&New Window'), 'Ctrl+Shift+N', lambda x: startNew()) self._addMenu(TR('&File'), '-') self._addMenu(TR('&File'), TR('&Open'), 'Ctrl+O', self.doOpen) self._addMenu(TR('&File'), '-') self._addMenu(TR('&File'), TR('&Save'), 'Ctrl+S', self.doSave) self._addMenu(TR('&File'), TR('&Save As...'), 'Ctrl+Shift+S', self.doSaveAs) self._addMenu(TR('&File'), '-') self._addMenu(TR('&File'), TR('&Export PNG...'), '', lambda x: self.doExportPngSvg(png=True)) self._addMenu(TR('&File'), TR('E&xport SVG...'), '', lambda x: self.doExportPngSvg(png=False)) self._addMenu(TR('&File'), '-') self._addMenu(TR('&File'), TR('&File Properties...'), 'F3', lambda x: FileProperty(self, self.fileMeta).exec_()) self._addMenu(TR('&File'), '-') self._addMenu( TR('&File'), TR('&Open Data Folder'), '', lambda x: QtGui.QDesktopServices.openUrl( QtCore.QUrl.fromLocalFile(BLOCK_DIR))) self._addMenu(TR('&File'), TR('&Download SVG Blocks'), '', self.actDownloadBlocks) self._addMenu(TR('&File'), '-') self._addMenu(TR('&File'), TR('&Quit'), '', lambda x: self._askSave(thenQuit=True)) self._addMenu(TR('&Edit'), TR('&Undo'), '', lambda x: self.mapview.actUndoRedo()) self._addMenu(TR('&Edit'), TR('&Redo'), '', lambda x: self.mapview.actUndoRedo(redo=True)) self._addMenu(TR('&Edit'), '-') self._addMenu(TR('&Edit'), TR('&Cut'), '', lambda x: self.mapview.actCut()) self._addMenu(TR('&Edit'), TR('&Copy'), '', lambda x: self.mapview.actCopy()) self._addMenu(TR('&Edit'), TR('&Paste'), '', lambda x: self.mapview.actPaste()) self._addMenu(TR('&Edit'), '-') self._addMenu(TR('&Edit'), TR('&Clear History'), 'Ctrl+K', lambda x: self.mapview.data.clearHistory()) self._addMenu(TR('&Edit'), '-') self._addMenu(TR('&Edit'), TR('&Select by Text'), 'Ctrl+F', lambda x: self.mapview.actSelectByText()) self._addMenu(TR('&View'), TR('&Center'), '', lambda x: self.mapview.center()) self._addMenu(TR('&View'), TR('Center &Selected'), 'Ctrl+Shift+H', lambda x: self.mapview.center(selected=True)) self._addMenu(TR('&View'), TR('100% &Zoom'), '', lambda x: self.mapview.center(resetzoom=True)) self._addMenu(TR('&View'), '-') self.showRuler = self._addMenu(TR('&View'), TR('&Ruler'), '', self.actShowRuler) self.showRuler.setCheckable(True) self.mapview.showRuler = not FLAGS["hide_ruler"] self.showRuler.setChecked(self.mapview.showRuler) self._addMenu(TR('&View'), '-') self._addMenu(TR('&View'), TR('&Logs'), '', lambda x: Logger(self, logfile).exec_()) self._addMenu(TR('&Help'), TR('&About'), '', lambda x: About(self).exec_()).setMenuRole( QAction.MenuRole.AboutRole) self.propertyPanel.update() self.showMaximized() self.fileMeta = {} import socket self.resetFileMeta = lambda: self.__setattr__( "fileMeta", { "author": socket.gethostname(), "desc": APP_NAME }) self.resetFileMeta() self.load(args.file or '') if args.convert: self.setVisible(False) if args.convert.endswith('.svg'): exportMapDataSvg(self, args.convert, self.mapview.data) else: self.mapview.scale = float(args.png_scale) exportMapDataPng(self, args.convert, self.mapview.data) print('Successfully export to {}'.format(args.convert)) sys.exit(0)
win = Window() def excepthook(exc_type, exc_value, exc_tb): import traceback msg = "".join(traceback.format_exception(exc_type, exc_value, exc_tb)) QtCore.qDebug(("exception:\n" + msg).encode('utf-8')) print(msg) global win if win: w = win win = None # prevent dead loop try: w.save( w.currentFile.removesuffix(".bsm") + '.crash.bsm' if w.currentFile else '.crash.bsm') except Exception as e: QtCore.qDebug( ("double exception in hook: {}".format(e)).encode('utf-8')) app.quit() sys.excepthook = excepthook if not os.path.exists(BLOCK_DIR) or len(SvgSource.Search.files) == 0: QMessageBox(QMessageBox.Icon.Warning, APP_NAME, TR("__block_dir__").format(BLOCK_DIR)).exec_() app.exec_()