From 045b7a24ed9dbffb667ba82e4494778d59ab5656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Thu, 5 Sep 2019 16:55:07 +0200 Subject: [PATCH] Implements importing from {ANSI,SAUCE} and exporting to ANSI files. assets/tools/{MiRCARTTo{Ansi,PngFile},SAUCETo{Ansi,MiRCART}}.py: reimplemented using Canvas{Ex,Im}portStore. libcanvas/Canvas.py: minor cleanup. libcanvas/CanvasExportStore.py:export{Ansi,Png}File(): merged from assets/tools/MiRCARTTo{Ansi,PngFile}.py. libcanvas/CanvasImportStore.py:import{Ansi,Sauce}File(): merged from assets/tools/SAUCETo{Ansi,MiRCART}.py. libcanvas/Colours.py:{AnsiBgToMiRCARTColours,AnsiFgToMiRCARTColours,AnsiFgBoldToMiRCARTColours}{}: merged from assets/tools/SAUCEToMiRCART.py. libcanvas/Colours.py:{ColourMap{Bold,Normal}}[]: merged from assets/tools/MiRCARTToPngFile.py. libcanvas/Colours.py: minor cleanup. libgui/GuiCanvasInterface.py:canvas{ExportAsAnsi,Import{Ansi,Sauce}}(): implemented. libgui/GuiFrame.py: adds CID_{EXPORT_AS_{ANSI,SAUCE},IMPORT_{ANSI,SAUCE}}. assets/text/TODO: updated. --- assets/text/TODO | 19 ++-- assets/tools/MiRCARTToAnsi.py | 53 ++--------- assets/tools/MiRCARTToPngFile.py | 121 ++--------------------- assets/tools/SAUCEToAnsi.py | 50 +++------- assets/tools/SAUCEToMiRCART.py | 144 +++------------------------- libcanvas/Canvas.py | 4 +- libcanvas/CanvasExportStore.py | 101 +++++++++++++++++--- libcanvas/CanvasImportStore.py | 159 +++++++++++++++++++++++++++++-- libcanvas/Colours.py | 127 +++++++++++++++++++++++- libgui/GuiCanvasInterface.py | 70 ++++++++++++-- libgui/GuiFrame.py | 121 +++++++++++------------ 11 files changed, 537 insertions(+), 432 deletions(-) diff --git a/assets/text/TODO b/assets/text/TODO index 072c487..54acafe 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -1,15 +1,14 @@ 1) General {cleanup,refactor} -2) Incremental auto{load,save} & {backup,restore} -3) Open and toggle a reference image in the background -4) Client-Server or Peer-to-Peer realtime collaboration -5) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.) -6) Hotkey & graphical interfaces to {composed,parametrised} tools -7) Layer, layout (e.g. for comics, zines, etc.) & {re,un}do canvas traits +2) Implement ANSI CSI CU[BDPU] sequences +3) Incremental auto{load,save} & {backup,restore} +4) Open and toggle a reference image in the background +5) Client-Server or Peer-to-Peer realtime collaboration +6) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.) +7) Hotkey & graphical interfaces to {composed,parametrised} tools 8) GUI: a) scrollbar b) switch from wxPython to GTK c) revisit About dialogue -9) {Insert,{ex,im}port}ing from/to ANSI, Blender, GIF, HTML, mIRC, printer, SAUCE, WEBM, etc. {clipboard,file} -10) Asset management (e.g. kade, lion, etc.) & traits w/ simple linking & synchronised editing respecting layers -11) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...) -12) Composition and parametrisation of tools from higher-order operators (brushes, filters, outlines, patterns & shaders) and unit tools; unit tools: +9) Layers, layout (e.g. for comics, zines, etc.) & asset management (e.g. kade, lion, etc.) & traits w/ {inserting,merging,linking} +10) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...) +11) Composition and parametrisation of tools from higher-order operators (brushes, filters, outlines, patterns & shaders) and unit tools; unit tools: a) geometric primitives (arrow, circle, cloud/speech bubble, curve, heart, hexagon, line, pentagon, polygon, rhombus, triangle, square, star) b) regions (crop, duplicate, erase, fill, invert, measure, pick, rotate, scale, select, shift, slice, tile, translate) c) text (edit, Unicode sets) diff --git a/assets/tools/MiRCARTToAnsi.py b/assets/tools/MiRCARTToAnsi.py index 33ae046..c057d82 100755 --- a/assets/tools/MiRCARTToAnsi.py +++ b/assets/tools/MiRCARTToAnsi.py @@ -8,59 +8,20 @@ import os, sys [sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]] +from CanvasExportStore import CanvasExportStore from CanvasImportStore import CanvasImportStore -MiRCARTToAnsiColours = [ - 97, # Bright White - 30, # Black - 94, # Light Blue - 32, # Green - 91, # Red - 31, # Light Red - 35, # Pink - 33, # Yellow - 93, # Light Yellow - 92, # Light Green - 36, # Cyan - 96, # Light Cyan - 34, # Blue - 95, # Light Pink - 90, # Grey - 37, # Light Grey -]; - -def ToAnsi(inPathName): - canvasStore = CanvasImportStore(inPathName) - inMap = canvasStore.outMap.copy(); del canvasStore; - with open(inPathName, "w+") as outFile: - for inCurRow in range(len(inMap)): - lastAttribs = CanvasImportStore._CellState.CS_NONE - lastColours = None - for inCurCol in range(len(inMap[inCurRow])): - inCurCell = inMap[inCurRow][inCurCol] - if lastAttribs != inCurCell[2]: - if inCurCell[2] & CanvasImportStore._CellState.CS_BOLD: - print("\u001b[1m", end="", file=outFile) - if inCurCell[2] & CanvasImportStore._CellState.CS_UNDERLINE: - print("\u001b[4m", end="", file=outFile) - lastAttribs = inCurCell[2] - if lastColours == None or lastColours != inCurCell[:2]: - ansiBg = MiRCARTToAnsiColours[int(inCurCell[1])] + 10 - ansiFg = MiRCARTToAnsiColours[int(inCurCell[0])] - print("\u001b[{:02d}m\u001b[{:02d}m{}".format(ansiBg, ansiFg, inCurCell[3]), end="", file=outFile) - lastColours = inCurCell[:2] - else: - print(inCurCell[3], end="", file=outFile) - print("\u001b[0m\n", end="", file=outFile) - # # Entry point def main(*argv): - ToAnsi(argv[1]) -if __name__ == "__main__": if (len(sys.argv) - 1) != 1: print("usage: {} ".format(sys.argv[0]), file=sys.stderr) else: - main(*sys.argv) + canvasImportStore = CanvasImportStore() + canvasImportStore.importAnsiFile(argv[1]) + canvasExportStore = CanvasExportStore() + canvasExportStore.exportAnsiFile(canvasImportStore.outMap, canvasImportStore.inSize, sys.stdout) +if __name__ == "__main__": + main(*sys.argv) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/assets/tools/MiRCARTToPngFile.py b/assets/tools/MiRCARTToPngFile.py index ea035dd..a958b91 100755 --- a/assets/tools/MiRCARTToPngFile.py +++ b/assets/tools/MiRCARTToPngFile.py @@ -6,122 +6,11 @@ # import os, sys -[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl", "libtools"]] +[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]] +from CanvasExportStore import CanvasExportStore from CanvasImportStore import CanvasImportStore from getopt import getopt, GetoptError -from PIL import Image, ImageDraw, ImageFont - -class MiRCARTToPngFile: - """XXX""" - inFile = inFromTextFile = None - outFontFilePath = outFontSize = None - - # {{{ _ColourMapBold: mIRC colour number to RGBA map given ^B (bold) - _ColourMapBold = [ - [255, 255, 255], # Bright White - [85, 85, 85], # Black - [85, 85, 255], # Light Blue - [85, 255, 85], # Green - [255, 85, 85], # Red - [255, 85, 85], # Light Red - [255, 85, 255], # Pink - [255, 255, 85], # Yellow - [255, 255, 85], # Light Yellow - [85, 255, 85], # Light Green - [85, 255, 255], # Cyan - [85, 255, 255], # Light Cyan - [85, 85, 255], # Blue - [255, 85, 255], # Light Pink - [85, 85, 85], # Grey - [255, 255, 255], # Light Grey - ] - # }}} - # {{{ _ColourMapNormal: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline) - _ColourMapNormal = [ - [255, 255, 255], # Bright White - [0, 0, 0], # Black - [0, 0, 187], # Light Blue - [0, 187, 0], # Green - [255, 85, 85], # Red - [187, 0, 0], # Light Red - [187, 0, 187], # Pink - [187, 187, 0], # Yellow - [255, 255, 85], # Light Yellow - [85, 255, 85], # Light Green - [0, 187, 187], # Cyan - [85, 255, 255], # Light Cyan - [85, 85, 255], # Blue - [255, 85, 255], # Light Pink - [85, 85, 85], # Grey - [187, 187, 187], # Light Grey - ] - # }}} - # {{{ _drawUnderline(self, curPos, fontSize, imgDraw, fillColour): XXX - def _drawUnderLine(self, curPos, fontSize, imgDraw, fillColour): - imgDraw.line( \ - xy=(curPos[0], curPos[1] + (fontSize[1] - 2), \ - curPos[0] + fontSize[0], curPos[1] + (fontSize[1] - 2)), \ - fill=fillColour) - # }}} - # {{{ export(self, outFilePath): XXX - def export(self, outFilePath): - inSize = (len(self.inCanvasMap[0]), len(self.inCanvasMap)) - outSize = [a*b for a,b in zip(inSize, self.outImgFontSize)] - outCurPos = [0, 0] - outImg = Image.new("RGBA", outSize, (*self._ColourMapNormal[1], 255)) - outImgDraw = ImageDraw.Draw(outImg) - outImgDraw.fontmode = "1" - for inCurRow in range(len(self.inCanvasMap)): - for inCurCol in range(len(self.inCanvasMap[inCurRow])): - inCurCell = self.inCanvasMap[inCurRow][inCurCol] - outColours = [0, 0] - if inCurCell[2] & CanvasImportStore._CellState.CS_BOLD: - if inCurCell[3] != " ": - if inCurCell[3] == "█": - outColours[1] = self._ColourMapNormal[inCurCell[0]] - else: - outColours[0] = self._ColourMapBold[inCurCell[0]] - outColours[1] = self._ColourMapNormal[inCurCell[1]] - else: - outColours[1] = self._ColourMapNormal[inCurCell[1]] - else: - if inCurCell[3] != " ": - if inCurCell[3] == "█": - outColours[1] = self._ColourMapNormal[inCurCell[0]] - else: - outColours[0] = self._ColourMapNormal[inCurCell[0]] - outColours[1] = self._ColourMapNormal[inCurCell[1]] - else: - outColours[1] = self._ColourMapNormal[inCurCell[1]] - outImgDraw.rectangle((*outCurPos, \ - outCurPos[0] + self.outImgFontSize[0], \ - outCurPos[1] + self.outImgFontSize[1]), \ - fill=(*outColours[1], 255)) - if not inCurCell[3] in " █" \ - and outColours[0] != outColours[1]: - # XXX implement italic - outImgDraw.text(outCurPos, \ - inCurCell[3], (*outColours[0], 255), self.outImgFont) - if inCurCell[2] & CanvasImportStore._CellState.CS_UNDERLINE: - outColours[0] = self._ColourMapNormal[inCurCell[0]] - self._drawUnderLine(outCurPos, \ - self.outImgFontSize, \ - outImgDraw, (*outColours[0], 255)) - outCurPos[0] += self.outImgFontSize[0]; - outCurPos[0] = 0 - outCurPos[1] += self.outImgFontSize[1] - outImg.save(outFilePath); - # }}} - - # - # __init__(self, inCanvasMap, fontFilePath, fontSize): initialisation method - def __init__(self, inCanvasMap, fontFilePath, fontSize): - self.inCanvasMap = inCanvasMap - self.outFontFilePath, self.outFontSize = fontFilePath, fontSize - self.outImgFont = ImageFont.truetype(self.outFontFilePath, self.outFontSize) - self.outImgFontSize = [*self.outImgFont.getsize(" ")] - self.outImgFontSize[1] += 3 # # Entry point @@ -137,8 +26,10 @@ def main(*argv): optdict["-f"] = os.path.join("..", "fonts", "DejaVuSansMono.ttf") optdict["-s"] = 11 if not "-s" in optdict else int(optdict["-s"]) for inFile in argv: - canvasStore, outFile = CanvasImportStore(inFile=inFile), os.path.splitext(inFile)[0] + ".png" - MiRCARTToPngFile(canvasStore.outMap, fontFilePath=optdict["-f"], fontSize=optdict["-s"]).export(outFile) + canvasImportStore = CanvasImportStore() + canvasImportStore.importTextFile(inFile) + canvasExportStore = CanvasExportStore() + canvasExportStore.exportPngFile(canvasImportStore.outMap, os.path.splitext(inFile)[0] + ".png", optdict["-f"], optdict["-s"]) if __name__ == "__main__": main(*sys.argv) diff --git a/assets/tools/SAUCEToAnsi.py b/assets/tools/SAUCEToAnsi.py index 1f7a7af..cfc8301 100755 --- a/assets/tools/SAUCEToAnsi.py +++ b/assets/tools/SAUCEToAnsi.py @@ -5,52 +5,24 @@ # This project is licensed under the terms of the MIT licence. # -import os, re, struct, sys +import os, sys +[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]] -def SAUCEToAnsi(inPathName, outPathName): - with open(inPathName, "rb") as inFile: - inFileStat = os.stat(inPathName) - inFile.seek(inFileStat.st_size - 128, 0) - inFile.seek(5 + 2 + 35 + 20 + 20 + 8 + 4, 1) - if (inFile.read(1) != b'\x01') \ - or (inFile.read(1) != b'\x01'): - print("error: only character based ANSi SAUCE files are supported.", file=sys.stderr) - return 1 - else: - width, height = struct.unpack("H", inFile.read(2))[0], struct.unpack("H", inFile.read(2))[0] - with open(outPathName, "w+") as outFile: - inFile.seek(0, 0) - inFileData, row, rowChars = inFile.read(inFileStat.st_size - 128).decode("cp437"), "", 0 - inFileChar, inFileCharMax = 0, len(inFileData) - while True: - if inFileChar >= inFileCharMax: - break - else: - m = re.match('\x1b\[((?:\d{1,3};?)+)m', inFileData[inFileChar:]) - if m: - row += m[0]; inFileChar += len(m[0]); - else: - m = re.match('\x1b\[(\d+)C', inFileData[inFileChar:]) - if m: - row += m[0]; inFileChar += len(m[0]); rowChars += int(m[1]); - elif inFileData[inFileChar:inFileChar+2] == "\r\n": - inFileChar += 2; rowChars = width; - elif inFileData[inFileChar] == "\r" \ - or inFileData[inFileChar] == "\n": - inFileChar += 1; rowChars = width; - else: - row += inFileData[inFileChar]; inFileChar += 1; rowChars += 1; - if rowChars >= width: - print(row, file=outFile); row = ""; rowChars = 0; +from CanvasExportStore import CanvasExportStore +from CanvasImportStore import CanvasImportStore # # Entry point def main(*argv): - SAUCEToAnsi(argv[1], argv[2]) -if __name__ == "__main__": if (len(sys.argv) - 1) != 2: print("usage: {} ".format(sys.argv[0]), file=sys.stderr) else: - main(*sys.argv) + canvasImportStore = CanvasImportStore() + canvasImportStore.importSauceFile(argv[1]) + canvasExportStore = CanvasExportStore() + with open(argv[2], "w", encoding="utf-8") as outFile: + canvasExportStore.exportAnsiFile(canvasImportStore.outMap, canvasImportStore.inSize, outFile) +if __name__ == "__main__": + main(*sys.argv) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/assets/tools/SAUCEToMiRCART.py b/assets/tools/SAUCEToMiRCART.py index 272725c..91ee27e 100755 --- a/assets/tools/SAUCEToMiRCART.py +++ b/assets/tools/SAUCEToMiRCART.py @@ -5,146 +5,24 @@ # This project is licensed under the terms of the MIT licence. # -import os, re, struct, sys +import os, sys +[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]] -AnsiBgToMiRCARTColours = { - 107: 0, # Bright White - 40: 1, # Black - 104: 2, # Blue - 42: 3, # Green - 101: 4, # Red - 41: 5, # Light Red - 45: 6, # Pink - 43: 7, # Yellow - 103: 8, # Light Yellow - 102: 9, # Light Green - 46: 10, # Cyan - 106: 11, # Light Cyan - 44: 12, # Light Blue - 105: 13, # Light Pink - 100: 14, # Grey - 47: 15, # Light Grey -}; +from CanvasExportStore import CanvasExportStore +from CanvasImportStore import CanvasImportStore -AnsiFgToMiRCARTColours = { - 97: 0, # Bright White - 30: 1, # Black - 94: 2, # Blue - 32: 3, # Green - 91: 4, # Red - 31: 5, # Light Red - 35: 6, # Pink - 33: 7, # Yellow - 93: 8, # Light Yellow - 92: 9, # Light Green - 36: 10, # Cyan - 96: 11, # Light Cyan - 34: 12, # Light Blue - 95: 13, # Light Pink - 90: 14, # Grey - 37: 15, # Light Grey -}; - -AnsiFgBoldToMiRCARTColours = { - 97: 0, # Bright White - 30: 14, # Grey - 94: 12, # Light Blue - 32: 9, # Light Green - 91: 4, # Light Red - 31: 4, # Light Red - 35: 13, # Light Pink - 33: 8, # Light Yellow - 93: 8, # Light Yellow - 92: 9, # Light Green - 36: 11, # Light Cyan - 96: 11, # Light Cyan - 34: 12, # Light Blue - 95: 13, # Light Pink - 90: 14, # Grey - 37: 0, # Bright White -}; - -def SAUCEToMiRCART(inPathName, outPathName): - with open(inPathName, "rb") as inFile: - inFileStat = os.stat(inPathName) - inFile.seek(inFileStat.st_size - 128, 0) - inFile.seek(5 + 2 + 35 + 20 + 20 + 8 + 4, 1) - if (inFile.read(1) != b'\x01') \ - or (inFile.read(1) != b'\x01'): - print("error: only character based ANSi SAUCE files are supported.", file=sys.stderr) - return 1 - else: - width, height = struct.unpack("H", inFile.read(2))[0], struct.unpack("H", inFile.read(2))[0] - with open(outPathName, "w+") as outFile: - inFile.seek(0, 0) - inFileData, row, rowChars = inFile.read(inFileStat.st_size - 128).decode("cp437"), "", 0 - inFileChar, inFileCharMax = 0, len(inFileData) - curBg, curFg = 1, 15; curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; - while True: - if inFileChar >= inFileCharMax: - break - else: - m = re.match('\x1b\[((?:\d{1,3};?)+)m', inFileData[inFileChar:]) - if m: - newBg, newFg = -1, -1 - for ansiCode in m[1].split(";"): - ansiCode = int(ansiCode) - if ansiCode == 0: - curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; newBg, newFg = 1, 15; - elif ansiCode == 1: - curBoldAnsi, newFg = True, AnsiFgBoldToMiRCARTColours[curFgAnsi] - elif ansiCode == 2: - curBoldAnsi, newFg = False, AnsiFgToMiRCARTColours[curFgAnsi] - elif ansiCode == 7: - newBg, newFg = curFg, curBg; curBgAnsi, curFgAnsi = curFgAnsi, curBgAnsi; - elif ansiCode in AnsiBgToMiRCARTColours: - curBgAnsi, newBg = ansiCode, AnsiBgToMiRCARTColours[ansiCode] - elif ansiCode in AnsiFgToMiRCARTColours: - if curBoldAnsi: - newFg = AnsiFgBoldToMiRCARTColours[ansiCode] - else: - newFg = AnsiFgToMiRCARTColours[ansiCode] - curFgAnsi = ansiCode - elif ansiCode in AnsiFgBoldToMiRCARTColours: - curFgAnsi, newFg = ansiCode, AnsiFgBoldToMiRCARTColours[ansiCode] - if ((newBg != -1) and (newFg != -1)) \ - and ((newBg == curFg) and (newFg == curBg)): - row += "\u0016"; curBg, curFg = newBg, newFg; - elif ((newBg != -1) and (newFg != -1)) \ - and ((newBg != curBg) and (newFg != curFg)): - row += "\u0003{},{}".format(newFg, newBg); curBg, curFg = newBg, newFg; - elif (newBg != -1) and (newBg != curBg): - row += "\u0003{},{}".format(curFg, newBg); curBg = newBg; - elif (newFg != -1) and (newFg != curFg): - row += "\u0003{}".format(newFg); curFg = newFg; - inFileChar += len(m[0]) - else: - m = re.match('\x1b\[(\d+)C', inFileData[inFileChar:]) - if m: - row += m[0]; inFileChar += len(m[0]); rowChars += int(m[1]); - elif inFileData[inFileChar:inFileChar+2] == "\r\n": - inFileChar += 2; rowChars = width; - elif inFileData[inFileChar] == "\r" \ - or inFileData[inFileChar] == "\n": - inFileChar += 1; rowChars = width; - else: - row += inFileData[inFileChar]; inFileChar += 1; rowChars += 1; - if rowChars >= width: - print(row, file=outFile); row = ""; rowChars = 0; - if (curBg != 1) and (curFg != 15): - row += "\u0003{},{}".format(curFg, curBg); - elif curBg != 1: - row += "\u0003{},{}".format(curFg, curBg); - elif curFg != 1: - row += "\u0003{}".format(curFg); # # Entry point def main(*argv): - SAUCEToMiRCART(argv[1], argv[2]) -if __name__ == "__main__": if (len(sys.argv) - 1) != 2: print("usage: {} ".format(sys.argv[0]), file=sys.stderr) else: - main(*sys.argv) + canvasImportStore = CanvasImportStore() + canvasImportStore.importSauceFile(argv[1]) + canvasExportStore = CanvasExportStore() + with open(argv[2], "w", encoding="utf-8") as outFile: + canvasExportStore.exportTextFile(canvasImportStore.outMap, canvasImportStore.inSize, outFile) +if __name__ == "__main__": + main(*sys.argv) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libcanvas/Canvas.py b/libcanvas/Canvas.py index 53bcc46..6761249 100644 --- a/libcanvas/Canvas.py +++ b/libcanvas/Canvas.py @@ -6,7 +6,7 @@ from CanvasBackend import CanvasBackend from CanvasJournal import CanvasJournal -from CanvasExportStore import CanvasExportStore, haveToPngFile, haveUrllib +from CanvasExportStore import CanvasExportStore, havePIL, haveUrllib from CanvasImportStore import CanvasImportStore from ImgurApiKey import ImgurApiKey import wx @@ -183,7 +183,7 @@ class Canvas(wx.Panel): self.canvasBackend = CanvasBackend(defaultCanvasSize, defaultCellSize) self.canvasJournal = CanvasJournal() - self.canvasExportStore = CanvasExportStore(parentCanvas=self) + self.canvasExportStore = CanvasExportStore() self.canvasImportStore = CanvasImportStore(parentCanvas=self) self.canvasInterface = canvasInterface(self, parentFrame) diff --git a/libcanvas/CanvasExportStore.py b/libcanvas/CanvasExportStore.py index 0632d6f..d1410a5 100644 --- a/libcanvas/CanvasExportStore.py +++ b/libcanvas/CanvasExportStore.py @@ -4,13 +4,14 @@ # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz # +from Colours import ColourMapBold, ColourMapNormal, MiRCARTToAnsiColours import io, os, tempfile try: - from ToPngFile import ToPngFile - haveToPngFile = True + from PIL import Image, ImageDraw, ImageFont + havePIL = True except ImportError: - haveToPngFile = False + havePIL = False try: import base64, json, requests, urllib.request @@ -20,9 +21,23 @@ except ImportError: class CanvasExportStore(): """XXX""" + # {{{ _CellState(): Cell state + class _CellState(): + CS_NONE = 0x00 + CS_BOLD = 0x01 + CS_ITALIC = 0x02 + CS_UNDERLINE = 0x04 + # }}} ImgurUploadUrl = "https://api.imgur.com/3/upload.json" PastebinPostUrl = "https://pastebin.com/api/api_post.php" + # {{{ _drawUnderline(self, curPos, fontSize, imgDraw, fillColour): XXX + def _drawUnderLine(self, curPos, fontSize, imgDraw, fillColour): + imgDraw.line( \ + xy=(curPos[0], curPos[1] + (fontSize[1] - 2), \ + curPos[0] + fontSize[0], curPos[1] + (fontSize[1] - 2)), \ + fill=fillColour) + # }}} # {{{ _exportFileToImgur(self, apiKey, imgName, imgTitle, pathName): upload single PNG file to Imgur def _exportFileToImgur(self, apiKey, imgName, imgTitle, pathName): with open(pathName, "rb") as requestImage: @@ -42,6 +57,30 @@ class CanvasExportStore(): return [responseHttp.status_code, ""] # }}} + # {{{ exportAnsiFile(self, canvasMap, canvasSize, outFile): XXX + def exportAnsiFile(self, canvasMap, canvasSize, outFile): + outBuffer = "" + for inCurRow in range(len(canvasMap)): + lastAttribs = self._CellState.CS_NONE + lastColours = None + for inCurCol in range(len(canvasMap[inCurRow])): + inCurCell = canvasMap[inCurRow][inCurCol] + if lastAttribs != inCurCell[2]: + if inCurCell[2] & self._CellState.CS_BOLD: + outBuffer += "\u001b[1m" + if inCurCell[2] & self._CellState.CS_UNDERLINE: + outBuffer += "\u001b[4m" + lastAttribs = inCurCell[2] + if lastColours == None or lastColours != inCurCell[:2]: + ansiBg = MiRCARTToAnsiColours[int(inCurCell[1])] + 10 + ansiFg = MiRCARTToAnsiColours[int(inCurCell[0])] + outBuffer += "\u001b[{:02d}m\u001b[{:02d}m{}".format(ansiBg, ansiFg, inCurCell[3]) + lastColours = inCurCell[:2] + else: + outBuffer += inCurCell[3] + outBuffer += "\u001b[0m\n" + outFile.write(outBuffer) + # }}} # {{{ exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType): XXX def exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType): tmpPathName = tempfile.mkstemp() @@ -77,10 +116,53 @@ class CanvasExportStore(): else: return (False, "missing requests and/or urllib3 module(s)") # }}} - # {{{ exportPngFile(self, canvasMap, outPathName): XXX - def exportPngFile(self, canvasMap, outPathName): - if haveToPngFile: - ToPngFile(canvasMap).export(outPathName) + # {{{ exportPngFile(self, canvasMap, outPathName, fontFilePath, fontSize): XXX + def exportPngFile(self, canvasMap, outPathName, fontFilePath, fontSize): + if havePIL: + outFontFilePath, outFontSize = fontFilePath, fontSize + outImgFont = ImageFont.truetype(outFontFilePath, outFontSize) + outImgFontSize = [*outImgFont.getsize(" ")] + outImgFontSize[1] += 3 + inSize = (len(canvasMap[0]), len(canvasMap)) + outSize = [a*b for a,b in zip(inSize, outImgFontSize)] + outCurPos = [0, 0] + outImg = Image.new("RGBA", outSize, (*ColourMapNormal[1], 255)) + outImgDraw = ImageDraw.Draw(outImg) + outImgDraw.fontmode = "1" + for inCurRow in range(len(canvasMap)): + for inCurCol in range(len(canvasMap[inCurRow])): + inCurCell = canvasMap[inCurRow][inCurCol] + outColours = [0, 0] + if inCurCell[2] & self._CellState.CS_BOLD: + if inCurCell[3] != " ": + if inCurCell[3] == "█": + outColours[1] = ColourMapNormal[inCurCell[0]] + else: + outColours[0] = ColourMapBold[inCurCell[0]] + outColours[1] = ColourMapNormal[inCurCell[1]] + else: + outColours[1] = ColourMapNormal[inCurCell[1]] + else: + if inCurCell[3] != " ": + if inCurCell[3] == "█": + outColours[1] = ColourMapNormal[inCurCell[0]] + else: + outColours[0] = ColourMapNormal[inCurCell[0]] + outColours[1] = ColourMapNormal[inCurCell[1]] + else: + outColours[1] = ColourMapNormal[inCurCell[1]] + outImgDraw.rectangle((*outCurPos, outCurPos[0] + outImgFontSize[0], outCurPos[1] + outImgFontSize[1]), fill=(*outColours[1], 255)) + if not inCurCell[3] in " █" \ + and outColours[0] != outColours[1]: + # XXX implement italic + outImgDraw.text(outCurPos, inCurCell[3], (*outColours[0], 255), outImgFont) + if inCurCell[2] & self._CellState.CS_UNDERLINE: + outColours[0] = ColourMapNormal[inCurCell[0]] + self._drawUnderLine(outCurPos, outImgFontSize, outImgDraw, (*outColours[0], 255)) + outCurPos[0] += outImgFontSize[0]; + outCurPos[0] = 0 + outCurPos[1] += outImgFontSize[1] + outImg.save(outPathName); return True else: return False @@ -117,9 +199,4 @@ class CanvasExportStore(): outFile.write(outBuffer) # }}} - # - # __init__(self, parentCanvas): initialisation method - def __init__(self, parentCanvas): - self.parentCanvas = parentCanvas - # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libcanvas/CanvasImportStore.py b/libcanvas/CanvasImportStore.py index 92c04f2..65575a1 100644 --- a/libcanvas/CanvasImportStore.py +++ b/libcanvas/CanvasImportStore.py @@ -5,23 +5,24 @@ # This project is licensed under the terms of the MIT licence. # +from Colours import AnsiBgToMiRCARTColours, AnsiFgToMiRCARTColours, AnsiFgBoldToMiRCARTColours +import os, re, struct, sys + class CanvasImportStore(): """XXX""" - - # - # _CellState(): Cell state + # {{{ _CellState(): Cell state class _CellState(): CS_NONE = 0x00 CS_BOLD = 0x01 CS_ITALIC = 0x02 CS_UNDERLINE = 0x04 - - # - # _ParseState(): Parsing loop state + # }}} + # {{{ _ParseState(): Parsing loop state class _ParseState(): PS_CHAR = 1 PS_COLOUR_DIGIT0 = 2 PS_COLOUR_DIGIT1 = 3 + # }}} # {{{ _flipCellStateBit(self, cellState, bit): XXX def _flipCellStateBit(self, cellState, bit): @@ -41,6 +42,77 @@ class CanvasImportStore(): return (15, 1) # }}} + # {{{ importAnsiFile(self, inPathName, encoding="cp437"): XXX + def importAnsiFile(self, inPathName, encoding="cp437"): + return self.importAnsiFileBuffer(open(inPathName, "rb"), encoding) + # }}} + # {{{ importAnsiFileBuffer(self, inFile, encoding="cp437"): XXX + def importAnsiFileBuffer(self, inFile, encoding="cp437"): + self.inSize, self.outMap = None, None; inMaxCols, inSize, outMap = 0, [0, 0], [[]]; + inFileData, row, rowChars = inFile.read().decode(encoding), "", 0 + inFileChar, inFileCharMax = 0, len(inFileData) + curBg, curFg, done, inCurRow = 1, 15, False, 0; curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; + while True: + if inFileChar >= inFileCharMax: + break + else: + m = re.match('\x1b\[((?:\d{1,3};?)+)m', inFileData[inFileChar:]) + if m: + newBg, newFg = -1, -1 + for ansiCode in m[1].split(";"): + ansiCode = int(ansiCode) + if ansiCode == 0: + curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; newBg, newFg = 1, 15; + elif ansiCode == 1: + curBoldAnsi, newFg = True, AnsiFgBoldToMiRCARTColours[curFgAnsi] + elif ansiCode == 2: + curBoldAnsi, newFg = False, AnsiFgToMiRCARTColours[curFgAnsi] + elif ansiCode == 7: + newBg, newFg = curFg, curBg; curBgAnsi, curFgAnsi = curFgAnsi, curBgAnsi; + elif ansiCode in AnsiBgToMiRCARTColours: + curBgAnsi, newBg = ansiCode, AnsiBgToMiRCARTColours[ansiCode] + elif ansiCode in AnsiFgToMiRCARTColours: + if curBoldAnsi: + newFg = AnsiFgBoldToMiRCARTColours[ansiCode] + else: + newFg = AnsiFgToMiRCARTColours[ansiCode] + curFgAnsi = ansiCode + elif ansiCode in AnsiFgBoldToMiRCARTColours: + curFgAnsi, newFg = ansiCode, AnsiFgBoldToMiRCARTColours[ansiCode] + if ((newBg != -1) and (newFg != -1)) \ + and ((newBg == curFg) and (newFg == curBg)): + curBg, curFg = newBg, newFg + elif ((newBg != -1) and (newFg != -1)) \ + and ((newBg != curBg) and (newFg != curFg)): + curBg, curFg = newBg, newFg + elif (newBg != -1) and (newBg != curBg): + curBg = newBg + elif (newFg != -1) and (newFg != curFg): + curFg = newFg + inFileChar += len(m[0]) + else: + m = re.match('\x1b\[(\d+)C', inFileData[inFileChar:]) + if m: + for numRepeat in range(int(m[1])): + outMap[inCurRow].append([curFg, curBg, self._CellState.CS_NONE, " "]) + inFileChar += len(m[0]) + elif inFileData[inFileChar:inFileChar+2] == "\r\n": + inFileChar += 2; done = True; + elif inFileData[inFileChar] == "\r" \ + or inFileData[inFileChar] == "\n": + inFileChar += 1; done = True; + else: + outMap[inCurRow].append([curFg, curBg, self._CellState.CS_NONE, inFileData[inFileChar]]) + inFileChar += 1; rowChars += 1; + if done: + inMaxCols = max(inMaxCols, len(outMap[inCurRow])); inSize[1] += 1; + done = False; rowChars = 0; inCurRow += 1; outMap.append([]); + inSize[0] = inMaxCols + for numRow in range(inSize[1]): + for numCol in range(len(outMap[numRow]), inSize[0]): + outMap[numRow].append([curFg, curBg, self._CellState.CS_NONE, " "]) + self.inSize, self.outMap = inSize, outMap + # }}} # {{{ importIntoPanel(self): XXX def importIntoPanel(self): self.parentCanvas.onStoreUpdate(self.inSize, self.outMap) @@ -52,6 +124,81 @@ class CanvasImportStore(): for y in range(newCanvasSize[1])] self.parentCanvas.onStoreUpdate(newCanvasSize, newMap) # }}} + # {{{ importSauceFile(self, inPathName): XXX + def importSauceFile(self, inPathName): + with open(inPathName, "rb") as inFile: + self.inSize, self.outMap = None, None; inMaxCols, inSize, outMap = 0, [0, 0], [[]]; + inFileStat = os.stat(inPathName) + inFile.seek(inFileStat.st_size - 128, 0) + inFile.seek(5 + 2 + 35 + 20 + 20 + 8 + 4, 1) + if (inFile.read(1) == b'\x01') \ + and (inFile.read(1) == b'\x01'): + width, height = struct.unpack("H", inFile.read(2))[0], struct.unpack("H", inFile.read(2))[0] + inFile.seek(0, 0) + inFileData, row, rowChars = inFile.read(inFileStat.st_size - 128).decode("cp437"), "", 0 + inFileChar, inFileCharMax = 0, len(inFileData) + curBg, curFg, inCurRow = 1, 15, 0; curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; + while True: + if inFileChar >= inFileCharMax: + break + else: + m = re.match('\x1b\[((?:\d{1,3};?)+)m', inFileData[inFileChar:]) + if m: + newBg, newFg = -1, -1 + for ansiCode in m[1].split(";"): + ansiCode = int(ansiCode) + if ansiCode == 0: + curBgAnsi, curBoldAnsi, curFgAnsi = 30, False, 37; newBg, newFg = 1, 15; + elif ansiCode == 1: + curBoldAnsi, newFg = True, AnsiFgBoldToMiRCARTColours[curFgAnsi] + elif ansiCode == 2: + curBoldAnsi, newFg = False, AnsiFgToMiRCARTColours[curFgAnsi] + elif ansiCode == 7: + newBg, newFg = curFg, curBg; curBgAnsi, curFgAnsi = curFgAnsi, curBgAnsi; + elif ansiCode in AnsiBgToMiRCARTColours: + curBgAnsi, newBg = ansiCode, AnsiBgToMiRCARTColours[ansiCode] + elif ansiCode in AnsiFgToMiRCARTColours: + if curBoldAnsi: + newFg = AnsiFgBoldToMiRCARTColours[ansiCode] + else: + newFg = AnsiFgToMiRCARTColours[ansiCode] + curFgAnsi = ansiCode + elif ansiCode in AnsiFgBoldToMiRCARTColours: + curFgAnsi, newFg = ansiCode, AnsiFgBoldToMiRCARTColours[ansiCode] + if ((newBg != -1) and (newFg != -1)) \ + and ((newBg == curFg) and (newFg == curBg)): + curBg, curFg = newBg, newFg + elif ((newBg != -1) and (newFg != -1)) \ + and ((newBg != curBg) and (newFg != curFg)): + curBg, curFg = newBg, newFg + elif (newBg != -1) and (newBg != curBg): + curBg = newBg + elif (newFg != -1) and (newFg != curFg): + curFg = newFg + inFileChar += len(m[0]) + else: + m = re.match('\x1b\[(\d+)C', inFileData[inFileChar:]) + if m: + for numRepeat in range(int(m[1])): + outMap[inCurRow].append([curFg, curBg, self._CellState.CS_NONE, " "]) + inFileChar += len(m[0]) + elif inFileData[inFileChar:inFileChar+2] == "\r\n": + inFileChar += 2; rowChars = width; + elif inFileData[inFileChar] == "\r" \ + or inFileData[inFileChar] == "\n": + inFileChar += 1; rowChars = width; + else: + outMap[inCurRow].append([curFg, curBg, self._CellState.CS_NONE, inFileData[inFileChar]]) + inFileChar += 1; rowChars += 1; + if rowChars >= width: + inMaxCols = max(inMaxCols, len(outMap[inCurRow])); inSize[1] += 1; + rowChars = 0; inCurRow += 1; outMap.append([]); + inSize[0] = inMaxCols + for numRow in range(inSize[1]): + for numCol in range(len(outMap[numRow]), inSize[0]): + outMap[numRow].append([curFg, curBg, self._CellState.CS_NONE, " "]) + self.inSize, self.outMap = inSize, outMap + # }}} # {{{ importTextFile(self, pathName): XXX def importTextFile(self, pathName): return self.importTextFileBuffer(open(pathName, "r", encoding="utf-8-sig")) diff --git a/libcanvas/Colours.py b/libcanvas/Colours.py index 6f13eab..213e0b3 100644 --- a/libcanvas/Colours.py +++ b/libcanvas/Colours.py @@ -4,9 +4,107 @@ # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz # -# -# Colours: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline], -# +# {{{ AnsiBgToMiRCARTColours: XXX +AnsiBgToMiRCARTColours = { + 107: 0, # Bright White + 40: 1, # Black + 104: 2, # Blue + 42: 3, # Green + 101: 4, # Red + 41: 5, # Light Red + 45: 6, # Pink + 43: 7, # Yellow + 103: 8, # Light Yellow + 102: 9, # Light Green + 46: 10, # Cyan + 106: 11, # Light Cyan + 44: 12, # Light Blue + 105: 13, # Light Pink + 100: 14, # Grey + 47: 15, # Light Grey +}; +# }}} +# {{{ AnsiFgBoldToMiRCARTColours: XXX +AnsiFgBoldToMiRCARTColours = { + 97: 0, # Bright White + 30: 14, # Grey + 94: 12, # Light Blue + 32: 9, # Light Green + 91: 4, # Light Red + 31: 4, # Light Red + 35: 13, # Light Pink + 33: 8, # Light Yellow + 93: 8, # Light Yellow + 92: 9, # Light Green + 36: 11, # Light Cyan + 96: 11, # Light Cyan + 34: 12, # Light Blue + 95: 13, # Light Pink + 90: 14, # Grey + 37: 0, # Bright White +}; +# }}} +# {{{ AnsiFgToMiRCARTColours: XXX +AnsiFgToMiRCARTColours = { + 97: 0, # Bright White + 30: 1, # Black + 94: 2, # Blue + 32: 3, # Green + 91: 4, # Red + 31: 5, # Light Red + 35: 6, # Pink + 33: 7, # Yellow + 93: 8, # Light Yellow + 92: 9, # Light Green + 36: 10, # Cyan + 96: 11, # Light Cyan + 34: 12, # Light Blue + 95: 13, # Light Pink + 90: 14, # Grey + 37: 15, # Light Grey +}; +# }}} +# {{{ ColourMapBold: mIRC colour number to RGBA map given ^B (bold) +ColourMapBold = [ + [255, 255, 255], # Bright White + [85, 85, 85], # Black + [85, 85, 255], # Light Blue + [85, 255, 85], # Green + [255, 85, 85], # Red + [255, 85, 85], # Light Red + [255, 85, 255], # Pink + [255, 255, 85], # Yellow + [255, 255, 85], # Light Yellow + [85, 255, 85], # Light Green + [85, 255, 255], # Cyan + [85, 255, 255], # Light Cyan + [85, 85, 255], # Blue + [255, 85, 255], # Light Pink + [85, 85, 85], # Grey + [255, 255, 255], # Light Grey +] +# }}} +# {{{ ColourMapNormal: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline) +ColourMapNormal = [ + [255, 255, 255], # Bright White + [0, 0, 0], # Black + [0, 0, 187], # Light Blue + [0, 187, 0], # Green + [255, 85, 85], # Red + [187, 0, 0], # Light Red + [187, 0, 187], # Pink + [187, 187, 0], # Yellow + [255, 255, 85], # Light Yellow + [85, 255, 85], # Light Green + [0, 187, 187], # Cyan + [85, 255, 255], # Light Cyan + [85, 85, 255], # Blue + [255, 85, 255], # Light Pink + [85, 85, 85], # Grey + [187, 187, 187], # Light Grey +] +# }}} +# {{{ Colours: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline], Colours = [ [255, 255, 255, 255, "White"], [0, 0, 0, 255, "Black"], @@ -24,6 +122,27 @@ Colours = [ [255, 85, 255, 255, "Pink"], [85, 85, 85, 255, "Grey"], [187, 187, 187, 255, "Light Grey"], -] +]; +# }}} +# {{{ MiRCARTToAnsiColours: XXX +MiRCARTToAnsiColours = [ + 97, # Bright White + 30, # Black + 94, # Light Blue + 32, # Green + 91, # Red + 31, # Light Red + 35, # Pink + 33, # Yellow + 93, # Light Yellow + 92, # Light Green + 36, # Cyan + 96, # Light Cyan + 34, # Blue + 95, # Light Pink + 90, # Grey + 37, # Light Grey +]; +# }}} # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libgui/GuiCanvasInterface.py b/libgui/GuiCanvasInterface.py index a6775ff..10e16bc 100644 --- a/libgui/GuiCanvasInterface.py +++ b/libgui/GuiCanvasInterface.py @@ -115,9 +115,22 @@ class GuiCanvasInterface(): self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) return True # }}} + # {{{ canvasExportAsAnsi(self, event): XXX + def canvasExportAsAnsi(self, event): + with wx.FileDialog(self.parentFrame, "Save As...", os.getcwd(), "", "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog: + if dialog.ShowModal() == wx.ID_CANCEL: + return False + else: + outPathName = dialog.GetPath() + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + with open(outPathName, "w", encoding="utf-8") as outFile: + self.parentCanvas.canvasExportStore.exportAnsiFile(self.parentCanvas.canvasMap, self.parentCanvas.canvasSize, outFile) + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + return True + # }}} # {{{ canvasExportAsPng(self, event): XXX def canvasExportAsPng(self, event): - with wx.FileDialog(self.parentFrame, "Save As...", os.getcwd(), "", "*.png", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog: + with wx.FileDialog(self.parentFrame, "Save As...", os.getcwd(), "", "PNG (*.png)|*.png|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog: if dialog.ShowModal() == wx.ID_CANCEL: return False else: @@ -166,6 +179,29 @@ class GuiCanvasInterface(): wx.MessageBox("Failed to export to Pastebin: " + pasteResult, \ "Export to Pastebin", wx.OK|wx.ICON_EXCLAMATION) # }}} + # {{{ canvasImportAnsi(self, event): XXX + def canvasImportAnsi(self, event): + if self.canvasPathName != None: + saveChanges = self._dialogSaveChanges() + if saveChanges == wx.ID_CANCEL: + return + elif saveChanges == wx.ID_NO: + pass + elif saveChanges == wx.ID_YES: + self.canvasSave(event) + with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog: + if dialog.ShowModal() == wx.ID_CANCEL: + return False + else: + self.canvasPathName = dialog.GetPath() + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + self.parentCanvas.canvasImportStore.importAnsiFile(self.canvasPathName) + self.parentCanvas.canvasImportStore.importIntoPanel() + self.canvasPathName = "(Imported)" + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + self.parentFrame.onCanvasUpdate(pathName="(Imported)", undoLevel=-1) + return True + # }}} # {{{ canvasImportFromClipboard(self, event): XXX def canvasImportFromClipboard(self, event): rc = False @@ -193,6 +229,29 @@ class GuiCanvasInterface(): with wx.MessageDialog(self.parentCanvas, "Clipboard does not contain text data and/or cannot be opened", "", wx.ICON_QUESTION | wx.OK | wx.OK_DEFAULT) as dialog: dialog.ShowModal() # }}} + # {{{ canvasImportSauce(self, event): XXX + def canvasImportSauce(self, event): + if self.canvasPathName != None: + saveChanges = self._dialogSaveChanges() + if saveChanges == wx.ID_CANCEL: + return + elif saveChanges == wx.ID_NO: + pass + elif saveChanges == wx.ID_YES: + self.canvasSave(event) + with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "SAUCE files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog: + if dialog.ShowModal() == wx.ID_CANCEL: + return False + else: + self.canvasPathName = dialog.GetPath() + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + self.parentCanvas.canvasImportStore.importSauceFile(self.canvasPathName) + self.parentCanvas.canvasImportStore.importIntoPanel() + self.canvasPathName = "(Imported)" + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + self.parentFrame.onCanvasUpdate(pathName="(Imported)", undoLevel=-1) + return True + # }}} # {{{ canvasIncrBrushHeight(self, event): XXX def canvasIncrBrushHeight(self, event): self.parentCanvas.brushSize[1] += 1 @@ -253,12 +312,12 @@ class GuiCanvasInterface(): pass elif saveChanges == wx.ID_YES: self.canvasSave(event) - with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", \ - "*.txt", wx.FD_OPEN) as dialog: + with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog: if dialog.ShowModal() == wx.ID_CANCEL: return False else: self.canvasPathName = dialog.GetPath() + print(self.canvasPathName) self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) self.parentCanvas.canvasImportStore.importTextFile(self.canvasPathName) self.parentCanvas.canvasImportStore.importIntoPanel() @@ -282,7 +341,7 @@ class GuiCanvasInterface(): if self.canvasSaveAs(event) == False: return try: - with open(self.canvasPathName, "w") as outFile: + with open(self.canvasPathName, "w", encoding="utf-8") as outFile: self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) self.parentCanvas.canvasExportStore.exportTextFile( \ self.parentCanvas.canvasMap, \ @@ -294,8 +353,7 @@ class GuiCanvasInterface(): # }}} # {{{ canvasSaveAs(self, event): XXX def canvasSaveAs(self, event): - with wx.FileDialog(self.parentCanvas, "Save As", os.getcwd(), "", \ - "*.txt", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog: + with wx.FileDialog(self.parentCanvas, "Save As", os.getcwd(), "", "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog: if dialog.ShowModal() == wx.ID_CANCEL: return False else: diff --git a/libgui/GuiFrame.py b/libgui/GuiFrame.py index b66cfba..2031ec2 100644 --- a/libgui/GuiFrame.py +++ b/libgui/GuiFrame.py @@ -20,79 +20,82 @@ class GuiFrame(GuiGeneralFrame): # {{{ Commands (0x0100-0x0fff) # # MID_FILE - # Id Type Id Labels Icon bitmap Accelerator [Initial state] - CID_NEW = [0x0100, TID_COMMAND, "New", "&New", ["", wx.ART_NEW], [wx.ACCEL_CTRL, ord("N")], None, GuiCanvasInterface.canvasNew] - CID_OPEN = [0x0101, TID_COMMAND, "Open", "&Open", ["", wx.ART_FILE_OPEN], [wx.ACCEL_CTRL, ord("O")], None, GuiCanvasInterface.canvasOpen] - CID_SAVE = [0x0102, TID_COMMAND, "Save", "&Save", ["", wx.ART_FILE_SAVE], [wx.ACCEL_CTRL, ord("S")], None, GuiCanvasInterface.canvasSave] - CID_SAVEAS = [0x0103, TID_COMMAND, "Save As...", "Save &As...", ["", wx.ART_FILE_SAVE_AS], None, None, GuiCanvasInterface.canvasSaveAs] - CID_EXPORT_CLIPB = [0x0104, TID_COMMAND, "Export to clipboard", "&Export to clipboard", None, None, None, GuiCanvasInterface.canvasExportToClipboard] - CID_EXPORT_AS_PNG = [0x0105, TID_COMMAND, "Export as PNG...", "Export as PN&G...", None, None, None, GuiCanvasInterface.canvasExportAsPng] - CID_EXPORT_IMGUR = [0x0106, TID_COMMAND, "Export to Imgur...", "Export to I&mgur...", None, None, haveUrllib, GuiCanvasInterface.canvasExportImgur] - CID_EXPORT_PASTEBIN = [0x0107, TID_COMMAND, "Export to Pastebin...", "Export to Pasteb&in...", None, None, haveUrllib, GuiCanvasInterface.canvasExportPastebin] - CID_IMPORT_CLIPB = [0x0108, TID_COMMAND, "Import from clipboard", "&Import from clipboard", None, None, None, GuiCanvasInterface.canvasImportFromClipboard] - CID_EXIT = [0x0109, TID_COMMAND, "Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None, GuiCanvasInterface.canvasExit] + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_NEW = [0x0100, TID_COMMAND, "New", "&New", ["", wx.ART_NEW], [wx.ACCEL_CTRL, ord("N")], None, GuiCanvasInterface.canvasNew] + CID_OPEN = [0x0101, TID_COMMAND, "Open", "&Open", ["", wx.ART_FILE_OPEN], [wx.ACCEL_CTRL, ord("O")], None, GuiCanvasInterface.canvasOpen] + CID_SAVE = [0x0102, TID_COMMAND, "Save", "&Save", ["", wx.ART_FILE_SAVE], [wx.ACCEL_CTRL, ord("S")], None, GuiCanvasInterface.canvasSave] + CID_SAVEAS = [0x0103, TID_COMMAND, "Save As...", "Save &As...", ["", wx.ART_FILE_SAVE_AS], None, None, GuiCanvasInterface.canvasSaveAs] + CID_EXPORT_CLIPB = [0x0104, TID_COMMAND, "Export to clipboard", "&Export to clipboard", None, None, None, GuiCanvasInterface.canvasExportToClipboard] + CID_EXPORT_AS_ANSI = [0x0105, TID_COMMAND, "Export as ANSI...", "Export as ANSI...", None, None, None, GuiCanvasInterface.canvasExportAsAnsi] + CID_EXPORT_AS_PNG = [0x0106, TID_COMMAND, "Export as PNG...", "Export as PN&G...", None, None, None, GuiCanvasInterface.canvasExportAsPng] + CID_EXPORT_IMGUR = [0x0107, TID_COMMAND, "Export to Imgur...", "Export to I&mgur...", None, None, haveUrllib, GuiCanvasInterface.canvasExportImgur] + CID_EXPORT_PASTEBIN = [0x0108, TID_COMMAND, "Export to Pastebin...", "Export to Pasteb&in...", None, None, haveUrllib, GuiCanvasInterface.canvasExportPastebin] + CID_IMPORT_ANSI = [0x0109, TID_COMMAND, "Import ANSI...", "Import ANSI...", None, None, None, GuiCanvasInterface.canvasImportAnsi] + CID_IMPORT_CLIPB = [0x010a, TID_COMMAND, "Import from clipboard", "&Import from clipboard", None, None, None, GuiCanvasInterface.canvasImportFromClipboard] + CID_IMPORT_SAUCE = [0x010b, TID_COMMAND, "Import SAUCE...", "Import SAUCE...", None, None, None, GuiCanvasInterface.canvasImportSauce] + CID_EXIT = [0x010c, TID_COMMAND, "Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None, GuiCanvasInterface.canvasExit] # # MID_EDIT - # Id Type Id Labels Icon bitmap Accelerator [Initial state] - CID_UNDO = [0x0200, TID_COMMAND, "Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False, GuiCanvasInterface.canvasUndo] - CID_REDO = [0x0201, TID_COMMAND, "Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False, GuiCanvasInterface.canvasRedo] - CID_CUT = [0x0202, TID_COMMAND, "Cut", "Cu&t", ["", wx.ART_CUT], None, False, GuiCanvasInterface.canvasCut] - CID_COPY = [0x0203, TID_COMMAND, "Copy", "&Copy", ["", wx.ART_COPY], None, False, GuiCanvasInterface.canvasCopy] - CID_PASTE = [0x0204, TID_COMMAND, "Paste", "&Paste", ["", wx.ART_PASTE], None, False, GuiCanvasInterface.canvasPaste] - CID_DELETE = [0x0205, TID_COMMAND, "Delete", "De&lete", ["", wx.ART_DELETE], None, False, GuiCanvasInterface.canvasDelete] - CID_INCRW_CANVAS = [0x0206, TID_COMMAND, "Increase canvas width", "Increase canvas width", ["toolIncrCanvasW.png"], None, None, GuiCanvasInterface.canvasIncrCanvasWidth] - CID_DECRW_CANVAS = [0x0207, TID_COMMAND, "Decrease canvas width", "Decrease canvas width", ["toolDecrCanvasW.png"], None, None, GuiCanvasInterface.canvasDecrCanvasWidth] - CID_INCRH_CANVAS = [0x0208, TID_COMMAND, "Increase canvas height", "Increase canvas height", ["toolIncrCanvasH.png"], None, None, GuiCanvasInterface.canvasIncrCanvasHeight] - CID_DECRH_CANVAS = [0x0209, TID_COMMAND, "Decrease canvas height", "Decrease canvas height", ["toolDecrCanvasH.png"], None, None, GuiCanvasInterface.canvasDecrCanvasHeight] - CID_INCRHW_CANVAS = [0x020a, TID_COMMAND, "Increase canvas size", "Increase canvas size", ["toolIncrCanvasHW.png"], None, None, GuiCanvasInterface.canvasIncrCanvasHeightWidth] - CID_DECRHW_CANVAS = [0x020b, TID_COMMAND, "Decrease canvas size", "Decrease canvas size", ["toolDecrCanvasHW.png"], None, None, GuiCanvasInterface.canvasDecrCanvasHeightWidth] - CID_INCRW_BRUSH = [0x020c, TID_COMMAND, "Increase brush width", "Increase brush width", ["toolIncrBrushW.png"], None, None, GuiCanvasInterface.canvasIncrBrushWidth] - CID_DECRW_BRUSH = [0x020d, TID_COMMAND, "Decrease brush width", "Decrease brush width", ["toolDecrBrushW.png"], None, None, GuiCanvasInterface.canvasDecrBrushWidth] - CID_INCRH_BRUSH = [0x020e, TID_COMMAND, "Increase brush height", "Increase brush height", ["toolIncrBrushH.png"], None, None, GuiCanvasInterface.canvasIncrBrushHeight] - CID_DECRH_BRUSH = [0x020f, TID_COMMAND, "Decrease brush height", "Decrease brush height", ["toolDecrBrushH.png"], None, None, GuiCanvasInterface.canvasDecrBrushHeight] - CID_INCRHW_BRUSH = [0x0210, TID_COMMAND, "Increase brush size", "Increase brush size", ["toolIncrBrushHW.png"], None, None, GuiCanvasInterface.canvasIncrBrushHeightWidth] - CID_DECRHW_BRUSH = [0x0211, TID_COMMAND, "Decrease brush size", "Decrease brush size", ["toolDecrBrushHW.png"], None, None, GuiCanvasInterface.canvasDecrBrushHeightWidth] - CID_SOLID_BRUSH = [0x0212, TID_SELECT, "Solid brush", "Solid brush", None, None, True, GuiCanvasInterface.canvasBrushSolid] + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_UNDO = [0x0200, TID_COMMAND, "Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False, GuiCanvasInterface.canvasUndo] + CID_REDO = [0x0201, TID_COMMAND, "Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False, GuiCanvasInterface.canvasRedo] + CID_CUT = [0x0202, TID_COMMAND, "Cut", "Cu&t", ["", wx.ART_CUT], None, False, GuiCanvasInterface.canvasCut] + CID_COPY = [0x0203, TID_COMMAND, "Copy", "&Copy", ["", wx.ART_COPY], None, False, GuiCanvasInterface.canvasCopy] + CID_PASTE = [0x0204, TID_COMMAND, "Paste", "&Paste", ["", wx.ART_PASTE], None, False, GuiCanvasInterface.canvasPaste] + CID_DELETE = [0x0205, TID_COMMAND, "Delete", "De&lete", ["", wx.ART_DELETE], None, False, GuiCanvasInterface.canvasDelete] + CID_INCRW_CANVAS = [0x0206, TID_COMMAND, "Increase canvas width", "Increase canvas width", ["toolIncrCanvasW.png"], None, None, GuiCanvasInterface.canvasIncrCanvasWidth] + CID_DECRW_CANVAS = [0x0207, TID_COMMAND, "Decrease canvas width", "Decrease canvas width", ["toolDecrCanvasW.png"], None, None, GuiCanvasInterface.canvasDecrCanvasWidth] + CID_INCRH_CANVAS = [0x0208, TID_COMMAND, "Increase canvas height", "Increase canvas height", ["toolIncrCanvasH.png"], None, None, GuiCanvasInterface.canvasIncrCanvasHeight] + CID_DECRH_CANVAS = [0x0209, TID_COMMAND, "Decrease canvas height", "Decrease canvas height", ["toolDecrCanvasH.png"], None, None, GuiCanvasInterface.canvasDecrCanvasHeight] + CID_INCRHW_CANVAS = [0x020a, TID_COMMAND, "Increase canvas size", "Increase canvas size", ["toolIncrCanvasHW.png"], None, None, GuiCanvasInterface.canvasIncrCanvasHeightWidth] + CID_DECRHW_CANVAS = [0x020b, TID_COMMAND, "Decrease canvas size", "Decrease canvas size", ["toolDecrCanvasHW.png"], None, None, GuiCanvasInterface.canvasDecrCanvasHeightWidth] + CID_INCRW_BRUSH = [0x020c, TID_COMMAND, "Increase brush width", "Increase brush width", ["toolIncrBrushW.png"], None, None, GuiCanvasInterface.canvasIncrBrushWidth] + CID_DECRW_BRUSH = [0x020d, TID_COMMAND, "Decrease brush width", "Decrease brush width", ["toolDecrBrushW.png"], None, None, GuiCanvasInterface.canvasDecrBrushWidth] + CID_INCRH_BRUSH = [0x020e, TID_COMMAND, "Increase brush height", "Increase brush height", ["toolIncrBrushH.png"], None, None, GuiCanvasInterface.canvasIncrBrushHeight] + CID_DECRH_BRUSH = [0x020f, TID_COMMAND, "Decrease brush height", "Decrease brush height", ["toolDecrBrushH.png"], None, None, GuiCanvasInterface.canvasDecrBrushHeight] + CID_INCRHW_BRUSH = [0x0210, TID_COMMAND, "Increase brush size", "Increase brush size", ["toolIncrBrushHW.png"], None, None, GuiCanvasInterface.canvasIncrBrushHeightWidth] + CID_DECRHW_BRUSH = [0x0211, TID_COMMAND, "Decrease brush size", "Decrease brush size", ["toolDecrBrushHW.png"], None, None, GuiCanvasInterface.canvasDecrBrushHeightWidth] + CID_SOLID_BRUSH = [0x0212, TID_SELECT, "Solid brush", "Solid brush", None, None, True, GuiCanvasInterface.canvasBrushSolid] # # MID_TOOLS - # Id Type Id Labels Icon bitmap Accelerator [Initial state] - CID_RECT = [0x0300, TID_SELECT, "Rectangle", "&Rectangle", ["toolRect.png"], [wx.ACCEL_CTRL, ord("R")], True, GuiCanvasInterface.canvasToolRect] - CID_CIRCLE = [0x0301, TID_SELECT, "Circle", "&Circle", ["toolCircle.png"], [wx.ACCEL_CTRL, ord("C")], False, GuiCanvasInterface.canvasToolCircle] - CID_FILL = [0x0302, TID_SELECT, "Fill", "&Fill", ["toolFill.png"], [wx.ACCEL_CTRL, ord("F")], False, GuiCanvasInterface.canvasToolFill] - CID_LINE = [0x0303, TID_SELECT, "Line", "&Line", ["toolLine.png"], [wx.ACCEL_CTRL, ord("L")], False, GuiCanvasInterface.canvasToolLine] - CID_TEXT = [0x0304, TID_SELECT, "Text", "&Text", ["toolText.png"], [wx.ACCEL_CTRL, ord("T")], False, GuiCanvasInterface.canvasToolText] - CID_CLONE_SELECT = [0x0305, TID_SELECT, "Clone", "Cl&one", ["toolClone.png"], [wx.ACCEL_CTRL, ord("E")], False, GuiCanvasInterface.canvasToolSelectClone] - CID_MOVE_SELECT = [0x0306, TID_SELECT, "Move", "&Move", ["toolMove.png"], [wx.ACCEL_CTRL, ord("M")], False, GuiCanvasInterface.canvasToolSelectMove] + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_RECT = [0x0300, TID_SELECT, "Rectangle", "&Rectangle", ["toolRect.png"], [wx.ACCEL_CTRL, ord("R")], True, GuiCanvasInterface.canvasToolRect] + CID_CIRCLE = [0x0301, TID_SELECT, "Circle", "&Circle", ["toolCircle.png"], [wx.ACCEL_CTRL, ord("C")], False, GuiCanvasInterface.canvasToolCircle] + CID_FILL = [0x0302, TID_SELECT, "Fill", "&Fill", ["toolFill.png"], [wx.ACCEL_CTRL, ord("F")], False, GuiCanvasInterface.canvasToolFill] + CID_LINE = [0x0303, TID_SELECT, "Line", "&Line", ["toolLine.png"], [wx.ACCEL_CTRL, ord("L")], False, GuiCanvasInterface.canvasToolLine] + CID_TEXT = [0x0304, TID_SELECT, "Text", "&Text", ["toolText.png"], [wx.ACCEL_CTRL, ord("T")], False, GuiCanvasInterface.canvasToolText] + CID_CLONE_SELECT = [0x0305, TID_SELECT, "Clone", "Cl&one", ["toolClone.png"], [wx.ACCEL_CTRL, ord("E")], False, GuiCanvasInterface.canvasToolSelectClone] + CID_MOVE_SELECT = [0x0306, TID_SELECT, "Move", "&Move", ["toolMove.png"], [wx.ACCEL_CTRL, ord("M")], False, GuiCanvasInterface.canvasToolSelectMove] # # BID_TOOLBAR - # Id Type Id Labels Icon bitmap Accelerator [Initial state] - CID_COLOUR00 = [0x0400, TID_SELECT, "Colour #00", "Colour #00", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR01 = [0x0401, TID_SELECT, "Colour #01", "Colour #01", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR02 = [0x0402, TID_SELECT, "Colour #02", "Colour #02", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR03 = [0x0403, TID_SELECT, "Colour #03", "Colour #03", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR04 = [0x0404, TID_SELECT, "Colour #04", "Colour #04", None, None, True, GuiCanvasInterface.canvasColour] - CID_COLOUR05 = [0x0405, TID_SELECT, "Colour #05", "Colour #05", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR06 = [0x0406, TID_SELECT, "Colour #06", "Colour #06", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR07 = [0x0407, TID_SELECT, "Colour #07", "Colour #07", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR08 = [0x0408, TID_SELECT, "Colour #08", "Colour #08", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR09 = [0x0409, TID_SELECT, "Colour #09", "Colour #09", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR10 = [0x040a, TID_SELECT, "Colour #10", "Colour #10", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR11 = [0x040b, TID_SELECT, "Colour #11", "Colour #11", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR12 = [0x040c, TID_SELECT, "Colour #12", "Colour #12", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR13 = [0x040d, TID_SELECT, "Colour #13", "Colour #13", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR14 = [0x040e, TID_SELECT, "Colour #14", "Colour #14", None, None, False, GuiCanvasInterface.canvasColour] - CID_COLOUR15 = [0x040f, TID_SELECT, "Colour #15", "Colour #15", None, None, False, GuiCanvasInterface.canvasColour] + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_COLOUR00 = [0x0400, TID_SELECT, "Colour #00", "Colour #00", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR01 = [0x0401, TID_SELECT, "Colour #01", "Colour #01", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR02 = [0x0402, TID_SELECT, "Colour #02", "Colour #02", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR03 = [0x0403, TID_SELECT, "Colour #03", "Colour #03", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR04 = [0x0404, TID_SELECT, "Colour #04", "Colour #04", None, None, True, GuiCanvasInterface.canvasColour] + CID_COLOUR05 = [0x0405, TID_SELECT, "Colour #05", "Colour #05", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR06 = [0x0406, TID_SELECT, "Colour #06", "Colour #06", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR07 = [0x0407, TID_SELECT, "Colour #07", "Colour #07", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR08 = [0x0408, TID_SELECT, "Colour #08", "Colour #08", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR09 = [0x0409, TID_SELECT, "Colour #09", "Colour #09", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR10 = [0x040a, TID_SELECT, "Colour #10", "Colour #10", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR11 = [0x040b, TID_SELECT, "Colour #11", "Colour #11", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR12 = [0x040c, TID_SELECT, "Colour #12", "Colour #12", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR13 = [0x040d, TID_SELECT, "Colour #13", "Colour #13", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR14 = [0x040e, TID_SELECT, "Colour #14", "Colour #14", None, None, False, GuiCanvasInterface.canvasColour] + CID_COLOUR15 = [0x040f, TID_SELECT, "Colour #15", "Colour #15", None, None, False, GuiCanvasInterface.canvasColour] # # MID_ABOUT - # Id Type Id Labels Icon bitmap Accelerator [Initial state] - CID_ABOUT = [0x0500, TID_COMMAND, "About", "&About", None, None, True, GuiCanvasInterface.canvasAbout] + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_ABOUT = [0x0500, TID_COMMAND, "About", "&About", None, None, True, GuiCanvasInterface.canvasAbout] # }}} # {{{ Menus (0x1100-0x1fff) - MID_FILE = (0x1100, TID_MENU, "File", "&File", (CID_NEW, CID_OPEN, CID_SAVE, CID_SAVEAS, NID_MENU_SEP, CID_EXPORT_CLIPB, CID_EXPORT_AS_PNG, CID_EXPORT_IMGUR, CID_EXPORT_PASTEBIN, NID_MENU_SEP, CID_IMPORT_CLIPB, NID_MENU_SEP, CID_EXIT)) + MID_FILE = (0x1100, TID_MENU, "File", "&File", (CID_NEW, CID_OPEN, CID_SAVE, CID_SAVEAS, NID_MENU_SEP, CID_EXPORT_AS_ANSI, CID_EXPORT_CLIPB, CID_EXPORT_IMGUR, CID_EXPORT_PASTEBIN, CID_EXPORT_AS_PNG, NID_MENU_SEP, CID_IMPORT_ANSI, CID_IMPORT_CLIPB, CID_IMPORT_SAUCE, NID_MENU_SEP, CID_EXIT)) MID_EDIT = (0x1101, TID_MENU, "Edit", "&Edit", (CID_UNDO, CID_REDO, NID_MENU_SEP, CID_CUT, CID_COPY, CID_PASTE, CID_DELETE, NID_MENU_SEP, CID_INCRW_CANVAS, CID_DECRW_CANVAS, CID_INCRH_CANVAS, CID_DECRH_CANVAS, NID_MENU_SEP, CID_INCRHW_CANVAS, CID_DECRHW_CANVAS, NID_MENU_SEP, CID_INCRW_BRUSH, CID_DECRW_BRUSH, CID_INCRH_BRUSH, CID_DECRH_BRUSH, NID_MENU_SEP, CID_INCRHW_BRUSH, CID_DECRHW_BRUSH, NID_MENU_SEP, CID_SOLID_BRUSH)) MID_TOOLS = (0x1102, TID_MENU, "Tools", "&Tools", (CID_RECT, CID_CIRCLE, CID_FILL, CID_LINE, CID_TEXT, CID_CLONE_SELECT, CID_MOVE_SELECT)) MID_HELP = (0x1103, TID_MENU, "Help", "&Help", (CID_ABOUT,))