def get_palette_image(self): """ Returns a 8x2 palette, with 16x16 square characters """ pal_image = AnsiImage(self.graphics) pal_image.clear_image(16, 2) pal_idx = 0 for y in range(0, 2): for x in range(0, 16, 2): fore_col = 15 if pal_idx >= 8: fore_col = 0 fore_char = ' ' if pal_idx == self.fore(): fore_char = 'f' back_char = ' ' if pal_idx == self.back(): back_char = 'b' pal_image.set_cell(char=ord(fore_char), fore=fore_col, back=pal_idx, x=x, y=y) pal_image.set_cell(char=ord(back_char), fore=fore_col, back=pal_idx, x=x + 1, y=y) pal_idx += 1 return pal_image.to_bitmap()
def __init__(self, ansi_graphics, palette_file): """ Just stores a graphics object to work with. """ self.graphics = ansi_graphics self.cur_fore = 15 # White self.cur_back = 0 # Black self.char_idx = 0 self.invalid = [0, 8, 9, 10, 13, 26, 27, 255] # These will break acidview self.char_palettes = AnsiImage(self.graphics, min_line_len=12) self.char_palettes.load_ans(palette_file) self.select_char_sequence(5)
def newFile(self): """ Create blank 80x24 document """ self.ansiImage = AnsiImage(self.ansiGraphics) self.ansiImage.clear_image(80, 24) self.undoStack = [] self.redoStack = [] self.currentFileName = None self.previewBuffer = None self.redisplayAnsi() self.updateTitle()
def get_char_image(self, idx=None, from_seq=False): """ Returns an image of a single character """ char = self.get_char(idx, from_seq) char_image = AnsiImage(self.graphics) char_image.clear_image(1, 1) char_image.set_cell(x=0, y=0, fore=char[1], back=char[2], char=char[0]) return char_image.to_bitmap()
def load_ansi(path): if ".." in path or path[0] == '/': raise (ValueError("dangerous.")) wide_mode = False if request.args.get('wide', False) != False: wide_mode = True ansi_image = AnsiImage(ansi_graphics) ansi_image.clear_image(1, 1) if path[0:4] == 'http': ansi_data = get_remote_file(path) ansi_image.parse_ans(ansi_data, wide_mode=wide_mode) else: ansi_image.load_ans(base_path + path, wide_mode=wide_mode) return ansi_image
def get_char_sequence_image(self, index): """ Returns an image of one of the character sequences """ char_image = AnsiImage(self.graphics) char_image.clear_image(12, 1) for i in range(12): char, _, _ = self.char_palettes.get_cell(i, index) char_image.set_cell(x=i, y=0, char=char, back=self.cur_back, fore=self.cur_fore) return char_image.to_bitmap()
class AnsiPalette: """ Manages a palette of coloured ansi characters """ def __init__(self, ansi_graphics, palette_file): """ Just stores a graphics object to work with. """ self.graphics = ansi_graphics self.cur_fore = 15 # White self.cur_back = 0 # Black self.char_idx = 0 self.invalid = [0, 8, 9, 10, 13, 26, 27, 255] # These will break acidview self.char_palettes = AnsiImage(self.graphics, min_line_len=12) self.char_palettes.load_ans(palette_file) self.select_char_sequence(5) def change_graphics(self, graphics): """ Change the ansi graphics used """ self.graphics = graphics def set_fore(self, fore, relative=False): """ Sets the foreground colour """ if relative == False: self.cur_fore = 0 self.cur_fore += fore self.cur_fore = max(0, min(self.cur_fore, 15)) def fore(self): """ Returns the current foreground colour """ return self.cur_fore def set_back(self, back, relative=False): """ Sets the background colour """ if relative == False: self.cur_back = 0 self.cur_back += back self.cur_back = max(0, min(self.cur_back, 15)) def back(self): """ Returns the current background colour """ return self.cur_back def set_char_sequence(self, char_sequence): """ Sets the palette character sequence directly """ self.chars = char_sequence if len(self.chars) > 12: self.chars = self.chars[:12] if self.char_idx >= len(char_sequence): self.char_idx = 0 def select_char_sequence(self, index, relative=False): """ Select a palette from the character palettes """ if relative == False: self.char_sequence_index = 0 self.char_sequence_index += index self.char_sequence_index = max( 0, min(self.char_sequence_count() - 1, self.char_sequence_index)) new_palette = [] for x in range(0, 12): char, _, _ = self.char_palettes.get_cell(x, self.char_sequence_index) new_palette.append(char) self.chars = new_palette def get_char_sequence_image(self, index): """ Returns an image of one of the character sequences """ char_image = AnsiImage(self.graphics) char_image.clear_image(12, 1) for i in range(12): char, _, _ = self.char_palettes.get_cell(i, index) char_image.set_cell(x=i, y=0, char=char, back=self.cur_back, fore=self.cur_fore) return char_image.to_bitmap() def char_sequence_count(self): """ Return how many selectable character sequences exist """ return self.char_palettes.get_size()[1] def set_char_idx(self, char_idx): """ Sets the current character index """ self.char_idx = char_idx def get_char(self, idx=None, from_seq=False): """ Gets the character at the given index with the appropriate colours Active char is returned if none specified """ if idx == None: idx = self.char_idx if from_seq == True: return [self.chars[idx], self.fore(), self.back()] else: if idx in self.invalid: idx = ord(' ') return [idx, self.fore(), self.back()] def get_char_image(self, idx=None, from_seq=False): """ Returns an image of a single character """ char = self.get_char(idx, from_seq) char_image = AnsiImage(self.graphics) char_image.clear_image(1, 1) char_image.set_cell(x=0, y=0, fore=char[1], back=char[2], char=char[0]) return char_image.to_bitmap() def get_palette_image(self): """ Returns a 8x2 palette, with 16x16 square characters """ pal_image = AnsiImage(self.graphics) pal_image.clear_image(16, 2) pal_idx = 0 for y in range(0, 2): for x in range(0, 16, 2): fore_col = 15 if pal_idx >= 8: fore_col = 0 fore_char = ' ' if pal_idx == self.fore(): fore_char = 'f' back_char = ' ' if pal_idx == self.back(): back_char = 'b' pal_image.set_cell(char=ord(fore_char), fore=fore_col, back=pal_idx, x=x, y=y) pal_image.set_cell(char=ord(back_char), fore=fore_col, back=pal_idx, x=x + 1, y=y) pal_idx += 1 return pal_image.to_bitmap() def get_character_image(self, width=32, fore=None, back=None): """ Returns a character selection image """ height = int(256 / width) if height < 256 / width: height += 1 sel_image = AnsiImage(self.graphics) sel_image.clear_image(width, height) if fore == None: fore = self.fore() if back == None: back = self.back() char_idx = 0 for y in range(0, height): for x in range(0, width): if char_idx < 256 and not char_idx in self.invalid: sel_image.set_cell(char=char_idx, fore=fore, back=back, x=x, y=y) else: sel_image.set_cell(char=ord(' '), fore=fore, back=back, x=x, y=y) char_idx += 1 sel_image.move_cursor(self.char_idx % width, self.char_idx // width, False) return sel_image.to_bitmap(cursor=True, transparent=True)
def get_character_image(self, width=32, fore=None, back=None): """ Returns a character selection image """ height = int(256 / width) if height < 256 / width: height += 1 sel_image = AnsiImage(self.graphics) sel_image.clear_image(width, height) if fore == None: fore = self.fore() if back == None: back = self.back() char_idx = 0 for y in range(0, height): for x in range(0, width): if char_idx < 256 and not char_idx in self.invalid: sel_image.set_cell(char=char_idx, fore=fore, back=back, x=x, y=y) else: sel_image.set_cell(char=ord(' '), fore=fore, back=back, x=x, y=y) char_idx += 1 sel_image.move_cursor(self.char_idx % width, self.char_idx // width, False) return sel_image.to_bitmap(cursor=True, transparent=True)
class MainWindow(QtWidgets.QMainWindow): """ Halcys Ansi Editor """ def __init__(self): super(MainWindow, self).__init__() self.refImage = QtGui.QPixmap() # Set up window properties self.title = "HANSE" self.setWindowTitle(self.title) self.resize(1100, 600) # Set up other stuff self.currentFileName = None self.previewBuffer = None # Load font self.fonts = json.load(open(os.path.join('config', 'fonts.json'), 'r')) self.activeFont = 0 self.ansiGraphics = AnsiGraphics( os.path.join('config', self.fonts[self.activeFont]['file']), self.fonts[self.activeFont]['width'], self.fonts[self.activeFont]['height'], ) # Set up palette self.palette = AnsiPalette(self.ansiGraphics, os.path.join("config", "palettes.ans")) # Create and link up GUI components self.createMenuBar() self.createComponents() self.createLayout() self.connectEvents() # Set up image self.newFile() # Make sure everything looks proper self.redisplayPalette() # Set up tools self.tools = [] self.tools.append(ToolSelection(self, self.imageView, self.ansiImage)) self.tools[0].activate() # The selection tool is Special because you can use it using the keyboard self.selectionTool = self.tools[0] def createMenuBar(self): menuFile = self.menuBar().addMenu("File") self.actionNew = QtWidgets.QAction("New", self) self.actionOpen = QtWidgets.QAction("Open", self) self.actionSave = QtWidgets.QAction("Save", self) self.actionSaveAs = QtWidgets.QAction("Save As", self) self.actionExport = QtWidgets.QAction("Export as PNG", self) self.actionExit = QtWidgets.QAction("Exit", self) menuEdit = self.menuBar().addMenu("Edit") self.actionSize = QtWidgets.QAction("Set size", self) self.actionUndo = QtWidgets.QAction("Undo", self) self.actionRedo = QtWidgets.QAction("Redo", self) self.actionCopy = QtWidgets.QAction("Copy", self) self.actionCut = QtWidgets.QAction("Cut", self) self.actionPaste = QtWidgets.QAction("Paste", self) self.toggleSkipSpace = QtWidgets.QAction("Skip space", self) self.toggleSkipSpace.setCheckable(True) self.toggleSkipSpace.setChecked(True) self.toggleWriteChar = QtWidgets.QAction("Write character", self) self.toggleWriteChar.setCheckable(True) self.toggleWriteChar.setChecked(True) self.toggleWriteFore = QtWidgets.QAction("Write foreground", self) self.toggleWriteFore.setCheckable(True) self.toggleWriteFore.setChecked(True) self.toggleWriteBack = QtWidgets.QAction("Write background", self) self.toggleWriteBack.setCheckable(True) self.toggleWriteBack.setChecked(True) menuView = self.menuBar().addMenu("View") self.toggleTransparent = QtWidgets.QAction("Transparent", self) self.toggleTransparent.setCheckable(True) self.toggleTransparent.setChecked(False) self.toggleHideCursor = QtWidgets.QAction("Hide cursor and selection", self) self.toggleHideCursor.setCheckable(True) self.toggleHideCursor.setChecked(False) self.actionReferenceImage = QtWidgets.QAction("Set reference image", self) self.toggleReferenceImage = QtWidgets.QAction("Show reference image", self) self.toggleReferenceImage.setCheckable(True) self.toggleReferenceImage.setChecked(True) self.toggleReferenceImageTop = QtWidgets.QAction( "Reference image on top", self) self.toggleReferenceImageTop.setCheckable(True) self.toggleReferenceImageTop.setChecked(True) self.toggleSmallPreview = QtWidgets.QAction("Small preview", self) self.toggleSmallPreview.setCheckable(True) self.toggleSmallPreview.setChecked(False) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addAction(self.actionExport) menuFile.addSeparator() menuFile.addAction(self.actionExit) menuEdit.addAction(self.actionSize) menuEdit.addSeparator() menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionPaste) menuEdit.addAction(self.toggleSkipSpace) menuEdit.addSeparator() menuEdit.addAction(self.toggleWriteChar) menuEdit.addAction(self.toggleWriteFore) menuEdit.addAction(self.toggleWriteBack) menuView.addAction(self.toggleTransparent) menuView.addAction(self.toggleHideCursor) menuView.addSeparator() menuView.addAction(self.actionReferenceImage) menuView.addAction(self.toggleReferenceImage) menuView.addAction(self.toggleReferenceImageTop) menuOpacity = menuView.addMenu("Reference opacity") menuView.addSeparator() menuView.addAction(self.toggleSmallPreview) menuView.addSeparator() menuFont = menuView.addMenu("Font") opacityActionGroup = QtWidgets.QActionGroup(self) opacityActionGroup.setExclusive(True) self.toggleOpacity = [] for i in range(1, 10): opacityToggle = QtWidgets.QAction("0." + str(i), self) opacityToggle.setCheckable(True) if i == 3: opacityToggle.setChecked(True) else: opacityToggle.setChecked(False) opacityActionGroup.addAction(opacityToggle) menuOpacity.addAction(opacityToggle) self.toggleOpacity.append(opacityToggle) fontActionGroup = QtWidgets.QActionGroup(self) fontActionGroup.setExclusive(True) self.toggleFont = [] for i in range(len(self.fonts)): fontToggle = QtWidgets.QAction(self.fonts[i]["name"], self) fontToggle.setCheckable(True) if i == 0: fontToggle.setChecked(True) else: fontToggle.setChecked(False) fontActionGroup.addAction(fontToggle) menuFont.addAction(fontToggle) self.toggleFont.append(fontToggle) self.menuCharacterSelect = QtWidgets.QMenu() self.actionsCharacterSelect = [] self.hoveredCharSel = None characterSelectActionGroup = QtWidgets.QActionGroup(self) characterSelectActionGroup.setExclusive(True) for i in range(self.palette.char_sequence_count()): actionCharacterSelect = QtWidgets.QWidgetAction( self.menuCharacterSelect) actionCharacterSelect.setCheckable(True) if i == 5: actionCharacterSelect.setChecked(True) else: actionCharacterSelect.setChecked(False) charImage = QtGui.QPixmap.fromImage( ImageQt.ImageQt(self.palette.get_char_sequence_image(i))) charLabel = QtWidgets.QLabel() charLabel.setPixmap(charImage) charLabel.setMinimumSize(charLabel.pixmap().width() + 32, charLabel.pixmap().height() + 4) charLabel.paintEvent = ( lambda event, x=charLabel, y=actionCharacterSelect: self. paintCharSelMenu(event, x, y)) charLabel.setMouseTracking(True) charLabel.mouseMoveEvent = ( lambda event, x=charLabel: self.menuMouseMoved(event, x)) actionCharacterSelect.setDefaultWidget(charLabel) self.menuCharacterSelect.addAction(actionCharacterSelect) characterSelectActionGroup.addAction(actionCharacterSelect) self.actionsCharacterSelect.append(actionCharacterSelect) def menuMouseMoved(self, event, label): """ Repaint all the labels when the mouse moves not super efficient but w/e """ self.hoveredCharSel = label for action in self.actionsCharacterSelect: action.defaultWidget().repaint() def paintCharSelMenu(self, event, label, action): """ Super special "menu item with pixmap" repainter """ styleOptions = QtWidgets.QStyleOptionMenuItem() self.menuCharacterSelect.initStyleOption(styleOptions, action) if self.hoveredCharSel == label: styleOptions.state = styleOptions.state | QtWidgets.QStyle.State_Selected else: styleOptions.state = styleOptions.state & ~QtWidgets.QStyle.State_Selected styleOptions.rect = label.frameRect() painter = QtWidgets.QStylePainter(label) painter.drawControl(QtWidgets.QStyle.CE_MenuItem, styleOptions) painter.drawPixmap(30, 2, label.pixmap()) painter.end() def createComponents(self): """ Create window components """ self.previewScroll = QtWidgets.QScrollArea() self.previewScroll.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.previewScroll.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) self.imagePreview = QtWidgets.QLabel() self.imagePreview.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.imagePreview.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) self.imageScroll = QtWidgets.QScrollArea() self.imageScroll.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.imageScroll.setAlignment(QtCore.Qt.AlignCenter) self.imageView = QtWidgets.QLabel() self.imageView.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.imageView.setAlignment(QtCore.Qt.AlignCenter) self.palSel = QtWidgets.QLabel() self.palSel.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.palSel.setAlignment(QtCore.Qt.AlignCenter) self.charSel = QtWidgets.QLabel() self.charSel.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.charSel.setAlignment(QtCore.Qt.AlignCenter) self.charPalLabels = [] self.charPalPixmaps = [] for i in range(0, 12): charPalLabel = QtWidgets.QLabel("F" + str(i + 1)) charPalLabel.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.charPalLabels.append(charPalLabel) charPalPixmap = QtWidgets.QLabel() charPalPixmap.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.charPalPixmaps.append(charPalPixmap) self.buttonCharSelect = QtWidgets.QPushButton("▶") self.buttonCharSelect.setMaximumSize(30, 99999) self.buttonCharSelectNext = QtWidgets.QPushButton("▼") self.buttonCharSelectNext.setMaximumSize(30, 99999) self.buttonCharSelectPrev = QtWidgets.QPushButton("▲") self.buttonCharSelectPrev.setMaximumSize(30, 99999) self.cursorPositionLabel = QtWidgets.QLabel("Cursor: (0, 0)") self.cursorPositionLabel.setAlignment(QtCore.Qt.AlignRight) self.cursorPositionLabel.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) def createLayout(self): """ Layout window components """ mainLayout = QtWidgets.QVBoxLayout() layoutHorizontal = QtWidgets.QHBoxLayout() sidebarLayout = QtWidgets.QVBoxLayout() sidebarLayout.setAlignment(QtCore.Qt.AlignTop) sidebarLayout.addWidget(self.palSel) sidebarLayout.addWidget(self.charSel) self.imageScroll.setWidget(self.imageView) self.previewScroll.setWidget(self.imagePreview) layoutHorizontal.addWidget(self.previewScroll) layoutHorizontal.addWidget(self.imageScroll) layoutHorizontal.addLayout(sidebarLayout) charPalLayout = QtWidgets.QHBoxLayout() charPalLayout.setAlignment(QtCore.Qt.AlignLeft) for i in range(0, 12): charPalLayout.addWidget(self.charPalLabels[i]) charPalLayout.addWidget(self.charPalPixmaps[i]) charPalLayout.addWidget(self.buttonCharSelectPrev) charPalLayout.addWidget(self.buttonCharSelectNext) charPalLayout.addWidget(self.buttonCharSelect) charPalLayout.addWidget(self.cursorPositionLabel) mainLayout.addLayout(layoutHorizontal) mainLayout.addLayout(charPalLayout) centralWidget = QtWidgets.QWidget() centralWidget.setLayout(mainLayout) self.setCentralWidget(centralWidget) def connectEvents(self): """ Connect UI actions to functionality """ self.actionNew.triggered.connect(self.newFile) self.actionOpen.triggered.connect(self.openFile) self.actionOpen.setShortcut(QtGui.QKeySequence.Open) self.actionSave.triggered.connect(self.saveFile) self.actionSave.setShortcut(QtGui.QKeySequence.Save) self.actionSaveAs.triggered.connect(self.saveFileAs) self.actionExport.triggered.connect(self.exportPNG) self.actionExit.triggered.connect(self.exit) self.actionExit.setShortcut(QtGui.QKeySequence.Quit) self.actionSize.triggered.connect(self.resizeCanvas) self.actionUndo.triggered.connect(self.undo) self.actionUndo.setShortcut(QtGui.QKeySequence.Undo) self.actionRedo.triggered.connect(self.redo) self.actionRedo.setShortcuts( [QtGui.QKeySequence.Redo, QtGui.QKeySequence("Ctrl+Y")]) self.actionCopy.triggered.connect(self.clipboardCopy) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionCut.triggered.connect(self.clipboardCut) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionPaste.triggered.connect(self.clipboardPaste) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.toggleWriteChar.triggered.connect(self.changeWriteStatus) self.toggleWriteFore.triggered.connect(self.changeWriteStatus) self.toggleWriteBack.triggered.connect(self.changeWriteStatus) self.toggleTransparent.triggered.connect(self.changeTransparent) self.toggleHideCursor.triggered.connect(self.changeHideCursor) self.buttonCharSelect.clicked.connect(self.showCharSelect) self.buttonCharSelectPrev.clicked.connect( lambda: (self.palette.select_char_sequence(-1, True), self.redisplayPalette())) self.buttonCharSelectNext.clicked.connect( lambda: (self.palette.select_char_sequence(1, True), self.redisplayPalette())) self.imageScroll.keyPressEvent = self.keyPressEvent self.imageScroll.keyReleaseEvent = self.keyReleaseEvent self.charSel.mousePressEvent = self.charSelMousePress self.charSel.mouseDoubleClickEvent = self.charSelDoubleClick self.palSel.mousePressEvent = self.palSelMousePress self.actionReferenceImage.triggered.connect(self.loadReferenceImage) self.toggleReferenceImage.triggered.connect(self.redisplayAnsi) self.toggleReferenceImageTop.triggered.connect(self.redisplayAnsi) for i in range(1, 10): self.toggleOpacity[i - 1].triggered.connect(self.redisplayAnsi) for i in range(len(self.toggleFont)): self.toggleFont[i].triggered.connect(self.changeFont) self.imageView.paintEvent = self.repaintImage self.toggleSmallPreview.triggered.connect(self.redisplayAnsi) for action in self.actionsCharacterSelect: action.triggered.connect(self.setCharSequence) def charSelMousePress(self, event): """ Mouse down on character selection """ new_idx = (event.y() // self.ansiImage.get_char_size()[1] ) * 16 + event.x() // self.ansiImage.get_char_size()[0] self.palette.set_char_idx(new_idx) self.redisplayPalette() def charSelDoubleClick(self, event): """ Double clicked character selection """ char = self.palette.get_char() self.addUndo( self.ansiImage.set_cell(char=char[0], fore=char[1], back=char[2])) self.ansiImage.move_cursor(1, 0) self.redisplayAnsi() def palSelMousePress(self, event): """ Mouse down on palette selection """ new_idx = (event.y() // self.ansiImage.get_char_size()[1] ) * 8 + event.x() // self.ansiImage.get_char_size()[1] if event.button() == QtCore.Qt.LeftButton: self.palette.set_fore(new_idx) if event.button() == QtCore.Qt.RightButton: self.palette.set_back(new_idx) self.redisplayPalette() def updateCursorPositionLabel(self, text=None): """ Update the cursor position label text """ if text == None: x, y = self.ansiImage.get_cursor() if self.selectionTool.selStartX == None and self.selectionTool.selStartY == None: self.cursorPositionLabel.setText("Cursor: ({0}, {1})".format( x, y)) else: self.cursorPositionLabel.setText(text) def repaintImage(self, event): """ Repaint event - does a partial repaint of the image view label and preview label. """ repaintCoords = [ event.rect().x(), event.rect().y(), event.rect().x() + event.rect().width(), event.rect().y() + event.rect().height() ] transparent = self.toggleTransparent.isChecked() cursor = not self.toggleHideCursor.isChecked() paint_x, paint_y, bitmap, size_x, size_y = self.ansiImage.to_bitmap( transparent=transparent, cursor=cursor, area=repaintCoords) qtPixmap = QtGui.QPixmap.fromImage(ImageQt.ImageQt(bitmap)) painter = QtGui.QPainter(self.imageView) refOpacity = 0.0 for i in range(1, 10): if self.toggleOpacity[i - 1].isChecked(): refOpacity = float(i) / 10.0 if self.toggleReferenceImage.isChecked( ) == True and self.toggleReferenceImageTop.isChecked() == False: painter.setOpacity(refOpacity) painter.drawPixmap(0, 0, size_x, size_y, self.refImage) painter.setOpacity(1.0) painter.drawPixmap(paint_x, paint_y, qtPixmap) if self.toggleReferenceImage.isChecked( ) == True and self.toggleReferenceImageTop.isChecked() == True: painter.setOpacity(refOpacity) painter.drawPixmap(0, 0, size_x, size_y, self.refImage) painter.end() previewScale = 4 if self.toggleSmallPreview.isChecked(): previewScale = 8 preview_size_x = size_x // previewScale preview_size_y = size_y // previewScale if self.previewBuffer == None or self.previewBuffer.width( ) != preview_size_x or self.previewBuffer.height() != preview_size_y: self.previewBuffer = QtGui.QPixmap(preview_size_x, preview_size_y) previewBitmap = self.ansiImage.to_bitmap(transparent=transparent, cursor=cursor) previewPixmap = QtGui.QPixmap.fromImage( ImageQt.ImageQt(previewBitmap)) preview_x = 0 preview_y = 0 else: preview_x = paint_x // previewScale preview_y = paint_y // previewScale previewPixmap = qtPixmap previewPixmap = previewPixmap.scaled( previewPixmap.width() // previewScale, previewPixmap.height() // previewScale, transformMode=QtCore.Qt.SmoothTransformation) previewPainter = QtGui.QPainter(self.previewBuffer) previewPainter.drawPixmap(preview_x, preview_y, previewPixmap.width(), previewPixmap.height(), previewPixmap) previewPainter.end() self.imagePreview.setPixmap(self.previewBuffer) self.imagePreview.setMinimumSize(size_x // previewScale, size_y // previewScale) self.imagePreview.setMaximumSize(size_x // previewScale, size_y // previewScale) self.previewScroll.setMinimumSize(size_x // previewScale + 45, 1) self.previewScroll.setMaximumSize(size_x // previewScale, 90001) self.imageView.setMinimumSize(size_x, size_y) self.imageView.setMaximumSize(size_x, size_y) self.updateCursorPositionLabel() def redisplayAnsi(self): """ Redraws the ANSI label. """ self.imageView.repaint() self.imagePreview.repaint() def redisplayPalette(self): """ Redisplays the palette labels """ bitmap = self.palette.get_palette_image() qtBitmap = ImageQt.ImageQt(bitmap) self.palSel.setPixmap(QtGui.QPixmap.fromImage(qtBitmap)) self.palSel.setMinimumSize(qtBitmap.width(), qtBitmap.height()) bitmap = self.palette.get_character_image(16) qtBitmap = ImageQt.ImageQt(bitmap) self.charSel.setPixmap(QtGui.QPixmap.fromImage(qtBitmap)) self.charSel.setMinimumSize(qtBitmap.width(), qtBitmap.height()) for i in range(0, 12): bitmap = self.palette.get_char_image(i, from_seq=True) qtBitmap = ImageQt.ImageQt(bitmap) self.charPalPixmaps[i].setPixmap(QtGui.QPixmap.fromImage(qtBitmap)) self.charPalPixmaps[i].setMinimumSize(qtBitmap.width(), qtBitmap.height()) def keyPressEvent(self, event): """ Global key input handler """ handled = False if event.key() in [ QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Down, QtCore.Qt.Key_Up ]: # Selection start if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): selX, selY = self.ansiImage.get_cursor() append = event.modifiers( ) & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier remove = event.modifiers( ) & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier self.selectionTool.beginSelection(selX, selY, append, remove) else: # Palette change if event.modifiers( ) & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_Right: if event.modifiers( ) & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier: self.palette.set_back(1, True) else: self.palette.set_fore(1, True) if event.key() == QtCore.Qt.Key_Left: if event.modifiers( ) & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier: self.palette.set_back(-1, True) else: self.palette.set_fore(-1, True) if event.key() == QtCore.Qt.Key_Down: self.palette.select_char_sequence(1, True) if event.key() == QtCore.Qt.Key_Up: self.palette.select_char_sequence(-1, True) self.redisplayPalette() handled = True # Cursor move if event.key() == QtCore.Qt.Key_Right and not handled: self.ansiImage.move_cursor(1, 0, True) handled = True if event.key() == QtCore.Qt.Key_Left and not handled: self.ansiImage.move_cursor(-1, 0, True) handled = True if event.key() == QtCore.Qt.Key_Down and not handled: self.ansiImage.move_cursor(0, 1, True) handled = True if event.key() == QtCore.Qt.Key_Up and not handled: self.ansiImage.move_cursor(0, -1, True) handle = True # Selection resume if event.key() in [ QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Down, QtCore.Qt.Key_Up ]: if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): selX, selY = self.ansiImage.get_cursor() append = event.modifiers( ) & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier remove = event.modifiers( ) & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier self.selectionTool.updateSelection(selX, selY, True, append, remove) # Enter key insertion if event.key() == QtCore.Qt.Key_Return: char = self.palette.get_char() self.addUndo( self.ansiImage.set_cell(char=char[0], fore=char[1], back=char[2])) if not self.ansiImage.move_cursor(1, 0): self.ansiImage.move_cursor(x=0, relative=False) self.ansiImage.move_cursor(y=1) handled = True # Smart home/end if event.key() == QtCore.Qt.Key_Home: line_end = self.ansiImage.get_line_end() if self.ansiImage.get_cursor()[0] > line_end + 1: self.ansiImage.move_cursor(x=line_end + 1, relative=False) else: self.ansiImage.move_cursor(x=0, relative=False) handled = True if event.key() == QtCore.Qt.Key_End: line_end = self.ansiImage.get_line_end() if self.ansiImage.get_cursor()[0] < line_end + 1: self.ansiImage.move_cursor(x=line_end + 1, relative=False) else: self.ansiImage.move_cursor(x=10000000, relative=False) handled = True # Ins and Del if event.key() == QtCore.Qt.Key_Insert: if (event.modifiers() & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier): if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): inverse = [] for col in range(self.ansiImage.get_size()[0]): inverse.extend(self.ansiImage.shift_column(x=col)) self.addUndo(inverse) else: self.addUndo(self.ansiImage.shift_column()) else: if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): inverse = [] for line in range(self.ansiImage.get_size()[1]): inverse.extend(self.ansiImage.shift_line(y=line)) self.addUndo(inverse) else: self.addUndo(self.ansiImage.shift_line()) handled = True if event.key() == QtCore.Qt.Key_Delete: if self.ansiImage.has_selection(): self.addUndo(self.ansiImage.fill_selection()) else: if (event.modifiers() & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier): if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): inverse = [] for col in range(self.ansiImage.get_size()[0]): inverse.extend( self.ansiImage.shift_column(x=col, how_much=-1)) self.addUndo(inverse) else: self.addUndo(self.ansiImage.shift_column(how_much=-1)) else: if (event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier): inverse = [] for line in range(self.ansiImage.get_size()[1]): inverse.extend( self.ansiImage.shift_line(y=line, how_much=-1)) self.addUndo(inverse) else: self.addUndo(self.ansiImage.shift_line(how_much=-1)) handled = True # Backspace delete if event.key() == QtCore.Qt.Key_Backspace: if self.ansiImage.move_cursor(-1, 0): self.addUndo( self.ansiImage.set_cell(char=ord(' '), fore=0, back=0)) handled = True # F1-12 insert # Theoretically this could break, practically it should be fine. if event.key() >= QtCore.Qt.Key_F1 and event.key( ) <= QtCore.Qt.Key_F12: pal_idx = event.key() - QtCore.Qt.Key_F1 char = self.palette.get_char(pal_idx, True) self.addUndo( self.ansiImage.set_cell(char=char[0], fore=char[1], back=char[2])) self.ansiImage.move_cursor(1, 0) handled = True # Text-generating keys if event.text() != None and len( event.text()) == 1 and handled == False: char = self.palette.get_char() char[0] = ord(event.text()) if char[0] <= 255: self.addUndo( self.ansiImage.set_cell(char=char[0], fore=char[1], back=char[2])) self.ansiImage.move_cursor(1, 0) handled = True self.redisplayAnsi() self.updateTitle() def keyReleaseEvent(self, event): """ Global key input handler - key up edition """ if event.key() == QtCore.Qt.Key_Shift: if self.selectionTool.selStartX != None and self.selectionTool.selStartY != None: selEndX, selEndY = self.ansiImage.get_cursor() append = ( event.modifiers() & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier) remove = (event.modifiers() & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier) self.selectionTool.updateSelection(selEndX, selEndY, False, append, remove) def updateTitle(self): """ Update title to reflect file name and file dirty condition """ self.title = "HANSE" if self.currentFileName != None: self.title += " - " + self.currentFileName if self.ansiImage.dirty() == True: self.title += " *" self.setWindowTitle(self.title) def newFile(self): """ Create blank 80x24 document """ self.ansiImage = AnsiImage(self.ansiGraphics) self.ansiImage.clear_image(80, 24) self.undoStack = [] self.redoStack = [] self.currentFileName = None self.previewBuffer = None self.redisplayAnsi() self.updateTitle() def openFile(self): """ Load an ansi file """ loadFileName = QtWidgets.QFileDialog.getOpenFileName( self, caption="Open ANSI file", filter= "ANSI Files (*.ans);;Arbitrary width ANSI Files (*.ans);;All Files (*.*)" ) wideMode = False if loadFileName[1] == 'Arbitrary width ANSI Files (*.ans)': wideMode = True loadFileName = loadFileName[0] if len(loadFileName) != 0: self.currentFileName = loadFileName self.ansiImage.load_ans(self.currentFileName, wideMode) self.previewBuffer = None self.redisplayAnsi() self.updateTitle() def saveFileAs(self): """ Save an ansi file, with a certain name """ saveFileName = QtWidgets.QFileDialog.getSaveFileName( self, caption="Save ANSI file", filter="ANSI Files (*.ans);;All Files (*.*)") if saveFileName[1] == 'ANSI Files (*.ans)' and not saveFileName[ 0].endswith(".ans"): saveFileName = saveFileName[0] + ".ans" else: saveFileName = saveFileName[0] if len(saveFileName) != 0: self.currentFileName = saveFileName self.saveFile() def saveFile(self): """ Save an ansi file """ if self.currentFileName == None: self.saveFileAs() else: self.ansiImage.save_ans(self.currentFileName) self.ansiImage.dirty(False) self.updateTitle() def exportPNG(self): """ Export file as rendered PNG image """ exportFileName = QtWidgets.QFileDialog.getSaveFileName( self, caption="Export PNG", filter="PNG File (*.png)")[0] bitmap = self.ansiImage.to_bitmap(transparent=False, cursor=False) bitmap.save(exportFileName, "PNG") def exit(self): """ Close everything and exit. """ sys.exit(0) def clipboardCopy(self): """ Copy to (right now internal) clipboard """ pasteBuffer = self.ansiImage.get_selected( self.toggleSkipSpace.isChecked()) # Text representation for external use extent_x = 0 extent_y = 0 for (x, y, char) in pasteBuffer: extent_x = max(extent_x, x) extent_y = max(extent_y, y) stringData = [] for y in range(extent_y + 1): lineData = [] for x in range(extent_x + 1): lineData.append(" ") stringData.append(lineData) for (x, y, char) in pasteBuffer: #print(x, y, len(stringData), len(stringData[0])) stringData[y][x] = char[0] stringRepresentation = "" try: for y in range(extent_y): stringRepresentation += bytes( stringData[y]).decode('cp866') + "\n" except: pass # Internal json representation byteData = QtCore.QByteArray(json.dumps(pasteBuffer).encode('utf-8')) mimeData = QtCore.QMimeData() mimeData.setData('text/hansejson', byteData) mimeData.setText(stringRepresentation) # All to clipboard clipboard = Qt.QApplication.clipboard() clipboard.setMimeData(mimeData) def clipboardCut(self): """ Copy, then delete """ self.clipboardCopy() self.addUndo(self.ansiImage.fill_selection()) self.redisplayAnsi() def clipboardPaste(self): """ Paste at cursor """ clipboard = Qt.QApplication.clipboard() mimeData = clipboard.mimeData() # This can fail in a myriad ways - if it does, that's fine. try: pasteBuffer = None if mimeData.hasFormat('text/hansejson'): pasteBuffer = json.loads( mimeData.data('text/hansejson').data().decode('utf-8')) else: if mimeData.hasText(): pasteText = mimeData.text() lines = pasteText.split("\n") pasteBuffer = [] for y, line in enumerate(lines): for x, char in enumerate(line): pasteBuffer.append((x, y, (char.encode('cp866')[0], self.palette.fore(), self.palette.back()))) if pasteBuffer != None: self.addUndo(self.ansiImage.paste(pasteBuffer)) self.redisplayAnsi() except: pass def changeWriteStatus(self): """ Change which channels we are writing to. """ self.ansiImage.set_write_allowed(self.toggleWriteChar.isChecked(), self.toggleWriteFore.isChecked(), self.toggleWriteBack.isChecked()) def resizeCanvas(self): """ Get size via dialog and resize """ cur_width, cur_height = self.ansiImage.get_size() sizeDialog = SizeDialog(cur_width, cur_height) if sizeDialog.exec() == 1: new_width = sizeDialog.spinBoxWidth.value() new_height = sizeDialog.spinBoxHeight.value() self.addUndo( (-1, self.ansiImage.change_size(new_width, new_height))) self.redisplayAnsi() def addUndo(self, operation): """ Add an undo step (and clean out the redo stack) """ self.undoStack.append(operation) self.redoStack = [] def undo(self): """ Undo last action """ if len(self.undoStack) != 0: undoAction = self.undoStack.pop() if undoAction[0] == -1: undoAction = undoAction[1] self.redoStack.append( (-1, self.ansiImage.change_size(undoAction[0], undoAction[1], undoAction[2]))) else: self.redoStack.append( self.ansiImage.paste(undoAction, x=0, y=0)) self.redisplayAnsi() def redo(self): """ Undo last undo """ if len(self.redoStack) != 0: redoAction = self.redoStack.pop() if redoAction[0] == -1: redoAction = redoAction[1] self.undoStack.append( (-1, self.ansiImage.change_size(redoAction[0], redoAction[1], redoAction[2]))) else: self.undoStack.append( self.ansiImage.paste(redoAction, x=0, y=0)) self.redisplayAnsi() def changeTransparent(self): """ Just call the redisplay function - it knows what to do. """ self.redisplayAnsi() def changeHideCursor(self): """ Just call the redisplay function - it knows what to do. """ self.redisplayAnsi() def loadReferenceImage(self): """ Sets up a reference image """ refFileName = QtWidgets.QFileDialog.getOpenFileName( self, caption="Open reference image", filter="Image Files (*.png *.jpg)")[0] try: self.refImage.load(refFileName) self.redisplayAnsi() except: pass def changeFont(self): """ Change the font """ for i in range(len(self.toggleFont)): if self.toggleFont[i].isChecked(): self.activeFont = i self.ansiGraphics = AnsiGraphics( os.path.join('config', self.fonts[self.activeFont]['file']), self.fonts[self.activeFont]['width'], self.fonts[self.activeFont]['height'], ) self.palette.change_graphics(self.ansiGraphics) self.ansiImage.change_graphics(self.ansiGraphics) self.previewBuffer = None self.redisplayAnsi() self.redisplayPalette() def showCharSelect(self): """ Allow the user to select a new set of characters """ self.hoveredCharSel = None self.menuCharacterSelect.exec(QtGui.QCursor.pos()) def setCharSequence(self): """ Change the selected character sequence """ idx = 0 for num, action in enumerate(self.actionsCharacterSelect): if action.isChecked(): idx = num self.palette.select_char_sequence(idx) self.redisplayPalette()