diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04ad9ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +MiRCARTImgurApiKey.py +__pycache__/ +*.sw[op] diff --git a/DejaVuSansMono.ttf b/DejaVuSansMono.ttf new file mode 100644 index 0000000..f578602 Binary files /dev/null and b/DejaVuSansMono.ttf differ diff --git a/IrcClient.py b/IrcClient.py new file mode 100644 index 0000000..ba1d2ec --- /dev/null +++ b/IrcClient.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# mirc2png -- convert ASCII w/ mIRC control codes to monospaced PNG (for EFnet #MiRCART) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from itertools import chain +import select, socket, time + +class IrcClient: + """Non-blocking abstraction over the IRC protocol""" + serverHname = serverPort = None; + clientNick = clientIdent = clientGecos = None; + clientSocket = clientSocketFile = None; + clientNextTimeout = None; clientQueue = None; + + # {{{ close(self): Close connection to server + def close(self): + if self.clientSocket != None: + self.clientSocket.close() + self.clientSocket = self.clientSocketFile = None; + # }}} + # {{{ connect(self, preferFamily=socket.AF_INET, timeout=None): Connect to server and register w/ optional timeout + def connect(self, preferFamily=socket.AF_INET, timeout=None): + gaiInfo = socket.getaddrinfo(self.serverHname, self.serverPort, + preferFamily, socket.SOCK_STREAM, socket.IPPROTO_TCP) + self.clientSocket = socket.socket(*gaiInfo[0][:3]) + self.clientSocket.setblocking(0) + try: + self.clientSocket.connect(gaiInfo[0][4]) + except BlockingIOError: + pass + if timeout: + readySet = select.select([], [self.clientSocket.fileno()], [], timeout) + if len(readySet[1]) == 0: + self.close(); return False; + else: + select.select([], [self.clientSocket.fileno()], []) + self.clientSocketFile = self.clientSocket.makefile(encoding="utf-8", errors="replace") + self.clientQueue = [] + self.queue("NICK", self.clientNick) + self.queue("USER", self.clientIdent, "0", "0", self.clientGecos) + return True + # }}} + # {{{ readline(self): Read and parse single line from server into canonicalised list, honouring timers + def readline(self): + if self.clientNextTimeout: + timeNow = time.time() + if self.clientNextTimeout <= timeNow: + return "" + else: + readySet = select.select([self.clientSocket.fileno()], [], [], self.clientNextTimeout - timeNow) + else: + readySet = select.select([self.clientSocket.fileno()], [], []) + msg = self.clientSocketFile.readline() + if len(msg): + msg = msg.rstrip("\r\n") + else: + if len(readySet[0]) == 0: + return "" + else: + return None + msg = msg.split(" :", 1) + if len(msg) == 1: + msg = list(chain.from_iterable(m.split(" ") for m in msg)) + elif len(msg) == 2: + msg = msg[0].split(" ") + [msg[1]] + if msg[0][0] == ':': + msg = [msg[0][1:]] + msg[1:] + else: + msg = [""] + msg[0:] + return msg + # }}} + # {{{ queue(self, *args): Parse and queue single line to server from list + def queue(self, *args): + msg = ""; argNumMax = len(args); + for argNum in range(argNumMax): + if argNum == (argNumMax - 1): + msg += ":" + args[argNum] + else: + msg += args[argNum] + " " + self.clientQueue.append((msg + "\r\n").encode()) + # }}} + # {{{ unqueue(self): Send all queued lines to server, honouring timers + def unqueue(self): + while self.clientQueue: + msg = self.clientQueue[0]; msgLen = len(msg); msgBytesSent = 0; + while msgBytesSent < msgLen: + if self.clientNextTimeout: + timeNow = time.time() + if self.clientNextTimeout <= timeNow: + self.clientQueue[0] = msg; return; + else: + readySet = select.select([], [self.clientSocket.fileno()], [], self.clientNextTimeout - timeNow) + if len(readySet[1]) == 0: + self.clientQueue[0] = msg; return; + else: + readySet = select.select([], [self.clientSocket.fileno()], []) + msgBytesSent = self.clientSocket.send(msg) + msg = msg[msgBytesSent:]; msgLen -= msgBytesSent; + del self.clientQueue[0] + # }}} + + # + # __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos): initialisation method + def __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos): + self.serverHname = serverHname; self.serverPort = serverPort; + self.clientNick = clientNick; self.clientIdent = clientIdent; self.clientGecos = clientGecos; + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/IrcMiRCARTBot.py b/IrcMiRCARTBot.py new file mode 100755 index 0000000..1c1f7e3 --- /dev/null +++ b/IrcMiRCARTBot.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# +# IrcMiRCARTBot.py -- IRC<->MiRC2png bot (for EFnet #MiRCART) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from getopt import getopt, GetoptError +import base64 +import os, socket, sys, time +import json +import IrcClient +import requests, urllib.request +from MiRCARTCanvasImportStore import MiRCARTCanvasImportStore +from MiRCARTImgurApiKey import MiRCARTImgurApiKey +from MiRCARTToPngFile import MiRCARTToPngFile + +class IrcMiRCARTBot(IrcClient.IrcClient): + """IRC<->MiRC2png bot""" + clientChannelLastMessage = clientChannelOps = clientChannel = None + clientChannelRejoin = None + imgurApiKey = MiRCARTImgurApiKey.imgurApiKey + + # {{{ ContentTooLargeException(Exception): Raised by _urlretrieveReportHook() given download size > 1 MB + class ContentTooLargeException(Exception): + pass + # }}} + # {{{ _dispatch001(self, message): Dispatch single 001 (RPL_WELCOME) + def _dispatch001(self, message): + self._log("Registered on {}:{} as {}, {}, {}.".format(self.serverHname, self.serverPort, self.clientNick, self.clientIdent, self.clientGecos)) + self._log("Attempting to join {} on {}:{}...".format(self.clientChannel, self.serverHname, self.serverPort)) + self.queue("JOIN", self.clientChannel) + # }}} + # {{{ _dispatch353(self, message): Dispatch single 353 (RPL_NAMREPLY) + def _dispatch353(self, message): + if message[4].lower() == self.clientChannel.lower(): + for channelNickSpec in message[5].split(" "): + if len(channelNickSpec) \ + and channelNickSpec[0] == "@" \ + and len(channelNickSpec[1:]): + self.clientChannelOps.append(channelNickSpec[1:].lower()) + self._log("Authorising {} on {}".format(channelNickSpec[1:].lower(), message[4].lower())) + # }}} + # {{{ _dispatchJoin(self, message): Dispatch single JOIN message from server + def _dispatchJoin(self, message): + self._log("Joined {} on {}:{}.".format(message[2].lower(), self.serverHname, self.serverPort)) + self.clientNextTimeout = None; self.clientChannelRejoin = False; + # }}} + # {{{ _dispatchKick(self, message): Dispatch single KICK message from server + def _dispatchKick(self, message): + if message[2].lower() == self.clientChannel.lower() \ + and message[3].lower() == self.clientNick.lower(): + self._log("Kicked from {} by {}, rejoining in 15 seconds".format(message[2].lower(), message[0])) + self.clientNextTimeout = time.time() + 15; self.clientChannelRejoin = True; + # }}} + # {{{ _dispatchMode(self, message): Dispatch single MODE message from server + def _dispatchMode(self, message): + if message[2].lower() == self.clientChannel.lower(): + channelModeType = "+"; channelModeArg = 4; + channelAuthAdd = ""; channelAuthDel = ""; + for channelModeChar in message[3]: + if channelModeChar[0] == "-": + channelModeType = "-" + elif channelModeChar[0] == "+": + channelModeType = "+" + elif channelModeChar[0].isalpha(): + if channelModeChar[0] == "o": + if channelModeType == "+": + channelAuthAdd = message[channelModeArg]; channelAuthDel = ""; + elif channelModeType == "-": + channelAuthAdd = ""; channelAuthDel = message[channelModeArg]; + channelModeArg += 1 + if len(channelAuthAdd) \ + and channelAuthAdd not in self.clientChannelOps: + channelAuthAdd = channelAuthAdd.lower() + self._log("Authorising {} on {}".format(channelAuthAdd, message[2].lower())) + self.clientChannelOps.append(channelAuthAdd) + elif len(channelAuthDel) \ + and channelAuthDel in self.clientChannelOps: + channelAuthDel = channelAuthDel.lower() + self._log("Deauthorising {} on {}".format(channelAuthDel, message[2].lower())) + self.clientChannelOps.remove(channelAuthDel) + # }}} + # {{{ _dispatchNone(self): Dispatch None message from server + def _dispatchNone(self): + self._log("Disconnected from {}:{}.".format(self.serverHname, self.serverPort)) + self.close() + # }}} + # {{{ _dispatchPing(self, message): Dispatch single PING message from server + def _dispatchPing(self, message): + self.queue("PONG", message[2]) + # }}} + # {{{ _dispatchPrivmsg(self, message): Dispatch single PRIVMSG message from server + def _dispatchPrivmsg(self, message): + if message[2].lower() == self.clientChannel.lower() \ + and message[3].startswith("!pngbot "): + if (int(time.time()) - self.clientLastMessage) < 5: + self._log("Ignoring request on {} from {} due to rate limit: {}".format(message[2].lower(), message[0], message[3])) + return + elif message[0].split("!")[0].lower() not in self.clientChannelOps: + self._log("Ignoring request on {} from {} due to lack of authorisation: {}".format(message[2].lower(), message[0], message[3])) + return + else: + self._log("Processing request on {} from {}: {}".format(message[2].lower(), message[0], message[3])) + asciiUrl = message[3].split(" ")[1] + asciiTmpFilePath = "tmp.txt"; imgTmpFilePath = "tmp.png"; + if os.path.isfile(asciiTmpFilePath): + os.remove(asciiTmpFilePath) + if os.path.isfile(imgTmpFilePath): + os.remove(imgTmpFilePath) + try: + urllib.request.urlretrieve(asciiUrl, asciiTmpFilePath, IrcMiRCARTBot._urlretrieveReportHook) + except IrcMiRCARTBot.ContentTooLargeException: + self._log("Download size exceeds quota of 1 MB!") + self.queue("PRIVMSG", message[2], "4/!\\ Download size exceeds quota of 1 MB!") + return + except urllib.error.HTTPError as err: + self._log("Download failed with HTTP status code {}".format(err.code)) + self.queue("PRIVMSG", message[2], "4/!\\ Download failed with HTTP status code {}!".format(err.code)) + return + except urllib.error.URLError as err: + self._log("Invalid URL specified!") + self.queue("PRIVMSG", message[2], "4/!\\ Invalid URL specified!") + return + except ValueError as err: + self._log("Unknown URL type specified!") + self.queue("PRIVMSG", message[2], "4/!\\ Unknown URL type specified!") + return + + canvasStore = MiRCARTCanvasImportStore(inFile=asciiTmpFilePath) + numRowCols = 0 + for numRow in range(len(canvasStore.outMap)): + numRowCols = max(numRowCols, len(canvasStore.outMap[numRow])) + for numRow in range(len(canvasStore.outMap)): + if len(canvasStore.outMap[numRow]) != numRowCols: + for numColOff in range(numRowCols - len(canvasStore.outMap[numRow])): + canvasStore.outMap[numRow].append([1, 1, 0, " "]) + canvasStore.outMap[numRow].insert(0, [1, 1, 0, " "]) + canvasStore.outMap[numRow].append([1, 1, 0, " "]) + canvasStore.outMap.insert(0, [[1, 1, 0, " "]] * len(canvasStore.outMap[0])) + canvasStore.outMap.append([[1, 1, 0, " "]] * len(canvasStore.outMap[0])) + MiRCARTToPngFile(canvasStore.outMap, "DejaVuSansMono.ttf", 11).export(imgTmpFilePath) + imgurResponse = self._uploadToImgur(imgTmpFilePath, "MiRCART image", "MiRCART image", self.imgurApiKey) + if imgurResponse[0] == 200: + self._log("Uploaded as: {}".format(imgurResponse[1])) + self.queue("PRIVMSG", message[2], "8/!\\ Uploaded as: {}".format(imgurResponse[1])) + self.clientLastMessage = int(time.time()) + else: + self._log("Upload failed with HTTP status code {}".format(imgurResponse[0])) + self._log("Message from website: {}".format(imgurResponse[1])) + self.queue("PRIVMSG", message[2], "4/!\\ Upload failed with HTTP status code {}!".format(imgurResponse[0])) + self.queue("PRIVMSG", message[2], "4/!\\ Message from website: {}".format(imgurResponse[1])) + if os.path.isfile(asciiTmpFilePath): + os.remove(asciiTmpFilePath) + if os.path.isfile(imgTmpFilePath): + os.remove(imgTmpFilePath) + # }}} + # {{{ _dispatchTimer(self): Dispatch single client timer expiration + def _dispatchTimer(self): + if self.clientChannelRejoin: + self._log("Attempting to join {} on {}:{}...".format(self.clientChannel, self.serverHname, self.serverPort)) + self.queue("JOIN", self.clientChannel) + self.clientNextTimeout = time.time() + 15; self.clientChannelRejoin = True; + # }}} + # {{{ _log(self, msg): Log single message to stdout w/ timestamp + def _log(self, msg): + print(time.strftime("%Y/%m/%d %H:%M:%S") + " " + msg) + # }}} + # {{{ _uploadToImgur(self, imgFilePath, imgName, imgTitle, apiKey): Upload single file to Imgur + def _uploadToImgur(self, imgFilePath, imgName, imgTitle, apiKey): + with open(imgFilePath, "rb") as requestImage: + requestImageData = requestImage.read() + requestData = { \ + "image": base64.b64encode(requestImageData), \ + "key": apiKey, \ + "name": imgName, \ + "title": imgTitle, \ + "type": "base64"} + requestHeaders = { \ + "Authorization": "Client-ID " + apiKey} + responseHttp = requests.post("https://api.imgur.com/3/upload.json", data=requestData, headers=requestHeaders) + responseDict = json.loads(responseHttp.text) + if responseHttp.status_code == 200: + return [200, responseDict.get("data").get("link")] + else: + return [responseHttp.status_code, responseHttp.text] + # }}} + # {{{ _urlretrieveReportHook(count, blockSize, totalSize): Limit downloads to 1 MB + def _urlretrieveReportHook(count, blockSize, totalSize): + if (totalSize > pow(2,20)): + raise IrcMiRCARTBot.ContentTooLargeException + # }}} + # {{{ connect(self, preferFamily=0, timeout=None): Connect to server and (re)initialise w/ optional timeout + def connect(self, preferFamily=0, timeout=None): + self._log("Connecting to {}:{}...".format(self.serverHname, self.serverPort)) + if super().connect(preferFamily=preferFamily, timeout=timeout): + self._log("Connected to {}:{}.".format(self.serverHname, self.serverPort)) + self._log("Registering on {}:{} as {}, {}, {}...".format(self.serverHname, self.serverPort, self.clientNick, self.clientIdent, self.clientGecos)) + self.clientLastMessage = 0; self.clientChannelOps = []; + self.clientChannelRejoin = False + return True + else: + return False + # }}} + # {{{ dispatch(self): Read, parse, and dispatch single line from server + def dispatch(self): + while True: + if self.clientNextTimeout: + timeNow = time.time() + if self.clientNextTimeout <= timeNow: + self._dispatchTimer() + self.unqueue() + serverMessage = self.readline() + if serverMessage == None: + self._dispatchNone(); break; + elif serverMessage == "": + continue + elif serverMessage[1] == "001": + self._dispatch001(serverMessage) + elif serverMessage[1] == "353": + self._dispatch353(serverMessage) + elif serverMessage[1] == "JOIN": + self._dispatchJoin(serverMessage) + elif serverMessage[1] == "KICK": + self._dispatchKick(serverMessage) + elif serverMessage[1] == "MODE": + self._dispatchMode(serverMessage) + elif serverMessage[1] == "PING": + self._dispatchPing(serverMessage) + elif serverMessage[1] == "PRIVMSG": + self._dispatchPrivmsg(serverMessage) + # }}} + + # + # __init__(self, serverHname, serverPort="6667", clientNick="pngbot", clientIdent="pngbot", clientGecos="pngbot", clientChannel="#MiRCART"): initialisation method + def __init__(self, serverHname, serverPort="6667", clientNick="pngbot", clientIdent="pngbot", clientGecos="pngbot", clientChannel="#MiRCART"): + super().__init__(serverHname, serverPort, clientNick, clientIdent, clientGecos) + self.clientChannel = clientChannel + +# +# Entry point +def main(optdict, *argv): + _IrcMiRCARTBot = IrcMiRCARTBot(*argv) + while True: + if "-4" in optdict: + preferFamily = socket.AF_INET + elif "-6" in optdict: + preferFamily = socket.AF_INET6 + else: + preferFamily = 0 + if _IrcMiRCARTBot.connect(preferFamily=preferFamily, timeout=15): + _IrcMiRCARTBot.dispatch() + _IrcMiRCARTBot.close() + time.sleep(15) + +if __name__ == "__main__": + optlist, argv = getopt(sys.argv[1:], "46") + optdict = dict(optlist) + if len(argv) < 1 or len(argv) > 4: + print("usage: {} [-4|-6] " \ + " " \ + "[] " \ + "[] " \ + "[] " \ + "[] " \ + "[] ".format(sys.argv[0]), file=sys.stderr) + else: + main(optdict, *argv) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCART.png b/MiRCART.png new file mode 100644 index 0000000..fff39a2 Binary files /dev/null and b/MiRCART.png differ diff --git a/MiRCART.py b/MiRCART.py new file mode 100755 index 0000000..d7bd121 --- /dev/null +++ b/MiRCART.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# MiRCART.py -- mIRC art editor for Windows & Linux +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTFrame import MiRCARTFrame +import sys, wx + +# +# Entry point +def main(*argv): + wxApp = wx.App(False) + appFrame = MiRCARTFrame(None) + if len(argv) > 1 \ + and len(argv[1]) > 0: + appFrame.panelCanvas.canvasInterface.canvasPathName = argv[1] + appFrame.panelCanvas.canvasImportStore.importTextFile(argv[1]) + appFrame.panelCanvas.canvasImportStore.importIntoPanel() + appFrame.onCanvasUpdate(pathName=argv[1], undoLevel=-1) + wxApp.MainLoop() +if __name__ == "__main__": + main(*sys.argv) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanonicalise.py b/MiRCARTCanonicalise.py new file mode 100755 index 0000000..afda9b4 --- /dev/null +++ b/MiRCARTCanonicalise.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanonicalise.py -- canonicalise mIRC art {from,to} file (for munki) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTCanvasImportStore import MiRCARTCanvasImportStore +import sys + +def canonicalise(inPathName): + canvasStore = MiRCARTCanvasImportStore(inPathName) + inMap = canvasStore.outMap.copy(); del canvasStore; + with open(inPathName, "w+") as outFile: + for inCurRow in range(len(inMap)): + lastAttribs = MiRCARTCanvasImportStore._CellState.CS_NONE + lastColours = None + for inCurCol in range(len(inMap[inCurRow])): + inCurCell = inMap[inCurRow][inCurCol] + if lastAttribs != inCurCell[2]: + if inCurCell[2] & MiRCARTCanvasImportStore._CellState.CS_BOLD: + print("\u0002", end="", file=outFile) + if inCurCell[2] & MiRCARTCanvasImportStore._CellState.CS_UNDERLINE: + print("\u001f", end="", file=outFile) + lastAttribs = inCurCell[2] + if lastColours == None or lastColours != inCurCell[:2]: + print("\u0003{:02d},{:02d}{}".format(*inCurCell[:2], inCurCell[3]), end="", file=outFile) + lastColours = inCurCell[:2] + else: + print(inCurCell[3], end="", file=outFile) + print("\n", end="", file=outFile) + +# +# Entry point +def main(*argv): + canonicalise(argv[1]) +if __name__ == "__main__": + if (len(sys.argv) - 1) != 1: + print("usage: {} ".format(sys.argv[0]), file=sys.stderr) + else: + main(*sys.argv) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanvas.py b/MiRCARTCanvas.py new file mode 100644 index 0000000..8c9c2c5 --- /dev/null +++ b/MiRCARTCanvas.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvas.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTCanvasBackend import MiRCARTCanvasBackend +from MiRCARTCanvasJournal import MiRCARTCanvasJournal +from MiRCARTCanvasExportStore import MiRCARTCanvasExportStore, haveMiRCARTToPngFile, haveUrllib +from MiRCARTCanvasImportStore import MiRCARTCanvasImportStore +from MiRCARTCanvasInterface import MiRCARTCanvasInterface +from MiRCARTImgurApiKey import MiRCARTImgurApiKey +import wx + +class MiRCARTCanvas(wx.Panel): + """XXX""" + parentFrame = None + defaultCanvasPos = defaultCanvasSize = defaultCellSize = None + canvasMap = canvasPos = canvasSize = None + brushColours = brushPos = brushSize = None + canvasBackend = canvasJournal = None + canvasExportStore = canvasImportStore = None + canvasInterface = None + imgurApiKey = MiRCARTImgurApiKey.imgurApiKey + + # {{{ _commitPatch(self, patch): XXX + def _commitPatch(self, patch): + self.canvasMap[patch[1]][patch[0]] = patch[2:] + # }}} + # {{{ _dispatchDeltaPatches(self, deltaPatches): XXX + def _dispatchDeltaPatches(self, deltaPatches): + eventDc = self.canvasBackend.getDeviceContext(self) + for patch in deltaPatches: + if self.canvasBackend.drawPatch(eventDc, patch): + self._commitPatch(patch) + self.parentFrame.onCanvasUpdate(undoLevel=self.canvasJournal.patchesUndoLevel) + # }}} + # {{{ _dispatchPatch(self, eventDc, isCursor, patch): XXX + def _dispatchPatch(self, eventDc, isCursor, patch): + if not self._canvasDirtyCursor: + self.canvasBackend.drawCursorMaskWithJournal( \ + self.canvasJournal, eventDc) + self._canvasDirtyCursor = True + if self.canvasBackend.drawPatch(eventDc, patch): + patchDeltaCell = self.canvasMap[patch[1]][patch[0]] + patchDelta = [*patch[0:2], *patchDeltaCell] + if isCursor: + self.canvasJournal.pushCursor(patchDelta) + else: + if not self._canvasDirty: + self.canvasJournal.pushDeltas([], []) + self._canvasDirty = True + self.canvasJournal.updateCurrentDeltas(patchDelta, patch) + self._commitPatch(patch) + # }}} + + # {{{ onPanelClose(self, event): XXX + def onPanelClose(self, event): + self.Destroy() + # }}} + # {{{ onPanelEnterWindow(self, event): XXX + def onPanelEnterWindow(self, event): + self.parentFrame.SetFocus() + # }}} + # {{{ onPanelInput(self, event): XXX + def onPanelInput(self, event): + eventDc = self.canvasBackend.getDeviceContext(self) + eventType = event.GetEventType() + self._canvasDirty = self._canvasDirtyCursor = False + tool = self.canvasInterface.canvasTool + if eventType == wx.wxEVT_CHAR: + mapPoint = self.brushPos + doSkip = tool.onKeyboardEvent( \ + event, mapPoint, self.brushColours, self.brushSize, \ + chr(event.GetUnicodeKey()), self._dispatchPatch, eventDc) + if doSkip: + event.Skip(); return; + else: + mapPoint = self.canvasBackend.xlateEventPoint(event, eventDc) + if mapPoint[0] >= self.canvasSize[0] \ + or mapPoint[1] >= self.canvasSize[1]: + return + self.brushPos = mapPoint + tool.onMouseEvent( \ + event, mapPoint, self.brushColours, self.brushSize, \ + event.Dragging(), event.LeftIsDown(), event.RightIsDown(), \ + self._dispatchPatch, eventDc) + if self._canvasDirty: + self.parentFrame.onCanvasUpdate(cellPos=self.brushPos, \ + undoLevel=self.canvasJournal.patchesUndoLevel) + if eventType == wx.wxEVT_MOTION: + self.parentFrame.onCanvasUpdate(cellPos=mapPoint) + # }}} + # {{{ onPanelLeaveWindow(self, event): XXX + def onPanelLeaveWindow(self, event): + eventDc = self.canvasBackend.getDeviceContext(self) + self.canvasBackend.drawCursorMaskWithJournal( \ + self.canvasJournal, eventDc) + # }}} + # {{{ onPanelPaint(self, event): XXX + def onPanelPaint(self, event): + self.canvasBackend.onPanelPaintEvent(self, event) + # }}} + # {{{ onStoreUpdate(self, newCanvasSize, newCanvas=None): XXX + def onStoreUpdate(self, newCanvasSize, newCanvas=None): + self.resize(newCanvasSize=newCanvasSize) + eventDc = self.canvasBackend.getDeviceContext(self) + for numRow in range(self.canvasSize[1]): + for numCol in range(self.canvasSize[0]): + if newCanvas != None \ + and numRow < len(newCanvas) \ + and numCol < len(newCanvas[numRow]): + self._commitPatch([ \ + numCol, numRow, *newCanvas[numRow][numCol]]) + self.canvasBackend.drawPatch(eventDc, \ + [numCol, numRow, \ + *self.canvasMap[numRow][numCol]]) + wx.SafeYield() + # }}} + # {{{ resize(self, newCanvasSize): XXX + def resize(self, newCanvasSize): + if newCanvasSize != self.canvasSize: + if self.canvasMap == None: + self.canvasMap = []; oldCanvasSize = [0, 0]; + else: + oldCanvasSize = self.canvasSize + deltaCanvasSize = [b-a for a,b in zip(oldCanvasSize, newCanvasSize)] + + newWinSize = [a*b for a,b in zip(newCanvasSize, self.canvasBackend.cellSize)] + self.SetMinSize(newWinSize) + self.SetSize(wx.DefaultCoord, wx.DefaultCoord, *newWinSize) + curWindow = self + while curWindow != None: + curWindow.Layout() + curWindow = curWindow.GetParent() + + self.canvasBackend.resize(newCanvasSize, self.canvasBackend.cellSize) + eventDc = self.canvasBackend.getDeviceContext(self) + self.canvasJournal.resetCursor(); self.canvasJournal.resetUndo(); + + if deltaCanvasSize[0] < 0: + for numRow in range(oldCanvasSize[1]): + del self.canvasMap[numRow][-1:(deltaCanvasSize[0]-1):-1] + else: + for numRow in range(oldCanvasSize[1]): + self.canvasMap[numRow].extend( \ + [[1, 1, 0, " "]] * deltaCanvasSize[0]) + for numNewCol in range(oldCanvasSize[0], newCanvasSize[0]): + self.canvasBackend.drawPatch( \ + eventDc, [numNewCol, numRow, \ + *self.canvasMap[numRow][-1]]) + if deltaCanvasSize[1] < 0: + del self.canvasMap[-1:(deltaCanvasSize[1]-1):-1] + else: + for numNewRow in range(oldCanvasSize[1], newCanvasSize[1]): + self.canvasMap.extend( \ + [[[1, 1, 0, " "]] * newCanvasSize[0]]) + for numNewCol in range(newCanvasSize[0]): + self.canvasBackend.drawPatch( \ + eventDc, [numNewCol, numNewRow, \ + *self.canvasMap[-1][-1]]) + + self.canvasSize = newCanvasSize + wx.SafeYield() + self.parentFrame.onCanvasUpdate(size=newCanvasSize, undoLevel=-1) + # }}} + + # {{{ __del__(self): destructor method + def __del__(self): + if self.canvasMap != None: + self.canvasMap.clear(); self.canvasMap = None; + # }}} + + # + # __init__(self, parent, parentFrame, defaultCanvasPos, defaultCanvasSize, defaultCellSize): initialisation method + def __init__(self, parent, parentFrame, defaultCanvasPos, defaultCanvasSize, defaultCellSize): + super().__init__(parent, pos=defaultCanvasPos, \ + size=[w*h for w,h in zip(defaultCanvasSize, defaultCellSize)]) + + self.parentFrame = parentFrame + self.canvasMap = None + self.canvasPos = defaultCanvasPos; self.canvasSize = defaultCanvasSize; + self.defaultCanvasPos = defaultCanvasPos; self.defaultCanvasSize = defaultCanvasSize; + self.brushColours = [4, 1]; self.brushPos = [0, 0]; self.brushSize = [1, 1]; + self.parentFrame.onCanvasUpdate( \ + brushSize=self.brushSize, colours=self.brushColours) + self.canvasBackend = MiRCARTCanvasBackend(defaultCanvasSize, defaultCellSize) + self.canvasJournal = MiRCARTCanvasJournal() + self.canvasExportStore = MiRCARTCanvasExportStore(parentCanvas=self) + self.canvasImportStore = MiRCARTCanvasImportStore(parentCanvas=self) + self.canvasInterface = MiRCARTCanvasInterface(self, parentFrame) + + # Bind event handlers + self.Bind(wx.EVT_CLOSE, self.onPanelClose) + self.Bind(wx.EVT_ENTER_WINDOW, self.onPanelEnterWindow) + self.Bind(wx.EVT_LEAVE_WINDOW, self.onPanelLeaveWindow) + self.parentFrame.Bind(wx.EVT_CHAR, self.onPanelInput) + for eventType in( \ + wx.EVT_LEFT_DOWN, wx.EVT_MOTION, wx.EVT_RIGHT_DOWN): + self.Bind(eventType, self.onPanelInput) + self.Bind(wx.EVT_PAINT, self.onPanelPaint) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanvasBackend.py b/MiRCARTCanvasBackend.py new file mode 100644 index 0000000..9cf918f --- /dev/null +++ b/MiRCARTCanvasBackend.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvasBackend.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTColours import MiRCARTColours +import wx + +class MiRCARTCanvasBackend(): + """XXX""" + _font = _brushes = _pens = None + _lastBrush = _lastPen = None + canvasBitmap = cellSize = None + + # {{{ _drawBrushPatch(self, eventDc, patch): XXX + def _drawBrushPatch(self, eventDc, patch): + absPoint = self._xlatePoint(patch) + brushFg = self._brushes[patch[3]] + brushBg = self._brushes[patch[2]] + pen = self._pens[patch[3]] + self._setBrushDc(brushBg, brushFg, eventDc, pen) + eventDc.DrawRectangle(*absPoint, *self.cellSize) + # }}} + # {{{ _drawCharPatch(self, eventDc, patch): XXX + def _drawCharPatch(self, eventDc, patch): + absPoint = self._xlatePoint(patch) + brushFg = self._brushes[patch[2]] + brushBg = self._brushes[patch[3]] + pen = self._pens[patch[3]] + fontBitmap = wx.Bitmap(*self.cellSize) + fontDc = wx.MemoryDC(); fontDc.SelectObject(fontBitmap); + fontDc.SetTextForeground(wx.Colour(MiRCARTColours[patch[2]][0:4])) + fontDc.SetTextBackground(wx.Colour(MiRCARTColours[patch[3]][0:4])) + fontDc.SetBrush(brushBg); fontDc.SetBackground(brushBg); fontDc.SetPen(pen); + fontDc.SetFont(self._font) + fontDc.DrawRectangle(0, 0, *self.cellSize) + fontDc.DrawText(patch[5], 0, 0) + eventDc.Blit(*absPoint, *self.cellSize, fontDc, 0, 0) + # }}} + # {{{ _finiBrushesAndPens(self): XXX + def _finiBrushesAndPens(self): + for brush in self._brushes or []: + brush.Destroy() + self._brushes = None + for pen in self._pens or []: + pen.Destroy() + self._pens = None + self._lastBrushBg = self._lastBrushFg = self._lastPen = None; + # }}} + # {{{ _initBrushesAndPens(self): XXX + def _initBrushesAndPens(self): + self._brushes = [None for x in range(len(MiRCARTColours))] + self._pens = [None for x in range(len(MiRCARTColours))] + for mircColour in range(len(MiRCARTColours)): + self._brushes[mircColour] = wx.Brush( \ + wx.Colour(MiRCARTColours[mircColour][0:4]), wx.BRUSHSTYLE_SOLID) + self._pens[mircColour] = wx.Pen( \ + wx.Colour(MiRCARTColours[mircColour][0:4]), 1) + self._lastBrushBg = self._lastBrushFg = self._lastPen = None; + # }}} + # {{{ _setBrushDc(self, brushBg, brushFg, dc, pen): XXX + def _setBrushDc(self, brushBg, brushFg, dc, pen): + if self._lastBrushBg != brushBg: + dc.SetBackground(brushBg) + self._lastBrushBg = brushBg + if self._lastBrushFg != brushFg: + dc.SetBrush(brushFg) + self._lastBrushFg = brushFg + if self._lastPen != pen: + dc.SetPen(pen) + self._lastPen = pen + # }}} + # {{{ _xlatePoint(self, patch): XXX + def _xlatePoint(self, patch): + return [a*b for a,b in zip(patch[0:2], self.cellSize)] + # }}} + + # {{{ drawPatch(self, eventDc, patch): XXX + def drawPatch(self, eventDc, patch): + if patch[0] < self.canvasSize[0] \ + and patch[0] >= 0 \ + and patch[1] < self.canvasSize[1] \ + and patch[1] >= 0: + if patch[5] == " ": + self._drawBrushPatch(eventDc, patch) + else: + self._drawCharPatch(eventDc, patch) + return True + else: + return False + # }}} + # {{{ drawCursorMaskWithJournal(self, canvasJournal, eventDc): XXX + def drawCursorMaskWithJournal(self, canvasJournal, eventDc): + for patch in canvasJournal.popCursor(): + self.drawPatch(eventDc, patch) + # }}} + # {{{ getDeviceContext(self, parentWindow): XXX + def getDeviceContext(self, parentWindow): + eventDc = wx.BufferedDC( \ + wx.ClientDC(parentWindow), self.canvasBitmap) + self._lastBrushBg = self._lastBrushFg = self._lastPen = None; + return eventDc + # }}} + # {{{ onPanelPaintEvent(self, panelWindow, panelEvent): XXX + def onPanelPaintEvent(self, panelWindow, panelEvent): + if self.canvasBitmap != None: + eventDc = wx.BufferedPaintDC(panelWindow, self.canvasBitmap) + # }}} + # {{{ reset(self, canvasSize, cellSize): + def reset(self, canvasSize, cellSize): + self.resize(canvasSize, cellSize) + # }}} + # {{{ resize(self, canvasSize, cellSize): + def resize(self, canvasSize, cellSize): + winSize = [a*b for a,b in zip(canvasSize, cellSize)] + if self.canvasBitmap == None: + self.canvasBitmap = wx.Bitmap(winSize) + else: + oldDc = wx.MemoryDC() + oldDc.SelectObject(self.canvasBitmap) + newDc = wx.MemoryDC() + newBitmap = wx.Bitmap(winSize) + newDc.SelectObject(newBitmap) + newDc.Blit(0, 0, *self.canvasBitmap.GetSize(), oldDc, 0, 0) + oldDc.SelectObject(wx.NullBitmap) + self.canvasBitmap.Destroy(); self.canvasBitmap = newBitmap; + self.canvasSize = canvasSize; self.cellSize = cellSize; + self._font = wx.Font( \ + 8, \ + wx.FONTFAMILY_TELETYPE, \ + wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) + # }}} + # {{{ xlateEventPoint(self, event, eventDc): XXX + def xlateEventPoint(self, event, eventDc): + eventPoint = event.GetLogicalPosition(eventDc) + rectX = eventPoint.x - (eventPoint.x % self.cellSize[0]) + rectY = eventPoint.y - (eventPoint.y % self.cellSize[1]) + mapX = int(rectX / self.cellSize[0] if rectX else 0) + mapY = int(rectY / self.cellSize[1] if rectY else 0) + return (mapX, mapY) + # }}} + + # {{{ __del__(self): destructor method + def __del__(self): + if self.canvasBitmap != None: + self.canvasBitmap.Destroy(); self.canvasBitmap = None; + self._finiBrushesAndPens() + # }}} + + # + # __init__(self, canvasSize, cellSize): initialisation method + def __init__(self, canvasSize, cellSize): + self._initBrushesAndPens() + self.reset(canvasSize, cellSize) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanvasExportStore.py b/MiRCARTCanvasExportStore.py new file mode 100644 index 0000000..f8e7035 --- /dev/null +++ b/MiRCARTCanvasExportStore.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvasExportStore.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import io, os, tempfile + +try: + from MiRCARTToPngFile import MiRCARTToPngFile + haveMiRCARTToPngFile = True +except ImportError: + haveMiRCARTToPngFile = False + +try: + import base64, json, requests, urllib.request + haveUrllib = True +except ImportError: + haveUrllib = False + +class MiRCARTCanvasExportStore(): + """XXX""" + parentCanvas = None + + # {{{ _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: + requestImageData = requestImage.read() + requestData = { \ + "image": base64.b64encode(requestImageData), \ + "key": apiKey, \ + "name": imgName, \ + "title": imgTitle, \ + "type": "base64"} + requestHeaders = {"Authorization": "Client-ID " + apiKey} + responseHttp = requests.post( \ + "https://api.imgur.com/3/upload.json", \ + data=requestData, headers=requestHeaders) + responseDict = json.loads(responseHttp.text) + if responseHttp.status_code == 200: + return [200, responseDict.get("data").get("link")] + else: + return [responseHttp.status_code, ""] + # }}} + + # {{{ exportBitmapToPngFile(self, canvasBitmap, outPathName, outType): XXX + def exportBitmapToPngFile(self, canvasBitmap, outPathName, outType): + return canvasBitmap.ConvertToImage().SaveFile(outPathName, outType) + # }}} + # {{{ exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType): XXX + def exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType): + tmpPathName = tempfile.mkstemp() + os.close(tmpPathName[0]) + canvasBitmap.ConvertToImage().SaveFile(tmpPathName[1], imgType) + imgurResult = self._exportFileToImgur(apiKey, imgName, imgTitle, tmpPathName[1]) + os.remove(tmpPathName[1]) + return imgurResult + # }}} + # {{{ exportPastebin(self, apiDevKey, canvasMap, canvasSize, pasteName="", pastePrivate=0): XXX + def exportPastebin(self, apiDevKey, canvasMap, canvasSize, pasteName="", pastePrivate=0): + if haveUrllib: + outFile = io.StringIO() + self.exportTextFile(canvasMap, canvasSize, outFile) + requestData = { \ + "api_dev_key": apiDevKey, \ + "api_option": "paste", \ + "api_paste_code": outFile.getvalue().encode(), \ + "api_paste_name": pasteName, \ + "api_paste_private": pastePrivate} + responseHttp = requests.post("https://pastebin.com/api/api_post.php", \ + data=requestData) + if responseHttp.status_code == 200: + if responseHttp.text.startswith("http"): + return (True, responseHttp.text) + else: + return (False, responseHttp.text) + else: + return (False, str(responseHttp.status_code)) + else: + return (False, "missing requests and/or urllib3 module(s)") + # }}} + # {{{ exportPngFile(self, canvasMap, outPathName): XXX + def exportPngFile(self, canvasMap, outPathName): + if haveMiRCARTToPngFile: + MiRCARTToPngFile(canvasMap).export(outPathName) + return True + else: + return False + # }}} + # {{{ exportTextFile(self, canvasMap, canvasSize, outFile): XXX + def exportTextFile(self, canvasMap, canvasSize, outFile): + for canvasRow in range(canvasSize[1]): + canvasLastColours = [] + for canvasCol in range(canvasSize[0]): + canvasColColours = canvasMap[canvasRow][canvasCol][0] + canvasColText = canvasMap[canvasRow][canvasCol][2] + if canvasColColours != canvasLastColours: + canvasLastColours = canvasColColours + outFile.write("\x03" + \ + str(canvasColColours[0]) + \ + "," + str(canvasColColours[1])) + outFile.write(canvasColText) + outFile.write("\n") + # }}} + + # + # __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/MiRCARTCanvasImportStore.py b/MiRCARTCanvasImportStore.py new file mode 100644 index 0000000..a070b27 --- /dev/null +++ b/MiRCARTCanvasImportStore.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvasImportStore.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +class MiRCARTCanvasImportStore(): + """XXX""" + inFile = inSize = outMap = None + parentCanvas = None + + # + # _CellState(): Cell state + class _CellState(): + CS_NONE = 0x00 + CS_BOLD = 0x01 + CS_ITALIC = 0x02 + CS_UNDERLINE = 0x04 + + # + # _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): + if cellState & bit: + return cellState & ~bit + else: + return cellState | bit + # }}} + # {{{ _parseCharAsColourSpec(self, colourSpec, curColours): XXX + def _parseCharAsColourSpec(self, colourSpec, curColours): + if len(colourSpec) > 0: + colourSpec = colourSpec.split(",") + if len(colourSpec) == 2 \ + and len(colourSpec[1]) > 0: + return (int(colourSpec[0] or curColours[0]), \ + int(colourSpec[1])) + elif len(colourSpec) == 1 \ + or len(colourSpec[1]) == 0: + return (int(colourSpec[0]), curColours[1]) + else: + return (15, 1) + # }}} + + # {{{ importIntoPanel(self): XXX + def importIntoPanel(self): + self.parentCanvas.onStoreUpdate(self.inSize, self.outMap) + # }}} + # {{{ importTextFile(self, pathName): XXX + def importTextFile(self, pathName): + self.inFile = open(pathName, "r", encoding="utf-8") + self.inSize = self.outMap = None; + inCurColourSpec = ""; inCurRow = -1; + inLine = self.inFile.readline() + inSize = [0, 0]; outMap = []; inMaxCols = 0; + while inLine: + inCellState = self._CellState.CS_NONE + inParseState = self._ParseState.PS_CHAR + inCurCol = 0; inMaxCol = len(inLine); + inCurColourDigits = 0; inCurColours = (15, 1); inCurColourSpec = ""; + inCurRow += 1; outMap.append([]); inRowCols = 0; inSize[1] += 1; + while inCurCol < inMaxCol: + inChar = inLine[inCurCol] + if inChar in set("\r\n"): \ + inCurCol += 1 + elif inParseState == self._ParseState.PS_CHAR: + inCurCol += 1 + if inChar == "": + inCellState = self._flipCellStateBit( \ + inCellState, self._CellState.CS_BOLD) + elif inChar == "": + inParseState = self._ParseState.PS_COLOUR_DIGIT0 + elif inChar == "": + inCellState = self._flipCellStateBit( \ + inCellState, self._CellState.CS_ITALIC) + elif inChar == "": + inCellState |= self._CellState.CS_NONE + inCurColours = (15, 1) + elif inChar == "": + inCurColours = (inCurColours[1], inCurColours[0]) + elif inChar == "": + inCellState = self._flipCellStateBit( \ + inCellState, self._CellState.CS_UNDERLINE) + else: + inRowCols += 1 + outMap[inCurRow].append([*inCurColours, inCellState, inChar]) + elif inParseState == self._ParseState.PS_COLOUR_DIGIT0 \ + or inParseState == self._ParseState.PS_COLOUR_DIGIT1: + if inChar == "," \ + and inParseState == self._ParseState.PS_COLOUR_DIGIT0: + if (inCurCol + 1) < inMaxCol \ + and not inLine[inCurCol + 1] in set("0123456789"): + inCurColours = self._parseCharAsColourSpec( \ + inCurColourSpec, inCurColours) + inCurColourDigits = 0; inCurColourSpec = ""; + inParseState = self._ParseState.PS_CHAR + else: + inCurCol += 1 + inCurColourDigits = 0; inCurColourSpec += inChar; + inParseState = self._ParseState.PS_COLOUR_DIGIT1 + elif inChar in set("0123456789") \ + and inCurColourDigits == 0: + inCurCol += 1 + inCurColourDigits += 1; inCurColourSpec += inChar; + elif inChar in set("0123456789") \ + and inCurColourDigits == 1 \ + and inCurColourSpec[-1] == "0": + inCurCol += 1 + inCurColourDigits += 1; inCurColourSpec += inChar; + elif inChar in set("012345") \ + and inCurColourDigits == 1 \ + and inCurColourSpec[-1] == "1": + inCurCol += 1 + inCurColourDigits += 1; inCurColourSpec += inChar; + else: + inCurColours = self._parseCharAsColourSpec( \ + inCurColourSpec, inCurColours) + inCurColourDigits = 0; inCurColourSpec = ""; + inParseState = self._ParseState.PS_CHAR + inMaxCols = max(inMaxCols, inRowCols) + inLine = self.inFile.readline() + inSize[0] = inMaxCols; self.inSize = inSize; self.outMap = outMap; + self.inFile.close() + # }}} + # {{{ importNew(self, newCanvasSize=None): XXX + def importNew(self, newCanvasSize=None): + newMap = [[[1, 1, 0, " "] \ + for x in range(newCanvasSize[0])] \ + for y in range(newCanvasSize[1])] + self.parentCanvas.onStoreUpdate(newCanvasSize, newMap) + # }}} + + # + # __init__(self, inFile=None, parentCanvas=None): initialisation method + def __init__(self, inFile=None, parentCanvas=None): + self.inFile = inFile; self.inSize = self.outMap = None; + self.parentCanvas = parentCanvas + if inFile != None: + self.importTextFile(inFile) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanvasInterface.py b/MiRCARTCanvasInterface.py new file mode 100644 index 0000000..1f08baa --- /dev/null +++ b/MiRCARTCanvasInterface.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvasInterface.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTToolCircle import MiRCARTToolCircle +from MiRCARTToolFill import MiRCARTToolFill +from MiRCARTToolLine import MiRCARTToolLine +from MiRCARTToolSelectClone import MiRCARTToolSelectClone +from MiRCARTToolSelectMove import MiRCARTToolSelectMove +from MiRCARTToolRect import MiRCARTToolRect +from MiRCARTToolText import MiRCARTToolText + +import os, wx + +class MiRCARTCanvasInterface(): + """XXX""" + imgurApiKey = None + parentCanvas = parentFrame = canvasPathName = canvasTool = None + + # {{{ _dialogSaveChanges(self) + def _dialogSaveChanges(self): + with wx.MessageDialog(self.parentCanvas, \ + "Do you want to save changes to {}?".format( \ + self.canvasPathName), "MiRCART", \ + wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_QUESTION|wx.YES_NO) as dialog: + dialogChoice = dialog.ShowModal() + return dialogChoice + # }}} + + # {{{ canvasBrushSolid(self, event): XXX + def canvasBrushSolid(self, event): + pass + # }}} + # {{{ canvasColour(self, event, numColour): XXX + def canvasColour(self, event, numColour): + if event.GetEventType() == wx.wxEVT_TOOL: + self.parentCanvas.brushColours[0] = numColour + elif event.GetEventType() == wx.wxEVT_TOOL_RCLICKED: + self.parentCanvas.brushColours[1] = numColour + self.parentFrame.onCanvasUpdate(colours=self.parentCanvas.brushColours) + # }}} + # {{{ canvasCopy(self, event): XXX + def canvasCopy(self, event): + pass + # }}} + # {{{ canvasCut(self, event): XXX + def canvasCut(self, event): + pass + # }}} + # {{{ canvasDecrBrushHeight(self, event): XXX + def canvasDecrBrushHeight(self, event): + if self.parentCanvas.brushSize[1] > 1: + self.parentCanvas.brushSize[1] -= 1 + self.parentFrame.onCanvasUpdate(brushSize=self.parentCanvas.brushSize) + # }}} + # {{{ canvasDecrBrushHeightWidth(self, event): XXX + def canvasDecrBrushHeightWidth(self, event): + self.canvasDecrBrushHeight(event) + self.canvasDecrBrushWidth(event) + # }}} + # {{{ canvasDecrBrushWidth(self, event): XXX + def canvasDecrBrushWidth(self, event): + if self.parentCanvas.brushSize[0] > 1: + self.parentCanvas.brushSize[0] -= 1 + self.parentFrame.onCanvasUpdate(brushSize=self.parentCanvas.brushSize) + # }}} + # {{{ canvasDecrCanvasHeight(self, event): XXX + def canvasDecrCanvasHeight(self, event): + if self.parentCanvas.canvasSize[1] > 1: + self.parentCanvas.resize([ \ + self.parentCanvas.canvasSize[0], \ + self.parentCanvas.canvasSize[1]-1]) + # }}} + # {{{ canvasDecrCanvasHeightWidth(self, event): XXX + def canvasDecrCanvasHeightWidth(self, event): + self.canvasDecrCanvasHeight(event) + self.canvasDecrCanvasWidth(event) + # }}} + # {{{ canvasDecrCanvasWidth(self, event): XXX + def canvasDecrCanvasWidth(self, event): + if self.parentCanvas.canvasSize[0] > 1: + self.parentCanvas.resize([ \ + self.parentCanvas.canvasSize[0]-1, \ + self.parentCanvas.canvasSize[1]]) + # }}} + # {{{ canvasDelete(self, event): XXX + def canvasDelete(self, event): + pass + # }}} + # {{{ canvasExit(self, event): XXX + def canvasExit(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) + self.parentFrame.Close(True) + # }}} + # {{{ canvasExportAsPng(self, event): XXX + def canvasExportAsPng(self, event): + with wx.FileDialog(self, "Save As...", os.getcwd(), "", \ + "*.png", 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)) + self.parentCanvas.canvasExportStore.exportBitmapToPngFile( \ + self.parentCanvas.canvasBackend.canvasBitmap, outPathName, \ + wx.BITMAP_TYPE_PNG) + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + return True + # }}} + # {{{ canvasExportImgur(self, event): XXX + def canvasExportImgur(self, event): + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + imgurResult = self.parentCanvas.canvasExportStore.exportBitmapToImgur( \ + self.imgurApiKey, self.parentCanvas.canvasBackend.canvasBitmap, \ + "", "", wx.BITMAP_TYPE_PNG) + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + if imgurResult[0] == 200: + if not wx.TheClipboard.IsOpened(): + wx.TheClipboard.Open() + wx.TheClipboard.SetData(wx.TextDataObject(imgurResult[1])) + wx.TheClipboard.Close() + wx.MessageBox("Exported to Imgur: " + imgurResult[1], \ + "Export to Imgur", wx.OK|wx.ICON_INFORMATION) + else: + wx.MessageBox("Failed to export to Imgur: " + imgurResult[1], \ + "Export to Imgur", wx.OK|wx.ICON_EXCLAMATION) + # }}} + # {{{ canvasExportPastebin(self, event): XXX + def canvasExportPastebin(self, event): + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + pasteStatus, pasteResult = \ + self.parentCanvas.canvasExportStore.exportPastebin( \ + "", \ + self.parentCanvas.canvasMap, \ + self.parentCanvas.canvasSize) + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + if pasteStatus: + if not wx.TheClipboard.IsOpened(): + wx.TheClipboard.Open() + wx.TheClipboard.SetData(wx.TextDataObject(pasteResult)) + wx.TheClipboard.Close() + wx.MessageBox("Exported to Pastebin: " + pasteResult, \ + "Export to Pastebin", wx.OK|wx.ICON_INFORMATION) + else: + wx.MessageBox("Failed to export to Pastebin: " + pasteResult, \ + "Export to Pastebin", wx.OK|wx.ICON_EXCLAMATION) + # }}} + # {{{ canvasIncrBrushHeight(self, event): XXX + def canvasIncrBrushHeight(self, event): + self.parentCanvas.brushSize[1] += 1 + self.parentFrame.onCanvasUpdate(brushSize=self.parentCanvas.brushSize) + # }}} + # {{{ canvasIncrBrushHeightWidth(self, event): XXX + def canvasIncrBrushHeightWidth(self, event): + self.canvasIncrBrushHeight(event) + self.canvasIncrBrushWidth(event) + # }}} + # {{{ canvasIncrBrushWidth(self, event): XXX + def canvasIncrBrushWidth(self, event): + self.parentCanvas.brushSize[0] += 1 + self.parentFrame.onCanvasUpdate(brushSize=self.parentCanvas.brushSize) + # }}} + # {{{ canvasIncrCanvasHeight(self, event): XXX + def canvasIncrCanvasHeight(self, event): + self.parentCanvas.resize([ \ + self.parentCanvas.canvasSize[0], \ + self.parentCanvas.canvasSize[1]+1]) + # }}} + # {{{ canvasIncrCanvasHeightWidth(self, event): XXX + def canvasIncrCanvasHeightWidth(self, event): + self.canvasIncrCanvasHeight(event) + self.canvasIncrCanvasWidth(event) + # }}} + # {{{ canvasIncrCanvasWidth(self, event): XXX + def canvasIncrCanvasWidth(self, event): + self.parentCanvas.resize([ \ + self.parentCanvas.canvasSize[0]+1, \ + self.parentCanvas.canvasSize[1]]) + # }}} + # {{{ canvasNew(self, event, newCanvasSize=None): XXX + def canvasNew(self, event, newCanvasSize=None): + 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) + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + if newCanvasSize == None: + newCanvasSize = list(self.parentCanvas.defaultCanvasSize) + self.parentCanvas.canvasImportStore.importNew(newCanvasSize) + self.canvasPathName = None + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + self.parentFrame.onCanvasUpdate(pathName="", undoLevel=-1) + # }}} + # {{{ canvasOpen(self, event): XXX + def canvasOpen(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(), "", \ + "*.txt", 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.importTextFile(self.canvasPathName) + self.parentCanvas.canvasImportStore.importIntoPanel() + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + self.parentFrame.onCanvasUpdate( \ + pathName=self.canvasPathName, undoLevel=-1) + return True + # }}} + # {{{ canvasPaste(self, event): XXX + def canvasPaste(self, event): + pass + # }}} + # {{{ canvasRedo(self, event): XXX + def canvasRedo(self, event): + self.parentCanvas._dispatchDeltaPatches( \ + self.parentCanvas.canvasJournal.popRedo()) + # }}} + # {{{ canvasSave(self, event): XXX + def canvasSave(self, event): + if self.canvasPathName == None: + if self.canvasSaveAs(event) == False: + return + try: + with open(self.canvasPathName, "w") as outFile: + self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + self.parentCanvas.canvasExportStore.exportTextFile( \ + self.parentCanvas.canvasMap, \ + self.parentCanvas.canvasSize, outFile) + self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) + return True + except IOError as error: + return False + # }}} + # {{{ 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: + if dialog.ShowModal() == wx.ID_CANCEL: + return False + else: + self.canvasPathName = dialog.GetPath() + return self.canvasSave(event) + # }}} + # {{{ canvasToolCircle(self, event): XXX + def canvasToolCircle(self, event): + self.canvasTool = MiRCARTToolCircle(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_CIRCLE[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_CIRCLE[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_CIRCLE[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolFill(self, event): XXX + def canvasToolFill(self, event): + self.canvasTool = MiRCARTToolFill(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_FILL[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_FILL[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_FILL[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolLine(self, event): XXX + def canvasToolLine(self, event): + self.canvasTool = MiRCARTToolLine(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_LINE[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_LINE[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_LINE[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolSelectClone(self, event): XXX + def canvasToolSelectClone(self, event): + self.canvasTool = MiRCARTToolSelectClone(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_CLONE_SELECT[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_CLONE_SELECT[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_CLONE_SELECT[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolSelectMove(self, event): XXX + def canvasToolSelectMove(self, event): + self.canvasTool = MiRCARTToolSelectMove(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_MOVE_SELECT[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_MOVE_SELECT[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_MOVE_SELECT[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolRect(self, event): XXX + def canvasToolRect(self, event): + self.canvasTool = MiRCARTToolRect(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_RECT[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_RECT[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_RECT[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasToolText(self, event): XXX + def canvasToolText(self, event): + self.canvasTool = MiRCARTToolText(self.parentCanvas) + self.parentFrame.menuItemsById[self.parentFrame.CID_TEXT[0]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.parentFrame.CID_TEXT[0]].GetToolBar() + toolBar.ToggleTool(self.parentFrame.CID_TEXT[0], True) + self.parentFrame.onCanvasUpdate(toolName=self.canvasTool.name) + # }}} + # {{{ canvasUndo(self, event): XXX + def canvasUndo(self, event): + self.parentCanvas._dispatchDeltaPatches( \ + self.parentCanvas.canvasJournal.popUndo()) + # }}} + + # + # __init__(self, parentCanvas, parentFrame): + def __init__(self, parentCanvas, parentFrame): + self.parentCanvas = parentCanvas; self.parentFrame = parentFrame; + self.canvasPathName = None + self.canvasToolRect(None) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCanvasJournal.py b/MiRCARTCanvasJournal.py new file mode 100644 index 0000000..6bbaa81 --- /dev/null +++ b/MiRCARTCanvasJournal.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# MiRCARTCanvasJournal.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +class MiRCARTCanvasJournal(): + """XXX""" + patchesCursor = patchesUndo = patchesUndoLevel = None + + # {{{ popCursor(self): XXX + def popCursor(self): + if len(self.patchesCursor): + patchesCursor = self.patchesCursor + self.patchesCursor = [] + return patchesCursor + else: + return [] + # }}} + # {{{ popRedo(self): XXX + def popRedo(self): + if self.patchesUndoLevel > 0: + self.patchesUndoLevel -= 1 + patches = self.patchesUndo[self.patchesUndoLevel] + return patches[1] + else: + return [] + # }}} + # {{{ popUndo(self): XXX + def popUndo(self): + if self.patchesUndo[self.patchesUndoLevel] != None: + patches = self.patchesUndo[self.patchesUndoLevel] + self.patchesUndoLevel += 1 + return patches[0] + else: + return [] + # }}} + # {{{ pushCursor(self, patches): XXX + def pushCursor(self, patches): + self.patchesCursor.append(patches) + # }}} + # {{{ pushDeltas(self, undoPatches, redoPatches): XXX + def pushDeltas(self, undoPatches, redoPatches): + if self.patchesUndoLevel > 0: + del self.patchesUndo[0:self.patchesUndoLevel] + self.patchesUndoLevel = 0 + deltaItem = [undoPatches, redoPatches] + self.patchesUndo.insert(0, deltaItem) + return deltaItem + # }}} + # {{{ resetCursor(self): XXX + def resetCursor(self): + if self.patchesCursor != None: + self.patchesCursor.clear() + self.patchesCursor = [] + # }}} + # {{{ resetUndo(self): XXX + def resetUndo(self): + if self.patchesUndo != None: + self.patchesUndo.clear() + self.patchesUndo = [None]; self.patchesUndoLevel = 0; + # }}} + # {{{ updateCurrentDeltas(self, undoPatches, redoPatches): XXX + def updateCurrentDeltas(self, undoPatches, redoPatches): + self.patchesUndo[0][0].append(undoPatches) + self.patchesUndo[0][1].append(redoPatches) + # }}} + + # {{{ __del__(self): destructor method + def __del__(self): + self.resetCursor(); self.resetUndo(); + # }}} + + # + # __init__(self): initialisation method + def __init__(self): + self.resetCursor(); self.resetUndo(); + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTCheckLineLengths.sh b/MiRCARTCheckLineLengths.sh new file mode 100755 index 0000000..17702cf --- /dev/null +++ b/MiRCARTCheckLineLengths.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# MiRCARTCheckLineLengths.py -- check mIRC art line lengths +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +for FNAME in "${@}"; do + FNAME_LINES="$(wc -l "${FNAME}" | awk '{print $1}')"; + for FNAME_LINE in $(seq "${FNAME_LINES}"); do + printf "%-5d %-5d %s\n" \ + "$(sed -n "${FNAME_LINE}p" "${FNAME}" | wc -c)" \ + "${FNAME_LINE}" "${FNAME}"; + done; +done | sort -nk1; + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTColours.py b/MiRCARTColours.py new file mode 100644 index 0000000..2475cc6 --- /dev/null +++ b/MiRCARTColours.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# MiRCARTColours.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# +# MiRCARTColours: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline], +# +MiRCARTColours = [ + [255, 255, 255, 255, "White"], + [0, 0, 0, 255, "Black"], + [0, 0, 187, 255, "Blue"], + [0, 187, 0, 255, "Green"], + [255, 85, 85, 255, "Light Red"], + [187, 0, 0, 255, "Red"], + [187, 0, 187, 255, "Purple"], + [187, 187, 0, 255, "Yellow"], + [255, 255, 85, 255, "Light Yellow"], + [85, 255, 85, 255, "Light Green"], + [0, 187, 187, 255, "Cyan"], + [85, 255, 255, 255, "Light Cyan"], + [85, 85, 255, 255, "Light Blue"], + [255, 85, 255, 255, "Pink"], + [85, 85, 85, 255, "Grey"], + [187, 187, 187, 255, "Light Grey"], +] + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTFrame.py b/MiRCARTFrame.py new file mode 100644 index 0000000..4c929dc --- /dev/null +++ b/MiRCARTFrame.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# +# MiRCARTFrame.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTCanvas import MiRCARTCanvas, haveUrllib +from MiRCARTCanvasInterface import MiRCARTCanvasInterface +from MiRCARTColours import MiRCARTColours +from MiRCARTGeneralFrame import MiRCARTGeneralFrame, \ + TID_ACCELS, TID_COMMAND, TID_LIST, TID_MENU, TID_NOTHING, TID_SELECT, TID_TOOLBAR, \ + NID_MENU_SEP, NID_TOOLBAR_HSEP, NID_TOOLBAR_VSEP + +import os, wx + +class MiRCARTFrame(MiRCARTGeneralFrame): + """XXX""" + panelCanvas = None; lastPanelState = {}; + + # {{{ Commands + # Id Type Id Labels Icon bitmap Accelerator [Initial state] + CID_NEW = [0x100, TID_COMMAND, "New", "&New", ["", wx.ART_NEW], [wx.ACCEL_CTRL, ord("N")], None, MiRCARTCanvasInterface.canvasNew] + CID_OPEN = [0x101, TID_COMMAND, "Open", "&Open", ["", wx.ART_FILE_OPEN], [wx.ACCEL_CTRL, ord("O")], None, MiRCARTCanvasInterface.canvasOpen] + CID_SAVE = [0x102, TID_COMMAND, "Save", "&Save", ["", wx.ART_FILE_SAVE], [wx.ACCEL_CTRL, ord("S")], None, MiRCARTCanvasInterface.canvasSave] + CID_SAVEAS = [0x103, TID_COMMAND, "Save As...", "Save &As...", ["", wx.ART_FILE_SAVE_AS], None, None, MiRCARTCanvasInterface.canvasSaveAs] + CID_EXPORT_AS_PNG = [0x104, TID_COMMAND, "Export as PNG...", \ + "Export as PN&G...", None, None, None, MiRCARTCanvasInterface.canvasExportAsPng] + CID_EXPORT_IMGUR = [0x105, TID_COMMAND, "Export to Imgur...", \ + "Export to I&mgur...", None, None, haveUrllib, MiRCARTCanvasInterface.canvasExportImgur] + CID_EXPORT_PASTEBIN = [0x106, TID_COMMAND, "Export to Pastebin...", \ + "Export to Pasteb&in...", None, None, haveUrllib, MiRCARTCanvasInterface.canvasExportPastebin] + CID_EXIT = [0x107, TID_COMMAND, "Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None, MiRCARTCanvasInterface.canvasExit] + CID_UNDO = [0x108, TID_COMMAND, "Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False, MiRCARTCanvasInterface.canvasUndo] + CID_REDO = [0x109, TID_COMMAND, "Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False, MiRCARTCanvasInterface.canvasRedo] + CID_CUT = [0x10a, TID_COMMAND, "Cut", "Cu&t", ["", wx.ART_CUT], None, False, MiRCARTCanvasInterface.canvasCut] + CID_COPY = [0x10b, TID_COMMAND, "Copy", "&Copy", ["", wx.ART_COPY], None, False, MiRCARTCanvasInterface.canvasCopy] + CID_PASTE = [0x10c, TID_COMMAND, "Paste", "&Paste", ["", wx.ART_PASTE], None, False, MiRCARTCanvasInterface.canvasPaste] + CID_DELETE = [0x10d, TID_COMMAND, "Delete", "De&lete", ["", wx.ART_DELETE], None, False, MiRCARTCanvasInterface.canvasDelete] + CID_INCRW_CANVAS = [0x10e, TID_COMMAND, "Increase canvas width", \ + "Increase canvas width", ["toolIncrCanvasW.png"], None, None, MiRCARTCanvasInterface.canvasIncrCanvasWidth] + CID_DECRW_CANVAS = [0x10f, TID_COMMAND, "Decrease canvas width", \ + "Decrease canvas width", ["toolDecrCanvasW.png"], None, None, MiRCARTCanvasInterface.canvasDecrCanvasWidth] + CID_INCRH_CANVAS = [0x110, TID_COMMAND, "Increase canvas height", \ + "Increase canvas height", ["toolIncrCanvasH.png"], None, None, MiRCARTCanvasInterface.canvasIncrCanvasHeight] + CID_DECRH_CANVAS = [0x111, TID_COMMAND, "Decrease canvas height", \ + "Decrease canvas height", ["toolDecrCanvasH.png"], None, None, MiRCARTCanvasInterface.canvasDecrCanvasHeight] + CID_INCRHW_CANVAS = [0x112, TID_COMMAND, "Increase canvas size", \ + "Increase canvas size", ["toolIncrCanvasHW.png"], None, None, MiRCARTCanvasInterface.canvasIncrCanvasHeightWidth] + CID_DECRHW_CANVAS = [0x113, TID_COMMAND, "Decrease canvas size", \ + "Decrease canvas size", ["toolDecrCanvasHW.png"], None, None, MiRCARTCanvasInterface.canvasDecrCanvasHeightWidth] + CID_INCRW_BRUSH = [0x114, TID_COMMAND, "Increase brush width", \ + "Increase brush width", ["toolIncrBrushW.png"], None, None, MiRCARTCanvasInterface.canvasIncrBrushWidth] + CID_DECRW_BRUSH = [0x115, TID_COMMAND, "Decrease brush width", \ + "Decrease brush width", ["toolDecrBrushW.png"], None, None, MiRCARTCanvasInterface.canvasDecrBrushWidth] + CID_INCRH_BRUSH = [0x116, TID_COMMAND, "Increase brush height", \ + "Increase brush height", ["toolIncrBrushH.png"], None, None, MiRCARTCanvasInterface.canvasIncrBrushHeight] + CID_DECRH_BRUSH = [0x117, TID_COMMAND, "Decrease brush height", \ + "Decrease brush height", ["toolDecrBrushH.png"], None, None, MiRCARTCanvasInterface.canvasDecrBrushHeight] + CID_INCRHW_BRUSH = [0x118, TID_COMMAND, "Increase brush size", \ + "Increase brush size", ["toolIncrBrushHW.png"], None, None, MiRCARTCanvasInterface.canvasIncrBrushHeightWidth] + CID_DECRHW_BRUSH = [0x119, TID_COMMAND, "Decrease brush size", \ + "Decrease brush size", ["toolDecrBrushHW.png"], None, None, MiRCARTCanvasInterface.canvasDecrBrushHeightWidth] + CID_SOLID_BRUSH = [0x11a, TID_SELECT, "Solid brush", "Solid brush", None, None, True, MiRCARTCanvasInterface.canvasBrushSolid] + + CID_RECT = [0x150, TID_SELECT, "Rectangle", "&Rectangle", ["toolRect.png"], [wx.ACCEL_CTRL, ord("R")], True, MiRCARTCanvasInterface.canvasToolRect] + CID_CIRCLE = [0x151, TID_SELECT, "Circle", "&Circle", ["toolCircle.png"], [wx.ACCEL_CTRL, ord("C")], False, MiRCARTCanvasInterface.canvasToolCircle] + CID_FILL = [0x152, TID_SELECT, "Fill", "&Fill", ["toolFill.png"], [wx.ACCEL_CTRL, ord("F")], False, MiRCARTCanvasInterface.canvasToolFill] + CID_LINE = [0x153, TID_SELECT, "Line", "&Line", ["toolLine.png"], [wx.ACCEL_CTRL, ord("L")], False, MiRCARTCanvasInterface.canvasToolLine] + CID_TEXT = [0x154, TID_SELECT, "Text", "&Text", ["toolText.png"], [wx.ACCEL_CTRL, ord("T")], False, MiRCARTCanvasInterface.canvasToolText] + CID_CLONE_SELECT = [0x155, TID_SELECT, "Clone", "Cl&one", ["toolClone.png"], [wx.ACCEL_CTRL, ord("E")], False, MiRCARTCanvasInterface.canvasToolSelectClone] + CID_MOVE_SELECT = [0x156, TID_SELECT, "Move", "&Move", ["toolMove.png"], [wx.ACCEL_CTRL, ord("M")], False, MiRCARTCanvasInterface.canvasToolSelectMove] + + CID_COLOUR00 = [0x1a0, TID_SELECT, "Colour #00", "Colour #00", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR01 = [0x1a1, TID_SELECT, "Colour #01", "Colour #01", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR02 = [0x1a2, TID_SELECT, "Colour #02", "Colour #02", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR03 = [0x1a3, TID_SELECT, "Colour #03", "Colour #03", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR04 = [0x1a4, TID_SELECT, "Colour #04", "Colour #04", None, None, True, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR05 = [0x1a5, TID_SELECT, "Colour #05", "Colour #05", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR06 = [0x1a6, TID_SELECT, "Colour #06", "Colour #06", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR07 = [0x1a7, TID_SELECT, "Colour #07", "Colour #07", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR08 = [0x1a8, TID_SELECT, "Colour #08", "Colour #08", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR09 = [0x1a9, TID_SELECT, "Colour #09", "Colour #09", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR10 = [0x1aa, TID_SELECT, "Colour #10", "Colour #10", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR11 = [0x1ab, TID_SELECT, "Colour #11", "Colour #11", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR12 = [0x1ac, TID_SELECT, "Colour #12", "Colour #12", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR13 = [0x1ad, TID_SELECT, "Colour #13", "Colour #13", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR14 = [0x1ae, TID_SELECT, "Colour #14", "Colour #14", None, None, False, MiRCARTCanvasInterface.canvasColour] + CID_COLOUR15 = [0x1af, TID_SELECT, "Colour #15", "Colour #15", None, None, False, MiRCARTCanvasInterface.canvasColour] + # }}} + # {{{ Menus + MID_FILE = (0x300, TID_MENU, "File", "&File", ( \ + CID_NEW, CID_OPEN, CID_SAVE, CID_SAVEAS, NID_MENU_SEP, \ + CID_EXPORT_AS_PNG, CID_EXPORT_IMGUR, CID_EXPORT_PASTEBIN, NID_MENU_SEP, \ + CID_EXIT)) + MID_EDIT = (0x301, 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 = (0x302, TID_MENU, "Tools", "&Tools", ( \ + CID_RECT, CID_CIRCLE, CID_FILL, CID_LINE, CID_TEXT, CID_CLONE_SELECT, CID_MOVE_SELECT)) + # }}} + # {{{ Toolbars + BID_TOOLBAR = (0x400, TID_TOOLBAR, ( \ + CID_NEW, CID_OPEN, CID_SAVE, CID_SAVEAS, NID_TOOLBAR_HSEP, \ + CID_UNDO, CID_REDO, NID_TOOLBAR_HSEP, \ + CID_CUT, CID_COPY, CID_PASTE, CID_DELETE, NID_TOOLBAR_HSEP, \ + CID_INCRW_CANVAS, CID_DECRW_CANVAS, CID_INCRH_CANVAS, CID_DECRH_CANVAS, NID_TOOLBAR_HSEP, \ + CID_INCRHW_CANVAS, CID_DECRHW_CANVAS, NID_TOOLBAR_HSEP, \ + CID_RECT, CID_CIRCLE, CID_FILL, CID_LINE, CID_TEXT, CID_CLONE_SELECT, CID_MOVE_SELECT, \ + NID_TOOLBAR_VSEP, \ + CID_COLOUR00, CID_COLOUR01, CID_COLOUR02, CID_COLOUR03, CID_COLOUR04, \ + CID_COLOUR05, CID_COLOUR06, CID_COLOUR07, CID_COLOUR08, CID_COLOUR09, \ + CID_COLOUR10, CID_COLOUR11, CID_COLOUR12, CID_COLOUR13, CID_COLOUR14, \ + CID_COLOUR15, NID_TOOLBAR_HSEP, \ + CID_INCRW_BRUSH, CID_DECRW_BRUSH, CID_INCRH_BRUSH, CID_DECRH_BRUSH, NID_TOOLBAR_HSEP, \ + CID_INCRHW_BRUSH, CID_DECRHW_BRUSH)) + # }}} + # {{{ Accelerators (hotkeys) + AID_EDIT = (0x500, TID_ACCELS, ( \ + CID_NEW, CID_OPEN, CID_SAVE, CID_EXIT, CID_UNDO, CID_REDO, \ + CID_RECT, CID_CIRCLE, CID_FILL, CID_LINE, CID_TEXT, CID_CLONE_SELECT, CID_MOVE_SELECT)) + # }}} + # {{{ Lists + LID_ACCELS = (0x600, TID_LIST, (AID_EDIT)) + LID_MENUS = (0x601, TID_LIST, (MID_FILE, MID_EDIT, MID_TOOLS)) + LID_TOOLBARS = (0x602, TID_LIST, (BID_TOOLBAR)) + # }}} + + # {{{ _initPaletteToolBitmaps(self): XXX + def _initPaletteToolBitmaps(self): + paletteDescr = ( \ + self.CID_COLOUR00, self.CID_COLOUR01, self.CID_COLOUR02, self.CID_COLOUR03, self.CID_COLOUR04, \ + self.CID_COLOUR05, self.CID_COLOUR06, self.CID_COLOUR07, self.CID_COLOUR08, self.CID_COLOUR09, \ + self.CID_COLOUR10, self.CID_COLOUR11, self.CID_COLOUR12, self.CID_COLOUR13, self.CID_COLOUR14, \ + self.CID_COLOUR15) + for numColour in range(len(paletteDescr)): + toolBitmapColour = MiRCARTColours[numColour][0:4] + toolBitmap = wx.Bitmap((16,16)) + toolBitmapDc = wx.MemoryDC(); toolBitmapDc.SelectObject(toolBitmap); + toolBitmapBrush = wx.Brush( \ + wx.Colour(toolBitmapColour), wx.BRUSHSTYLE_SOLID) + toolBitmapDc.SetBrush(toolBitmapBrush) + toolBitmapDc.SetBackground(toolBitmapBrush) + toolBitmapDc.SetPen(wx.Pen(wx.Colour(toolBitmapColour), 1)) + toolBitmapDc.DrawRectangle(0, 0, 16, 16) + paletteDescr[numColour][4] = ["", None, toolBitmap] + # }}} + + # {{{ onInput(self, event): XXX + def onInput(self, event): + eventId = event.GetId() + if eventId >= self.CID_COLOUR00[0] \ + and eventId <= self.CID_COLOUR15[0]: + numColour = eventId - self.CID_COLOUR00[0] + self.itemsById[eventId][7](self.panelCanvas.canvasInterface, event, numColour) + else: + self.itemsById[eventId][7](self.panelCanvas.canvasInterface, event) + # }}} + # {{{ onCanvasUpdate(self, newBrushSize=None, newCellPos=None, newColours=None, newPathName=None, newSize=None, newToolName=None, newUndoLevel=None): XXX + def onCanvasUpdate(self, **kwargs): + self.lastPanelState.update(kwargs) + textItems = [] + if "cellPos" in self.lastPanelState: + textItems.append("X: {:03d} Y: {:03d}".format( \ + *self.lastPanelState["cellPos"])) + if "size" in self.lastPanelState: + textItems.append("W: {:03d} H: {:03d}".format( \ + *self.lastPanelState["size"])) + if "brushSize" in self.lastPanelState: + textItems.append("Brush: {:02d}x{:02d}".format( \ + *self.lastPanelState["brushSize"])) + if "colours" in self.lastPanelState: + textItems.append("FG: {:02d}, BG: {:02d}".format( \ + *self.lastPanelState["colours"])) + textItems.append("{} on {}".format( \ + MiRCARTColours[self.lastPanelState["colours"][0]][4], \ + MiRCARTColours[self.lastPanelState["colours"][1]][4])) + if "pathName" in self.lastPanelState: + if self.lastPanelState["pathName"] != "": + basePathName = os.path.basename(self.lastPanelState["pathName"]) + textItems.append("Current file: {}".format(basePathName)) + self.SetTitle("{} - MiRCART".format(basePathName)) + else: + self.SetTitle("MiRCART") + if "toolName" in self.lastPanelState: + textItems.append("Current tool: {}".format( \ + self.lastPanelState["toolName"])) + self.statusBar.SetStatusText(" | ".join(textItems)) + if "undoLevel" in self.lastPanelState: + if self.lastPanelState["undoLevel"] >= 0: + self.menuItemsById[self.CID_UNDO[0]].Enable(True) + toolBar = self.toolBarItemsById[self.CID_UNDO[0]].GetToolBar() + toolBar.EnableTool(self.CID_UNDO[0], True) + else: + self.menuItemsById[self.CID_UNDO[0]].Enable(False) + toolBar = self.toolBarItemsById[self.CID_UNDO[0]].GetToolBar() + toolBar.EnableTool(self.CID_UNDO[0], False) + if self.lastPanelState["undoLevel"] > 0: + self.menuItemsById[self.CID_REDO[0]].Enable(True) + toolBar = self.toolBarItemsById[self.CID_REDO[0]].GetToolBar() + toolBar.EnableTool(self.CID_REDO[0], True) + else: + self.menuItemsById[self.CID_REDO[0]].Enable(False) + toolBar = self.toolBarItemsById[self.CID_REDO[0]].GetToolBar() + toolBar.EnableTool(self.CID_REDO[0], False) + # }}} + + # {{{ __del__(self): destructor method + def __del__(self): + if self.panelCanvas != None: + del self.panelCanvas; self.panelCanvas = None; + # }}} + + # + # __init__(self, parent, appSize=(840, 630), defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), defaultCellSize=(7, 14)): initialisation method + def __init__(self, parent, appSize=(840, 630), defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), defaultCellSize=(7, 14)): + self._initPaletteToolBitmaps() + self.panelSkin = super().__init__(parent, wx.ID_ANY, "MiRCART", size=appSize) + self.panelCanvas = MiRCARTCanvas(self.panelSkin, parentFrame=self, \ + defaultCanvasPos=defaultCanvasPos, \ + defaultCanvasSize=defaultCanvasSize, \ + defaultCellSize=defaultCellSize) + self.panelCanvas.canvasInterface.canvasNew(None) + self.sizerSkin.AddSpacer(5) + self.sizerSkin.Add(self.panelCanvas, 0, wx.ALL|wx.EXPAND, 14) + self.panelSkin.SetSizer(self.sizerSkin) + self.panelSkin.SetAutoLayout(1) + self.sizerSkin.Fit(self.panelSkin) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTGeneralFrame.py b/MiRCARTGeneralFrame.py new file mode 100644 index 0000000..ea53385 --- /dev/null +++ b/MiRCARTGeneralFrame.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# +# MiRCARTGeneralFrame.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import os, sys, wx + +# +# Types +TID_ACCELS = (0x001) +TID_COMMAND = (0x002) +TID_LIST = (0x003) +TID_MENU = (0x004) +TID_NOTHING = (0x005) +TID_SELECT = (0x006) +TID_TOOLBAR = (0x007) + +# +# Non-items +NID_MENU_SEP = (0x200, TID_NOTHING) +NID_TOOLBAR_HSEP = (0x201, TID_NOTHING) +NID_TOOLBAR_VSEP = (0x202, TID_NOTHING) + +class MiRCARTGeneralFrame(wx.Frame): + """XXX""" + accelItemsById = itemsById = menuItemsById = toolBarItemsById = None + statusBar = toolBars = None + panelSkin = sizerSkin = None + + # {{{ _initAccelTable(self, accelsDescr): XXX + def _initAccelTable(self, accelsDescr): + accelTableEntries = [wx.AcceleratorEntry() for n in range(len(accelsDescr[2]))] + self.accelItemsById = {} + for numAccel in range(len(accelsDescr[2])): + accelDescr = accelsDescr[2][numAccel] + if accelDescr[5] != None: + self.itemsById[accelDescr[0]] = accelDescr + accelTableEntries[numAccel].Set(*accelDescr[5], accelDescr[0]) + self.accelItemsById[accelDescr[0]] = accelTableEntries[numAccel] + self.Bind(wx.EVT_MENU, self.onInput, id=accelDescr[0]) + return accelTableEntries + # }}} + # {{{ _initMenus(self, menusDescr): XXX + def _initMenus(self, menusDescr): + self.menuItemsById = {}; menuBar = wx.MenuBar(); + for menuDescr in menusDescr: + menuWindow = wx.Menu() + for menuItem in menuDescr[4]: + if menuItem == NID_MENU_SEP: + menuWindow.AppendSeparator() + elif menuItem[1] == TID_SELECT: + self.itemsById[menuItem[0]] = menuItem + menuItemWindow = menuWindow.AppendRadioItem(menuItem[0], menuItem[3], menuItem[2]) + if menuItem[5] != None: + menuItemWindow.SetAccel(self.accelItemsById[menuItem[0]]) + self.menuItemsById[menuItem[0]] = menuItemWindow + self.Bind(wx.EVT_MENU, self.onInput, menuItemWindow) + if menuItem[6] != None: + menuItemWindow.Check(menuItem[6]) + else: + self.itemsById[menuItem[0]] = menuItem + menuItemWindow = menuWindow.Append(menuItem[0], menuItem[3], menuItem[2]) + if menuItem[5] != None: + menuItemWindow.SetAccel(self.accelItemsById[menuItem[0]]) + self.menuItemsById[menuItem[0]] = menuItemWindow + self.Bind(wx.EVT_MENU, self.onInput, menuItemWindow) + if menuItem[6] != None: + menuItemWindow.Enable(menuItem[6]) + menuBar.Append(menuWindow, menuDescr[3]) + return menuBar + # }}} + # {{{ _initToolBars(self, toolBarsDescr, panelSkin): XXX + def _initToolBars(self, toolBarsDescr, panelSkin): + self.toolBarItemsById = {} + self.sizerSkin = wx.BoxSizer(wx.VERTICAL) + self.toolBars = [None]; numToolBar = 0; + for toolBarItem in toolBarsDescr[2]: + if self.toolBars[numToolBar] == None: + self.toolBars[numToolBar] = \ + wx.ToolBar(panelSkin, -1, \ + style=wx.HORIZONTAL|wx.TB_FLAT|wx.TB_NODIVIDER) + self.toolBars[numToolBar].SetToolBitmapSize((16,16)) + if toolBarItem == NID_TOOLBAR_HSEP: + self.toolBars[numToolBar].AddSeparator() + elif toolBarItem == NID_TOOLBAR_VSEP: + numToolBar += 1; self.toolBars.append(None); + elif toolBarItem[1] == TID_SELECT: + self.itemsById[toolBarItem[0]] = toolBarItem + toolBarItemWindow = \ + self.toolBars[numToolBar].AddRadioTool( \ + toolBarItem[0], toolBarItem[2], toolBarItem[4][2]) + self.toolBarItemsById[toolBarItem[0]] = toolBarItemWindow + if toolBarItem[6] != None: + toolBarItemWindow.Toggle(toolBarItem[6]) + self.Bind(wx.EVT_TOOL, self.onInput, toolBarItemWindow) + self.Bind(wx.EVT_TOOL_RCLICKED, self.onInput, toolBarItemWindow) + else: + self.itemsById[toolBarItem[0]] = toolBarItem + toolBarItemWindow = \ + self.toolBars[numToolBar].AddTool( \ + toolBarItem[0], toolBarItem[2], toolBarItem[4][2]) + self.toolBarItemsById[toolBarItem[0]] = toolBarItemWindow + if toolBarItem[6] != None: + toolBarItemWindow.Enable(toolBarItem[6]) + self.Bind(wx.EVT_TOOL, self.onInput, toolBarItemWindow) + self.Bind(wx.EVT_TOOL_RCLICKED, self.onInput, toolBarItemWindow) + for numToolBar in range(len(self.toolBars)): + self.sizerSkin.Add( \ + self.toolBars[numToolBar], 0, wx.ALL|wx.ALIGN_LEFT, 3) + self.toolBars[numToolBar].Realize() + self.toolBars[numToolBar].Fit() + # }}} + # {{{ _initToolBitmaps(self, toolBarsDescr): XXX + def _initToolBitmaps(self, toolBarsDescr): + for toolBarItem in toolBarsDescr[2]: + if toolBarItem == NID_TOOLBAR_HSEP \ + or toolBarItem == NID_TOOLBAR_VSEP: + continue + elif toolBarItem[4] == None: + toolBarItem[4] = ["", None, wx.ArtProvider.GetBitmap( \ + wx.ART_HELP, wx.ART_TOOLBAR, (16,16))] + elif toolBarItem[4][0] == "" \ + and toolBarItem[4][1] != None: + toolBarItem[4] = ["", None, wx.ArtProvider.GetBitmap( \ + toolBarItem[4][1], wx.ART_TOOLBAR, (16,16))] + elif toolBarItem[4][0] == "" \ + and toolBarItem[4][1] == None: + toolBarItem[4] = ["", None, toolBarItem[4][2]] + elif toolBarItem[4][0] != "": + toolBitmapPathName = os.path.dirname(sys.argv[0]) + toolBitmapPathName = os.path.join(toolBitmapPathName, \ + "assets", toolBarItem[4][0]) + toolBitmap = wx.Bitmap((16,16)) + toolBitmap.LoadFile(toolBitmapPathName, wx.BITMAP_TYPE_ANY) + toolBarItem[4] = ["", None, toolBitmap] + # }}} + # {{{ onInput(self, event): XXX + def onInput(self, event): + pass + # }}} + + # + # __init__(self, *args, **kwargs): initialisation method + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs); self.itemsById = {}; + panelSkin = wx.Panel(self, wx.ID_ANY) + + # Initialise accelerators (hotkeys) + accelTable = wx.AcceleratorTable( \ + self._initAccelTable(self.LID_ACCELS[2])) + self.SetAcceleratorTable(accelTable) + + # Initialise menu bar, menus & menu items + # Initialise toolbar & toolbar items + menuBar = self._initMenus(self.LID_MENUS[2]) + self.SetMenuBar(menuBar) + self._initToolBitmaps(self.LID_TOOLBARS[2]) + toolBar = self._initToolBars(self.LID_TOOLBARS[2], panelSkin) + + # Initialise status bar + self.statusBar = self.CreateStatusBar() + + # Set focus on & show window + self.SetFocus(); self.Show(True); + + return panelSkin + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTImgurApiKey.py.template b/MiRCARTImgurApiKey.py.template new file mode 100644 index 0000000..6580ac7 --- /dev/null +++ b/MiRCARTImgurApiKey.py.template @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# MiRCARTImgurApiKey.py -- melp? +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +class MiRCARTImgurApiKey(object): + imgurApiKey = None + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToPngFile.py b/MiRCARTToPngFile.py new file mode 100755 index 0000000..922fb4f --- /dev/null +++ b/MiRCARTToPngFile.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# +# MiRCARTToPngFile.py -- convert ASCII w/ mIRC control codes to monospaced PNG (for EFnet #MiRCART) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import MiRCARTCanvasImportStore +from PIL import Image, ImageDraw, ImageFont +import sys + +class MiRCARTToPngFile: + """XXX""" + inFile = inFromTextFile = None + outFontFilePath = outFontSize = None + + # {{{ _ColourMapBold: mIRC colour number to RGBA map given ^B (bold) + _ColourMapBold = [ + [255, 255, 255], # White + [85, 85, 85], # Grey + [85, 85, 255], # Light Blue + [85, 255, 85], # Light Green + [255, 85, 85], # Light Red + [255, 85, 85], # Light Red + [255, 85, 255], # Pink + [255, 255, 85], # Light Yellow + [255, 255, 85], # Light Yellow + [85, 255, 85], # Light Green + [85, 255, 255], # Light Cyan + [85, 255, 255], # Light Cyan + [85, 85, 255], # Light Blue + [255, 85, 255], # Pink + [85, 85, 85], # Grey + [255, 255, 255], # White + ] + # }}} + # {{{ _ColourMapNormal: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline) + _ColourMapNormal = [ + [255, 255, 255], # White + [0, 0, 0], # Black + [0, 0, 187], # Blue + [0, 187, 0], # Green + [255, 85, 85], # Light Red + [187, 0, 0], # Red + [187, 0, 187], # Purple + [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], # Light Blue + [255, 85, 255], # 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) + 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] & MiRCARTCanvasImportStore.MiRCARTCanvasImportStore._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] & MiRCARTCanvasImportStore.MiRCARTCanvasImportStore._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="DejaVuSansMono.ttf", fontSize=11): initialisation method + def __init__(self, inCanvasMap, fontFilePath="DejaVuSansMono.ttf", fontSize=11): + self.inCanvasMap = inCanvasMap + self.outFontFilePath = fontFilePath; self.outFontSize = int(fontSize); + self.outImgFont = ImageFont.truetype( \ + self.outFontFilePath, self.outFontSize) + self.outImgFontSize = [*self.outImgFont.getsize(" ")] + self.outImgFontSize[1] += 3 + +# +# Entry point +def main(*argv): + canvasStore = MiRCARTCanvasImportStore.MiRCARTCanvasImportStore(inFile=argv[1]) + MiRCARTToPngFile(canvasStore.outMap, *argv[3:]).export(argv[2]) +if __name__ == "__main__": + if ((len(sys.argv) - 1) < 2)\ + or ((len(sys.argv) - 1) > 4): + print("usage: {} " \ + " " \ + " " \ + "[] " \ + "[]".format(sys.argv[0]), file=sys.stderr) + else: + main(*sys.argv) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToPngFiles.sh b/MiRCARTToPngFiles.sh new file mode 100755 index 0000000..53e17b1 --- /dev/null +++ b/MiRCARTToPngFiles.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# MiRCARTToPngFiles.sh -- convert ASCII(s) w/ mIRC control codes to monospaced PNG(s) (for EFnet #MiRCART) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +for FNAME in "${@}"; do + ./MiRCARTToPngFile.py "${FNAME}" "${FNAME%.txt}.png"; +done; + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTTool.py b/MiRCARTTool.py new file mode 100644 index 0000000..4798ebf --- /dev/null +++ b/MiRCARTTool.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# MiRCARTTool.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +class MiRCARTTool(): + """XXX""" + parentCanvas = None + + # {{{ onKeyboardEvent(self, event, atPoint, brushColours, brushSize, keyChar, dispatchFn, eventDc): + def onKeyboardEvent(self, event, atPoint, brushColours, brushSize, keyChar, dispatchFn, eventDc): + return True + # }}} + # {{{ onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + return () + # }}} + + # + # __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/MiRCARTToolCircle.py b/MiRCARTToolCircle.py new file mode 100644 index 0000000..9249b69 --- /dev/null +++ b/MiRCARTToolCircle.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolCircle.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool + +class MiRCARTToolCircle(MiRCARTTool): + """XXX""" + name = "Circle" + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + brushColours = brushColours.copy() + if isLeftDown: + brushColours[1] = brushColours[0] + elif isRightDown: + brushColours[0] = brushColours[1] + else: + brushColours[1] = brushColours[0] + _brushSize = brushSize[0]*2 + originPoint = (_brushSize/2, _brushSize/2) + radius = _brushSize + for brushY in range(-radius, radius + 1): + for brushX in range(-radius, radius + 1): + if ((brushX**2)+(brushY**2) < (((radius**2)+radius)*0.8)): + patch = [ \ + atPoint[0] + int(originPoint[0]+brushX), \ + atPoint[1] + int(originPoint[1]+brushY), \ + *brushColours, 0, " "] + if isLeftDown or isRightDown: + dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); + else: + dispatchFn(eventDc, True, patch) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolFill.py b/MiRCARTToolFill.py new file mode 100644 index 0000000..00675e0 --- /dev/null +++ b/MiRCARTToolFill.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolFill.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool + +class MiRCARTToolFill(MiRCARTTool): + """XXX""" + name = "Fill" + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + pointStack = [list(atPoint)]; pointsDone = []; + testColour = self.parentCanvas.canvasMap[atPoint[1]][atPoint[0]][0:2] + if isLeftDown or isRightDown: + if isRightDown: + brushColours = [brushColours[1], brushColours[0]] + while len(pointStack) > 0: + point = pointStack.pop() + pointCell = self.parentCanvas.canvasMap[point[1]][point[0]] + if pointCell[0:2] == testColour: + if not point in pointsDone: + dispatchFn(eventDc, False, [*point, \ + brushColours[0], brushColours[0], 0, " "]) + if point[0] > 0: + pointStack.append([point[0] - 1, point[1]]) + if point[0] < (self.parentCanvas.canvasSize[0] - 1): + pointStack.append([point[0] + 1, point[1]]) + if point[1] > 0: + pointStack.append([point[0], point[1] - 1]) + if point[1] < (self.parentCanvas.canvasSize[1] - 1): + pointStack.append([point[0], point[1] + 1]) + pointsDone += [point] + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolLine.py b/MiRCARTToolLine.py new file mode 100644 index 0000000..d70143e --- /dev/null +++ b/MiRCARTToolLine.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolLine.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool + +class MiRCARTToolLine(MiRCARTTool): + """XXX""" + name = "Line" + toolColours = toolOriginPoint = toolState = None + + TS_NONE = 0 + TS_ORIGIN = 1 + + # {{{ _pointDelta(self, a, b): XXX + def _pointDelta(self, a, b): + return [a2-a1 for a1, a2 in zip(a, b)] + # }}} + # {{{ _pointSwap(self, a, b): XXX + def _pointSwap(self, a, b): + return [b, a] + # }}} + # {{{ _getLine(self, brushColours, brushSize, eventDc, isCursor, originPoint, targetPoint, dispatchFn): XXX + def _getLine(self, brushColours, brushSize, eventDc, isCursor, originPoint, targetPoint, dispatchFn): + originPoint = originPoint.copy(); targetPoint = targetPoint.copy(); + pointDelta = self._pointDelta(originPoint, targetPoint) + lineXSign = 1 if pointDelta[0] > 0 else -1; + lineYSign = 1 if pointDelta[1] > 0 else -1; + pointDelta = [abs(a) for a in pointDelta] + if pointDelta[0] > pointDelta[1]: + lineXX, lineXY, lineYX, lineYY = lineXSign, 0, 0, lineYSign + else: + pointDelta = [pointDelta[1], pointDelta[0]] + lineXX, lineXY, lineYX, lineYY = 0, lineYSign, lineXSign, 0 + lineD = 2 * pointDelta[1] - pointDelta[0]; lineY = 0; + for lineX in range(pointDelta[0] + 1): + for brushStep in range(brushSize[0]): + patch = [ \ + originPoint[0] + lineX*lineXX + lineY*lineYX + brushStep, \ + originPoint[1] + lineX*lineXY + lineY*lineYY, \ + *brushColours, 0, " "] + if isCursor: + dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); + else: + dispatchFn(eventDc, True, patch) + if lineD > 0: + lineD -= pointDelta[0]; lineY += 1; + lineD += pointDelta[1] + # }}} + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + brushColours = brushColours.copy() + if isLeftDown: + brushColours[1] = brushColours[0] + elif isRightDown: + brushColours[0] = brushColours[1] + else: + brushColours[1] = brushColours[0] + if self.toolState == self.TS_NONE: + if isLeftDown or isRightDown: + self.toolColours = brushColours + self.toolOriginPoint = list(atPoint) + self.toolState = self.TS_ORIGIN + dispatchFn(eventDc, True, [*atPoint, *brushColours, 0, " "]) + elif self.toolState == self.TS_ORIGIN: + targetPoint = list(atPoint) + originPoint = self.toolOriginPoint + self._getLine(self.toolColours, brushSize, \ + eventDc, isLeftDown or isRightDown, \ + originPoint, targetPoint, dispatchFn) + if isLeftDown or isRightDown: + self.toolColours = None + self.toolOriginPoint = None + self.toolState = self.TS_NONE + + # __init__(self, *args): initialisation method + def __init__(self, *args): + super().__init__(*args) + self.toolColours = None + self.toolOriginPoint = None + self.toolState = self.TS_NONE + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolRect.py b/MiRCARTToolRect.py new file mode 100644 index 0000000..66c0094 --- /dev/null +++ b/MiRCARTToolRect.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolRect.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool + +class MiRCARTToolRect(MiRCARTTool): + """XXX""" + name = "Rectangle" + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + brushColours = brushColours.copy() + if isLeftDown: + brushColours[1] = brushColours[0] + elif isRightDown: + brushColours[0] = brushColours[1] + else: + brushColours[1] = brushColours[0] + brushSize = brushSize.copy() + if brushSize[0] > 1: + brushSize[0] *= 2 + for brushRow in range(brushSize[1]): + for brushCol in range(brushSize[0]): + patch = [atPoint[0] + brushCol, atPoint[1] + brushRow, *brushColours, 0, " "] + if isLeftDown or isRightDown: + dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); + else: + dispatchFn(eventDc, True, patch) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolSelect.py b/MiRCARTToolSelect.py new file mode 100644 index 0000000..3084776 --- /dev/null +++ b/MiRCARTToolSelect.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolSelect.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool + +class MiRCARTToolSelect(MiRCARTTool): + """XXX""" + toolColours = toolRect = toolState = None + toolLastAtPoint = None + toolSelectMap = None + srcRect = None + + TS_NONE = 0 + TS_ORIGIN = 1 + TS_TARGET = 2 + + # {{{ _drawSelectRect(self, rect, dispatchFn, eventDc): XXX + def _drawSelectRect(self, rect, dispatchFn, eventDc): + rectFrame = [ \ + [rect[0][0]-1, rect[0][1]-1], \ + [rect[1][0]+1, rect[1][1]+1]] + if rectFrame[0][0] > rectFrame[1][0]: + rectFrame[0][0], rectFrame[1][0] = \ + rectFrame[1][0], rectFrame[0][0] + if rectFrame[0][1] > rectFrame[1][1]: + rectFrame[0][1], rectFrame[1][1] = \ + rectFrame[1][1], rectFrame[0][1] + curColours = [0, 0] + for rectX in range(rectFrame[0][0], rectFrame[1][0]+1): + if curColours == [0, 0]: + curColours = [1, 1] + else: + curColours = [0, 0] + dispatchFn(eventDc, True, \ + [rectX, rectFrame[0][1], *curColours, 0, " "]) + dispatchFn(eventDc, True, \ + [rectX, rectFrame[1][1], *curColours, 0, " "]) + for rectY in range(rectFrame[0][1], rectFrame[1][1]+1): + if curColours == [0, 0]: + curColours = [1, 1] + else: + curColours = [0, 0] + dispatchFn(eventDc, True, \ + [rectFrame[0][0], rectY, *curColours, 0, " "]) + dispatchFn(eventDc, True, \ + [rectFrame[1][0], rectY, *curColours, 0, " "]) + # }}} + + # + # onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): + pass + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + if self.toolState == self.TS_NONE: + if isLeftDown or isRightDown: + self.toolColours = [0, 1] + self.toolRect = [list(atPoint), []] + self.toolState = self.TS_ORIGIN + else: + dispatchFn(eventDc, True, \ + [*atPoint, *brushColours, 0, " "]) + elif self.toolState == self.TS_ORIGIN: + self.toolRect[1] = list(atPoint) + if isLeftDown or isRightDown: + if self.toolRect[0][0] > self.toolRect[1][0]: + self.toolRect[0][0], self.toolRect[1][0] = \ + self.toolRect[1][0], self.toolRect[0][0] + if self.toolRect[0][1] > self.toolRect[1][1]: + self.toolRect[0][1], self.toolRect[1][1] = \ + self.toolRect[1][1], self.toolRect[0][1] + self.srcRect = self.toolRect[0] + self.toolLastAtPoint = list(atPoint) + self.toolState = self.TS_TARGET + self.toolSelectMap = [] + for numRow in range((self.toolRect[1][1] - self.toolRect[0][1]) + 1): + self.toolSelectMap.append([]) + for numCol in range((self.toolRect[1][0] - self.toolRect[0][0]) + 1): + rectY = self.toolRect[0][1] + numRow + rectX = self.toolRect[0][0] + numCol + self.toolSelectMap[numRow].append( \ + self.parentCanvas.canvasMap[rectY][rectX]) + self._drawSelectRect(self.toolRect, dispatchFn, eventDc) + elif self.toolState == self.TS_TARGET: + if isRightDown: + self.onSelectEvent(event, atPoint, self.toolRect, \ + brushColours, brushSize, isLeftDown, isRightDown, \ + dispatchFn, eventDc) + self.toolColours = None + self.toolRect = None + self.toolState = self.TS_NONE + else: + self.onSelectEvent(event, atPoint, self.toolRect, \ + brushColours, brushSize, isLeftDown, isRightDown, \ + dispatchFn, eventDc) + + # __init__(self, *args): initialisation method + def __init__(self, *args): + super().__init__(*args) + self.toolColours = None + self.toolRect = None + self.toolState = self.TS_NONE + self.toolLastAtPoint = None + self.toolSelectMap = None + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolSelectClone.py b/MiRCARTToolSelectClone.py new file mode 100644 index 0000000..3d2a5d5 --- /dev/null +++ b/MiRCARTToolSelectClone.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolSelectClone.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTToolSelect import MiRCARTToolSelect + +class MiRCARTToolSelectClone(MiRCARTToolSelect): + """XXX""" + name = "Clone selection" + + # + # onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): + if isLeftDown: + atPoint = list(atPoint) + disp = [atPoint[0]-self.toolLastAtPoint[0], \ + atPoint[1]-self.toolLastAtPoint[1]] + self.toolLastAtPoint = atPoint + newToolRect = [ \ + [selectRect[0][0]+disp[0], selectRect[0][1]+disp[1]], \ + [selectRect[1][0]+disp[0], selectRect[1][1]+disp[1]]] + isCursor = True + elif isRightDown: + disp = [0, 0] + newToolRect = selectRect.copy() + isCursor = False + else: + disp = [0, 0] + newToolRect = selectRect.copy() + isCursor = True + for numRow in range(len(self.toolSelectMap)): + for numCol in range(len(self.toolSelectMap[numRow])): + cellOld = self.toolSelectMap[numRow][numCol] + rectY = selectRect[0][1] + numRow + rectX = selectRect[0][0] + numCol + dispatchFn(eventDc, isCursor, [rectX+disp[0], rectY+disp[1], *cellOld]) + self._drawSelectRect(newToolRect, dispatchFn, eventDc) + self.toolRect = newToolRect + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolSelectMove.py b/MiRCARTToolSelectMove.py new file mode 100644 index 0000000..134826c --- /dev/null +++ b/MiRCARTToolSelectMove.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolSelectMove.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTToolSelect import MiRCARTToolSelect + +class MiRCARTToolSelectMove(MiRCARTToolSelect): + """XXX""" + name = "Move selection" + + # + # onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onSelectEvent(self, event, atPoint, selectRect, brushColours, brushSize, isLeftDown, isRightDown, dispatchFn, eventDc): + if isLeftDown: + atPoint = list(atPoint) + disp = [atPoint[0]-self.toolLastAtPoint[0], \ + atPoint[1]-self.toolLastAtPoint[1]] + self.toolLastAtPoint = atPoint + newToolRect = [ \ + [selectRect[0][0]+disp[0], selectRect[0][1]+disp[1]], \ + [selectRect[1][0]+disp[0], selectRect[1][1]+disp[1]]] + isCursor = True + elif isRightDown: + disp = [0, 0] + newToolRect = selectRect.copy() + isCursor = False + else: + disp = [0, 0] + newToolRect = selectRect.copy() + isCursor = True + for numRow in range(len(self.toolSelectMap)): + for numCol in range(len(self.toolSelectMap[numRow])): + dispatchFn(eventDc, isCursor, [self.srcRect[0] + numCol, \ + self.srcRect[1] + numRow, 1, 1, 0, " "]) + for numRow in range(len(self.toolSelectMap)): + for numCol in range(len(self.toolSelectMap[numRow])): + cellOld = self.toolSelectMap[numRow][numCol] + rectY = selectRect[0][1] + numRow + rectX = selectRect[0][0] + numCol + dispatchFn(eventDc, isCursor, [rectX+disp[0], rectY+disp[1], *cellOld]) + self._drawSelectRect(newToolRect, dispatchFn, eventDc) + self.toolRect = newToolRect + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/MiRCARTToolText.py b/MiRCARTToolText.py new file mode 100644 index 0000000..8026eba --- /dev/null +++ b/MiRCARTToolText.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# MiRCARTToolText.py -- XXX +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from MiRCARTTool import MiRCARTTool +import wx + +class MiRCARTToolText(MiRCARTTool): + """XXX""" + name = "Text" + textColours = textPos = None + + # + # onKeyboardEvent(self, event, atPoint, brushColours, brushSize, keyChar, dispatchFn, eventDc): XXX + def onKeyboardEvent(self, event, atPoint, brushColours, brushSize, keyChar, dispatchFn, eventDc): + keyModifiers = event.GetModifiers() + if keyModifiers != wx.MOD_NONE \ + and keyModifiers != wx.MOD_SHIFT: + return True + else: + if self.textColours == None: + self.textColours = brushColours.copy() + if self.textPos == None: + self.textPos = list(atPoint) + dispatchFn(eventDc, False, [*self.textPos, *self.textColours, 0, keyChar]) + if self.textPos[0] < (self.parentCanvas.canvasSize[0] - 1): + self.textPos[0] += 1 + elif self.textPos[1] < (self.parentCanvas.canvasSize[1] - 1): + self.textPos[0] = 0 + self.textPos[1] += 1 + else: + self.textPos = [0, 0] + return False + + # + # onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): XXX + def onMouseEvent(self, event, atPoint, brushColours, brushSize, isDragging, isLeftDown, isRightDown, dispatchFn, eventDc): + if isLeftDown: + self.textColours = brushColours.copy() + self.textPos = list(atPoint) + elif isRightDown: + self.textColours = [brushColours[1], brushColours[0]] + self.textPos = list(atPoint) + else: + if self.textColours == None: + self.textColours = brushColours.copy() + self.textPos = list(atPoint) + dispatchFn(eventDc, True, [*self.textPos, *self.textColours, 0, "_"]) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2795ff8 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# MiRCART.py -- mIRC art editor for Windows & Linux (WIP) +* Prerequisites on Windows: install Python v3.6.x[1] and script dependencies w/ the following elevated command prompt command line: + `pip install requests urllib3 wxPython` +* Prerequisites on Linux: python3 && python-wx{gtk2.8,tools} on Debian-family Linux distributions +* Screenshot: +![Screenshot](https://github.com/lalbornoz/MiRCARTools/raw/master/MiRCART.png "Screenshot") + +# IrcMiRCARTBot.py -- IRC<->MiRC2png bot (for EFnet #MiRCART) (pending cleanup) +* Prerequisites: python3 && python3-{json,requests,urllib3} on Debian-family Linux distributions +* IrcMiRCARTBot.py usage: IrcMiRCARTBot.py `` [``] [``] [``] [``] [``] + +# MiRCARTToPngFile.py -- convert ASCII w/ mIRC control codes to monospaced PNG (pending cleanup) +* Prerequisites: python3 && python3-pil on Debian-family Linux distributions +* MiRC2png.py usage: MiRC2png.py `` `` [``] [``] + +References: +Fri, 05 Jan 2018 17:01:47 +0100 [1] Python Releases for Windows | Python.org diff --git a/assets/toolCircle.png b/assets/toolCircle.png new file mode 100644 index 0000000..9e34f72 Binary files /dev/null and b/assets/toolCircle.png differ diff --git a/assets/toolClone.png b/assets/toolClone.png new file mode 100644 index 0000000..ea358d3 Binary files /dev/null and b/assets/toolClone.png differ diff --git a/assets/toolDecrBrushH.png b/assets/toolDecrBrushH.png new file mode 100644 index 0000000..6b37273 Binary files /dev/null and b/assets/toolDecrBrushH.png differ diff --git a/assets/toolDecrBrushHW.png b/assets/toolDecrBrushHW.png new file mode 100644 index 0000000..b5c14ca Binary files /dev/null and b/assets/toolDecrBrushHW.png differ diff --git a/assets/toolDecrBrushW.png b/assets/toolDecrBrushW.png new file mode 100644 index 0000000..64e964b Binary files /dev/null and b/assets/toolDecrBrushW.png differ diff --git a/assets/toolDecrCanvasH.png b/assets/toolDecrCanvasH.png new file mode 100644 index 0000000..1ea55ef Binary files /dev/null and b/assets/toolDecrCanvasH.png differ diff --git a/assets/toolDecrCanvasHW.png b/assets/toolDecrCanvasHW.png new file mode 100644 index 0000000..2bcefdc Binary files /dev/null and b/assets/toolDecrCanvasHW.png differ diff --git a/assets/toolDecrCanvasW.png b/assets/toolDecrCanvasW.png new file mode 100644 index 0000000..3336980 Binary files /dev/null and b/assets/toolDecrCanvasW.png differ diff --git a/assets/toolFill.png b/assets/toolFill.png new file mode 100644 index 0000000..54b8737 Binary files /dev/null and b/assets/toolFill.png differ diff --git a/assets/toolIncrBrushH.png b/assets/toolIncrBrushH.png new file mode 100644 index 0000000..157c81a Binary files /dev/null and b/assets/toolIncrBrushH.png differ diff --git a/assets/toolIncrBrushHW.png b/assets/toolIncrBrushHW.png new file mode 100644 index 0000000..34f3a41 Binary files /dev/null and b/assets/toolIncrBrushHW.png differ diff --git a/assets/toolIncrBrushW.png b/assets/toolIncrBrushW.png new file mode 100644 index 0000000..6790bfc Binary files /dev/null and b/assets/toolIncrBrushW.png differ diff --git a/assets/toolIncrCanvasH.png b/assets/toolIncrCanvasH.png new file mode 100644 index 0000000..a3004a6 Binary files /dev/null and b/assets/toolIncrCanvasH.png differ diff --git a/assets/toolIncrCanvasHW.png b/assets/toolIncrCanvasHW.png new file mode 100644 index 0000000..4002cc8 Binary files /dev/null and b/assets/toolIncrCanvasHW.png differ diff --git a/assets/toolIncrCanvasW.png b/assets/toolIncrCanvasW.png new file mode 100644 index 0000000..48f8053 Binary files /dev/null and b/assets/toolIncrCanvasW.png differ diff --git a/assets/toolLine.png b/assets/toolLine.png new file mode 100644 index 0000000..206b104 Binary files /dev/null and b/assets/toolLine.png differ diff --git a/assets/toolMove.png b/assets/toolMove.png new file mode 100644 index 0000000..c52a274 Binary files /dev/null and b/assets/toolMove.png differ diff --git a/assets/toolRect.png b/assets/toolRect.png new file mode 100644 index 0000000..851cb4a Binary files /dev/null and b/assets/toolRect.png differ diff --git a/assets/toolText.png b/assets/toolText.png new file mode 100644 index 0000000..d27b969 Binary files /dev/null and b/assets/toolText.png differ